切换UITabbar后CoreData托管对象丢失managedObjectContext求助
问题:UITabBar切换后Core Data实体丢失Managed Object Context
你遇到的情况是:UITabBar包含两个视图控制器,第一个用CollectionView展示的managedObjects列表运行正常,但切换Tab后,这些实体的managedObjectContext和关联数据都变成了nil——这个功能在旧版Xcode里能正常工作,但当前使用Xcode 9.3+Swift 3.3时出现了问题。你的切换逻辑伪代码如下:
// 此时上下文和属性都正常 NSLog("Context: %@", myEntities[0].managedObjectContext) NSLog("My label: %@", myEntities[0].label) // 切换Tab theTabbar.selectedItem = 1 // 这里managedObjectContext和label都变成nil了! NSLog("Context: %@", myEntities[0].managedObjectContext) NSLog("My label: %@", myEntities[0].label)
原因分析
Xcode 9之后,Apple对UIKit内存管理和Core Data上下文生命周期的处理做了调整,最可能的触发原因有这几个:
- 切换Tab时,第一个视图控制器被系统回收释放,导致其持有的Core Data上下文被销毁,关联的实体也就失去了上下文引用;
- 你的
myEntities数组使用了弱引用,当VC被释放时,实体对象被自动回收; - 上下文仅被单个VC持有,没有全局共享的实例,VC释放后上下文直接失效。
解决办法
1. 全局共享Managed Object Context
不要让上下文仅依附于单个VC,建议在AppDelegate或者专门的Core Data管理类中创建全局上下文实例,所有VC都使用这个共享上下文:
// 在AppDelegate中定义Core Data栈 lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "YourDataModelName") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Core Data加载失败:\(error), \(error.userInfo)") } }) return container }() // 提供全局可访问的上下文 var managedContext: NSManagedObjectContext { return persistentContainer.viewContext }
之后每个VC都通过以下方式获取上下文:
let context = (UIApplication.shared.delegate as! AppDelegate).managedContext
这样所有实体都会关联同一个全局上下文,不会因为单个VC释放而丢失上下文。
2. 避免视图控制器被意外释放
UITabBarController在内存紧张时会自动释放非当前的VC,你可以做这些检查:
- 确认UITabBarController的
viewControllers数组中的VC是强引用(默认是强引用,但如果手动修改过引用类型要排查); - 在第一个VC的
deinit方法中加日志,确认是否被释放:
如果确实被释放,说明需要调整VC的持有策略,或者改用传递实体deinit { print("FirstViewController已被释放") }objectID的方式(见下一条)。
3. 通过Object ID传递实体而非直接传对象
直接传递实体对象可能会因为原VC释放而导致实体失效,更稳妥的方式是传递实体的objectID,在目标VC中通过全局上下文重新获取实体:
// 在第一个VC中传递objectID if let targetVC = theTabbar.viewControllers?[1] as? SecondViewController { targetVC.targetObjectID = myEntities[0].objectID } theTabbar.selectedItem = 1 // 在第二个VC中定义接收属性 var targetObjectID: NSManagedObjectID? // 在第二个VC的viewDidLoad中获取实体 override func viewDidLoad() { super.viewDidLoad() guard let objectID = targetObjectID else { return } let context = (UIApplication.shared.delegate as! AppDelegate).managedContext do { let entity = try context.existingObject(with: objectID) as! YourEntityType // 这里就可以正常使用entity的属性和上下文了 print("Context: \(entity.managedObjectContext)") print("Label: \(entity.label)") } catch { print("获取实体失败:\(error)") } }
4. 检查Core Data栈的配置
确保你的Core Data栈没有配置错误:
- 确认
NSPersistentContainer的名称和数据模型文件名完全一致; - 检查上下文的
mergePolicy是否合理,避免因为数据合并冲突导致上下文失效:persistentContainer.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
内容的提问来源于stack exchange,提问作者Neekobus
相关产品推荐
相关产品推荐

