backgroundContext 是 NSManagedObjectContext 的一种后台上下文,用于在后台线程中执行 Core Data 相关的任务,避免阻塞主线程(UI 线程)。
使用场景
当主线程上执行大量的 Core Data 操作(如大批量数据导入、删除或复杂的查询)时,会导致 UI 卡顿或崩溃。为了避免这种情况,可以使用 backgroundContext 来在后台线程进行数据处理,不影响 UI 流畅度。
核心特点
后台线程运行:backgroundContext 不在主线程上运行,所以不会阻塞 UI。
异步任务:可以通过 perform 或 performAndWait 异步或同步执行操作。
独立的队列:backgroundContext 拥有自己的操作队列,独立管理事务。
创建 backgroundContext
1、使用 NSPersistentContainer 创建 backgroundContext
let container = NSPersistentContainer(name: "MyDataModel")
container.loadPersistentStores { (storeDescription, error) in
if let error = error {
fatalError("Error loading Core Data: \(error)")
}
}
// 创建 backgroundContext
let backgroundContext = container.newBackgroundContext()
2、使用 perform 异步执行任务
backgroundContext.perform {
let newTask = Task(context: backgroundContext)
newTask.name = "Background Task"
newTask.timestamp = Date()
do {
try backgroundContext.save()
print("Data saved successfully in background!")
} catch {
print("Error saving in background: \(error.localizedDescription)")
}
}
3、使用 performAndWait 同步执行任务
backgroundContext.performAndWait {
let newTask = Task(context: backgroundContext)
newTask.name = "Sync Background Task"
newTask.timestamp = Date()
do {
try backgroundContext.save()
print("Data saved synchronously in background!")
} catch {
print("Error saving in background: \(error.localizedDescription)")
}
}
perform vs. performAndWait
1、perform
异步执行,不会阻塞当前线程。
适合大批量数据导入、更新或删除等耗时操作。
backgroundContext.perform {
// 异步操作
}
2、performAndWait
同步执行,会阻塞当前线程,直到任务完成。
适合需要立刻完成的任务,但要谨慎使用,避免阻塞主线程。
backgroundContext.performAndWait {
// 同步操作
}
更新 viewContext
当 backgroundContext 中的数据更改时,主线程上的 viewContext 不会自动更新,所以需要手动通知它更新。
1、通过 Notification 通知 viewContext 刷新
NotificationCenter.default.addObserver(
forName: .NSManagedObjectContextDidSave,
object: backgroundContext,
queue: .main
) { notification in
viewContext.mergeChanges(fromContextDidSave: notification)
}
2、在后台 context 保存时通知 viewContext
backgroundContext.perform {
let newTask = Task(context: backgroundContext)
newTask.name = "New Task"
newTask.timestamp = Date()
do {
try backgroundContext.save()
// 通知 viewContext 更新
DispatchQueue.main.async {
viewContext.mergeChanges(fromContextDidSave: backgroundContext)
}
} catch {
print("Error saving in background: \(error.localizedDescription)")
}
}
使用场景
大批量数据导入/导出
网络数据同步到本地数据库
后台更新或删除数据
避免主线程 UI 卡顿
使用示例
在Xcode项目中,通常在入口文件中声明container,backgroundContext也可以通过environment注入到子视图中。
首先需要创建自定义的EnvironmentKey和添加环境变量扩展。
import SwiftUI
import CoreData
// 创建自定义 EnvironmentKey
struct BackgroundContextKey: EnvironmentKey {
static let defaultValue: NSManagedObjectContext? = nil
}
// 添加环境变量扩展
extension EnvironmentValues {
var backgroundContext: NSManagedObjectContext? {
get { self[BackgroundContextKey.self] }
set { self[BackgroundContextKey.self] = newValue }
}
}
在入口文件中,通过environment把backgroundContext传递到子视图中。
import SwiftUI
import CoreData
@main
struct ERdepotApp: App {
// 创建 NSPersistentContainer
let container: NSPersistentContainer
init() {
// 加载 xcdatamodeld 文件,确保名字匹配
container = NSPersistentContainer(name: "ExchangeRateDataModel")
// 加载持久化存储
container.loadPersistentStores { (storeDescription, error) in
print("storeDescription:\(storeDescription)")
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
}
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, container.viewContext)
.environment(\.backgroundContext, container.newBackgroundContext())
}
}
}
在子视图ContentView中,使用Environment接收backgroundContext。
@Environment(\.backgroundContext) private var backgroundContext
通过 addFetchedResult 和 deleteFetchedResult 调用backgroundContext。
func addFetchedResult() {
guard let backgroundContext = backgroundContext else {
print("backgroundContext is nil")
return
}
backgroundContext.perform {
let newEurofxrefhist = Eurofxrefhist(context: backgroundContext)
newEurofxrefhist.date = Date()
newEurofxrefhist.currencySymbol = "USD"
newEurofxrefhist.exchangeRate = 7
do {
try backgroundContext.save()
print("数据添加成功!")
} catch {
print("数据保存失败:\(error.localizedDescription)")
}
}
}
func deleteFetchedResult(Eurofxrefhist: Eurofxrefhist) {
guard let backgroundContext = backgroundContext else {
print("backgroundContext is nil")
return
}
let objectID = Eurofxrefhist.objectID
backgroundContext.perform {
if let objectToDelete = try? backgroundContext.existingObject(with: objectID) {
backgroundContext.delete(objectToDelete)
do {
try backgroundContext.save()
print("数据删除成功!")
} catch {
print("删除数据失败:\(error.localizedDescription)")
}
}
}
}
通过 addFetchedResult() 和 deleteFetchedResult(Eurofxrefhist: FetchedResultToDelete) 调用方法。
在使用backgroundContext时,还需要添加一个通知监听器,监听 NSManagedObjectContextDidSave 通知并合并更改。
.onAppear {
NotificationCenter.default.addObserver(
forName: .NSManagedObjectContextDidSave,
object: backgroundContext,
queue: .main
) { notification in
viewContext.perform {
viewContext.mergeChanges(fromContextDidSave: notification)
}
}
}
如果不添加通知监听器,可能报错:
Cannot convert value of type 'NSManagedObjectContext' to expected argument type 'Notification'
最后,完成backgroundContext的使用。
注意事项
backgroundContext 和 viewContext 不会自动同步数据,必须手动合并更改。
不要在 viewContext 和 backgroundContext 之间直接传递 NSManagedObject,否则会出现线程冲突。
总结
backgroundContext 允许后台线程进行 Core Data 操作,防止阻塞主线程。
使用 perform 进行异步操作,performAndWait 进行同步操作。
通过 mergeChanges 或 Notification 手动更新 viewContext。
相关文章
1、Core Data管理数据生命周期的NSManagedObjectContext:https://fangjunyu.com/2025/03/30/core-data%e7%ae%a1%e7%90%86%e6%95%b0%e6%8d%ae%e7%94%9f%e5%91%bd%e5%91%a8%e6%9c%9f%e7%9a%84nsmanagedobjectcontext/
2、iOS通知机制NotificationCenter:https://fangjunyu.com/2025/03/01/ios%e9%80%9a%e7%9f%a5%e6%9c%ba%e5%88%b6notificationcenter/
附录
入口文件代码
import SwiftUI
import CoreData
@main
struct ERdepotApp: App {
// 创建 NSPersistentContainer
let container: NSPersistentContainer
init() {
// 加载 xcdatamodeld 文件,确保名字匹配
container = NSPersistentContainer(name: "ExchangeRateDataModel")
// 加载持久化存储
container.loadPersistentStores { (storeDescription, error) in
print("storeDescription:\(storeDescription)")
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
}
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, container.viewContext)
.environment(\.backgroundContext, container.newBackgroundContext())
}
}
}
// 创建自定义 EnvironmentKey
struct BackgroundContextKey: EnvironmentKey {
static let defaultValue: NSManagedObjectContext? = nil
}
// 添加环境变量扩展
extension EnvironmentValues {
var backgroundContext: NSManagedObjectContext? {
get { self[BackgroundContextKey.self] }
set { self[BackgroundContextKey.self] = newValue }
}
}
视图文件代码
import SwiftUI
import CoreData
struct ContentView: View {
// 通过 @Environment 读取 viewContext
@Environment(\.managedObjectContext) private var viewContext
@Environment(\.backgroundContext) private var backgroundContext //
// 使用 @FetchRequest 获取数据
@FetchRequest(
entity: Eurofxrefhist.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Eurofxrefhist.date, ascending: true)],
animation: .default)
private var exchangeRates: FetchedResults<Eurofxrefhist>
func addFetchedResult() {
guard let backgroundContext = backgroundContext else {
print("backgroundContext is nil")
return
}
backgroundContext.perform {
let newEurofxrefhist = Eurofxrefhist(context: backgroundContext)
newEurofxrefhist.date = Date()
newEurofxrefhist.currencySymbol = "USD"
newEurofxrefhist.exchangeRate = 7
do {
try backgroundContext.save()
print("数据添加成功!")
} catch {
print("数据保存失败:\(error.localizedDescription)")
}
}
}
func deleteFetchedResult(Eurofxrefhist: Eurofxrefhist) {
guard let backgroundContext = backgroundContext else {
print("backgroundContext is nil")
return
}
let objectID = Eurofxrefhist.objectID
backgroundContext.perform {
if let objectToDelete = try? backgroundContext.existingObject(with: objectID) {
backgroundContext.delete(objectToDelete)
do {
try backgroundContext.save()
print("数据删除成功!")
} catch {
print("删除数据失败:\(error.localizedDescription)")
}
}
}
}
var body: some View {
NavigationView {
VStack {
if exchangeRates.isEmpty {
Text("这里是空的")
} else {
List {
ForEach(exchangeRates, id: \.self) { exchangeRate in
Text("\(exchangeRate.date ?? Date())")
}
}
}
}
.toolbar{
ToolbarItem(placement: .topBarLeading) {
Button(action: {
addFetchedResult()
}, label: {
Image(systemName: "plus")
})
}
ToolbarItem(placement: .topBarTrailing) {
Button(action: {
if let FetchedResultToDelete = exchangeRates.first {
deleteFetchedResult(Eurofxrefhist: FetchedResultToDelete)
}
}, label: {
Image(systemName: "xmark")
})
}
}
.onAppear {
NotificationCenter.default.addObserver(
forName: .NSManagedObjectContextDidSave,
object: backgroundContext,
queue: .main
) { notification in
viewContext.perform {
viewContext.mergeChanges(fromContextDidSave: notification)
}
}
}
}
}
}
#Preview {
// 创建 NSPersistentContainer
let container = NSPersistentContainer(name: "ExchangeRateDataModel")
// 存储在内存中,防止写入磁盘
container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
// 加载持久化存储
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
// 预加载一些数据进行预览
let context = container.viewContext
let sampleData = Eurofxrefhist(context: context)
sampleData.date = Date()
sampleData.currencySymbol = "USD"
sampleData.exchangeRate = 7.0
do {
try context.save()
} catch {
print("Error saving preview data: \(error.localizedDescription)")
}
return ContentView()
.environment(\.managedObjectContext,container.viewContext)
.environment(\.backgroundContext, container.newBackgroundContext())
}