从“核心数据编程指南”的“检索特定对象”部分
link:
If your application uses multiple contexts and you want to test whether an object has been deleted from a persistent store,you can create a fetch request with a predicate of the form self == %@. The object you pass in as the variable can be either a managed object or a managed object ID…”
If you need to test for the existence of several objects,it is more efficient to use the IN operator than it is to execute multiple fetches for individual objects,for example:
nspredicate *predicate = [nspredicate predicateWithFormat:@”self IN %@”,
arrayOfManagedobjectIDs];
虽然这是关于测试对象删除,但我假设如果没有删除对象,那么结果数组不为空,并且将包含实际的NSManagedobjects.但是,当我执行此代码时:
- (NSArray *)managedobjectsOfEntityName:(Nsstring *)entityName fromObjectIDs:(NSArray *)objectIDs managedobjectContext:(NSManagedobjectContext *)managedobjectContext { NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName]; request.predicate = [nspredicate predicateWithFormat:@"self IN %@",objectIDs]; __autoreleasing NSError *error = nil; NSManagedobjectID *testID = objectIDs[0]; NSManagedobject *obj = [managedobjectContext existingObjectWithID:testID error:&error]; if (!obj) { NSLog(@"Unable to perform fetch. Error: %@",error); } error = nil; NSArray *results = [managedobjectContext executeFetchRequest:request error:&error]; if (!results) { NSLog(@"Unable to perform fetch. Error: %@",error); } return results; }
结果数组是非零和空的,而obj已正确填充.
我已经添加了对existingObjectWithID的显式调用:作为完整性检查,它返回预期的对象,没有错误.
这是相关变量的调试器输出:
(lldb) po entityName Foo (lldb) po objectIDs <__NSArrayI 0x1170d4950>( 0xd0000000055c0082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p343>,0xd000000008e80082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p570>,0xd000000008840082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p545>,0xd000000006040082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p385>,0xd000000007740082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p477>,0xd000000008280082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p522>,0xd000000008e40082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p569> ) (lldb) po request <NSFetchRequest: 0x10f338840> (entity: Foo; predicate: (SELF IN { 0xd0000000055c0082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p343>,0xd000000008e40082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p569>}); sortDescriptors: ((null)); type: NSManagedobjectResultType; ) (lldb) po request.predicate SELF IN { 0xd0000000055c0082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p343>,0xd000000008e40082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p569>} (lldb) po testID 0xd0000000055c0082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p343> (lldb) p testID.istemporaryID (bool) $7 = false (lldb) p obj (NSManagedobject *) $9 = 0x000000010f338630 (lldb) po results <__NSArrayI 0x10f205c70>( ) (lldb) po testID.entity nil
testID上的nil实体很奇怪,但我不确定它是“坏”.
所以,我显然不知道为什么获取回来是空的.实体名称正确,fetch和谓词看起来很好,对象在上下文中,但结果仍为零.
附加背景:
从本质上讲,更大的图片是我有一个后台操作,它使用自己的托管对象上下文(MOC)来执行一些工作.主要队列需要完成该工作的结果,因此我将工作产生的objectID打包并将其传递给主队列.在主队列上我需要返回实际的托管对象,所以我试图通过objectID从主队列的MOC中获取它们.我意识到我可以在MOC上使用objectWithID:或existingObjectWithID:error:甚至objectRegisteredForID:来获取这些对象,但每个都有自己的特殊问题:
> objectWithID:如果对象不在上下文中,则可能返回一个伪造的对象,如果它在上下文中,它将返回一个错误.
> existingObjectWithID:错误:非常好,因为我们将返回nil(而不是伪造的对象),但它也会返回错误.
> objectRegisteredForID:如果对象不在上下文中,则返回nil.
因此,如果我使用objectWithID:或existingObjectWithID:error:在循环中获取一堆对象,那么可能会在持久存储中跳转以使对象出错,这意味着性能可能会非常糟糕.如果我使用objectRegisteredForID:如果它恰好不在主队列的MOC中,我可能根本不会得到该对象.
因此,我没有尝试迭代数组,而是期望获取请求会限制与persistance存储交互的开销,并一举返回我需要的所有对象.
加成:
顺便说一句,问题确实与@“self IN%@”谓词有关,因为我可以删除该谓词并获取entityName的所有对象而不会出现问题.我还尝试使用@“objectID IN%@”作为具有相同(零)结果的谓词.
解决方法
TL; DR
其持久性存储协调器不再在内存中的NSManagedobjectID会丢失其NSEntityDescription(实体),并且不会将(isEqual:返回NO)等同于来自不同持久性存储协调器的NSManagedobjectID,即使它们的URIRepresentation相同也是如此.
沿着兔子洞
甜蜜……我们走了.
请记住,我是在一个单独的线程上从一个单独的托管对象上下文(MOC)收集objectIDs数组?好.但是,我没有提到MOC正在使用自己的持久存储协调器(PSC)(当然,指向同一个文件). PSC和MOC是短暂的,因为一旦工作完成它们就会被自动释放,但是当它们还活着时,我将NSManagedobjectID的实例保存到NSArray中(它被传递给另一个线程).
一旦在单独的线程中收到,NSManagedobjectID就有一个nil实体.这似乎正在发生(有教育的猜测……),因为这些objectID来自的PSC现在不再在内存中,并且NSManagedobjectID必须保持对必须由PSC持有的NSEntityDescription(实体)的一周引用.正如评论者所怀疑的那样,零实体似乎会引起问题……
我无法确定内部结构,但似乎没有实体,NSManagedobjectID不等于具有相同URIRepresentation的另一个NSManagedobjectID.让我说明一下:
在以下代码中:
* objectID是来自持久性存储的NSManagedobjectID *,它不再存储在内存中,其实体属性为nil.
* managedobjectContext是我们当前线程上的MOC(当然)
NSURL *URIRep = objectID.URIRepresentation; NSManagedobjectID *newObjectID = [managedobjectContext.persistentStoreCoordinator managedobjectIDForURIRepresentation:URIRep];
如果我们在调试器中看到这个…猜猜:
(lldb) p [ojectID isEqual:newObjectID] (bool) $0 = false
尽管这两个objectID的URIRepresentation必须相同,但对象并不等同.
这几乎可以肯定为什么谓词[nspredicate predicateWithFormat:@“self IN%@”,objectIDs]无法匹配任何对象并导致获取请求返回零对象.
然而,有趣的是,在下面的代码中,testID是来自持久性存储的NSManagedobjectID *,它不再存储在内存中且其实体属性为nil,从MOC中检索对象时没有问题:
NSManagedobject *obj = [managedobjectContext existingObjectWithID:testID error:&error];
解决方法
有两件事我已经验证为解决方法:
>在上下文之间传递NSManagedobjectID时使用相同的持久存储协调器(PSC)(并确保它驻留在内存中).如果这样做,NSManagedobjectIDs永远不会丢失它们的实体,一切都按预期工作.
>不要将NSManagedobjectID从上下文传递到上下文,而是使用它们的URIRepresentation.即,不是传递NSManagedobjectID对象的数组,而是传递表示每个NSManagedobjectID的URIRepresentation的NSURL数组.准备好使用这些objectID执行提取后,使用managedobjectIDForURIRepresentation:消息从当前MOC的PSC获取NSManagedobjectID.
选项1更简单,但可能在并发时序方面存在一些缺点,例如,如果您希望为您的操作设置单独的PSC,则可能限制性太强.
在我(相对有限的)体验中调用managedobjectIDForURIRepresentation:它似乎非常高效,因此将URIRepresentation NSURL转换为NSManagedobjectID似乎不会增加太多开销.
谢谢
感谢您的关注…我希望这有助于清楚地解释问题和解决方法.我当然觉得我通过挖掘所有这些来获得Core Data对象ID和持久性商店协调员的优点徽章.
干杯,
列维