通过objectID获取NSManagedObjects数组返回空数组

lev*_*ker 6 core-data objective-c ios

我正在尝试使用从单独的上下文收集的对象ID数组来执行提取以从上下文中检索托管对象.但是,fetch返回一个空数组.

从"核心数据编程指南" 链接的"检索特定对象"部分 :

如果您的应用程序使用多个上下文并且您想测试是否已从持久性存储中删除对象,则可以使用self ==%@形式的谓词创建获取请求.作为变量传入的对象可以是托管对象,也可以是托管对象ID ......"

如果需要测试是否存在多个对象,则使用IN运算符比对单个对象执行多次提取更有效,例如:

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;
}
Run Code Online (Sandbox Code Playgroud)

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>,
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>});
sortDescriptors: ((null)); type: NSManagedObjectResultType; )

(lldb) po request.predicate
SELF IN {
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 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
Run Code Online (Sandbox Code Playgroud)

nil entitytestID很奇怪,但我不知道这是"坏".

所以,我显然不知道为什么获取回来是空的.实体名称正确,fetch和谓词看起来很好,对象在上下文中,但结果仍为零.

附加背景:

从本质上讲,更大的图片是我有一个后台操作,它使用自己的托管对象上下文(MOC)来执行一些工作.主要队列需要完成该工作的结果,因此我将工作产生的objectID打包并将其传递给主队列.在主队列上我需要返回实际的托管对象,所以我试图通过objectID从主队列的MOC中获取它们.我意识到我可以使用objectWithID:existingObjectWithID:error:objectRegisteredForID:至在MOC上获取这些对象,但每个都有自己的特殊问题:

  • objectWithID: 如果对象不在上下文中,则可能返回一个伪造的对象,如果它在上下文中,它将返回一个错误.
  • existingObjectWithID:error:是伟大的,因为我们会回来nil(而不是一个虚假的对象),但它也会返回一个错误.
  • objectRegisteredForID:nil如果对象已不在上下文中,则返回.

因此,如果我使用objectWithID:existingObjectWithID:error:在循环中获取一堆对象,那么可能会n跳转到持久存储以使对象出错,这意味着性能可能会非常糟糕.如果我使用objectRegisteredForID:我可能根本没有得到该对象,如果它恰好不在主队列的MOC中.

因此,我没有尝试迭代数组,而是期望获取请求会限制与persistance存储交互的开销,并一举返回我需要的所有对象.

加成:

顺便说一句,这个问题确实与@"self IN %@"谓词有关,因为我可以删除该谓词并获取所有entityName没有问题的对象.我也尝试过@"objectID IN %@"具有相同(零)结果的谓词.

lev*_*ker 12

好的,准备好沿着Core Data兔子洞旅行了吗?

TL; DR

NSManagedObjectID其持久性存储协调器不再在内存中丢失其NSEntityDescription(entity)并且不等同于(isEqual:返回NO)NSManagedObjectID来自不同持久性存储协调器的s,即使它们URIRepresentation是相同的.

沿着兔子洞

甜蜜......我们走了.

请记住,我是在一个单独的线程上从一个单独的托管对象上下文(MOC)收集objectIDs数组?好.但是,我没有提到MOC正在使用自己的持久存储协调器(PSC)(当然,指向同一个文件).PSC和MOC是短暂的,因为一旦工作完成它们就会被自动释放,但是当它们还活着时,我将实例保存NSManagedObjectID到一个NSArray(它被传递到另一个线程).

一旦在单独的线程中收到,NSManagedObjectIDs就有一个nil实体.这似乎正在发生(这里有教育的猜测......),因为这些objectID来自的PSC现在不再存在于内存中,并且NSManagedObjectID必须保持对PSC必须持有的NSEntityDescription(entity)的一周引用.这些nil entity似乎引起了问题,因为评论者怀疑......

我不能确定的内部的,但它会出现,如果没有entityNSManagedObjectID不是等于另一个NSManagedObjectID具有相同URIRepresentation.让我说明一下:

在下面的代码:*objectID是一个NSManagedObjectID *从一个持续性存储后者不再在存储器且其实体属性是nil.*managedObjectContext是我们当前线程的MOC(当然)

NSURL *URIRep = objectID.URIRepresentation;
NSManagedObjectID *newObjectID = [managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:URIRep];
Run Code Online (Sandbox Code Playgroud)

如果我们在调试器中看到这个...猜猜:

(lldb) p [ojectID isEqual:newObjectID]
(bool) $0 = false
Run Code Online (Sandbox Code Playgroud)

尽管URIRepresentation这两个objectID 必须相同,但对象并不等同.

这几乎可以肯定,谓词[NSPredicate predicateWithFormat:@"self IN %@", objectIDs]无法匹配任何对象并导致获取请求返回零对象.

然而有趣的是,在下面的代码,其中,testID是一种NSManagedObjectID *从持续性存储后者不再在存储器且其实体属性nil,所述对象从MOC检索没有问题:

NSManagedObject *obj = [managedObjectContext existingObjectWithID:testID error:&error];
Run Code Online (Sandbox Code Playgroud)

解决方法

有两件事我已经验证为解决方法:

  1. NSManagedObjectID在上下文之间进行通信时使用相同的持久存储协调器(PSC)(并确保它驻留在内存中).如果这样做,NSManagedObjectIDs永远不会失去它们entity,一切都按预期工作.

  2. 而不是NSManagedObjectID从上下文传递到上下文,使用他们的URIRepresentation.即,不是传递一个NSManagedObjectID对象数组,而是传递一个NSURL代表URIRepresentation每个对象的数组NSManagedObjectID.准备好使用这些objectID执行提取后,使用该managedObjectIDForURIRepresentation:消息NSManagedObjectID从当前MOC的PSC 获取a .

选项1更简单,但可能在并发时序方面存在一些缺点,例如,如果您希望为您的操作设置单独的PSC,则可能限制性太强.

在我(相对有限的)体验中,调用managedObjectIDForURIRepresentation:它似乎非常高效,因此将URIRepresentation NSURLs 转换为NSManagedObjectIDs似乎并没有增加那么多开销.

谢谢

感谢您的关注...我希望这有助于清楚地解释问题和解决方法.我当然觉得我通过挖掘所有这些来获得Core Data对象ID和持久性商店协调员的优点徽章.

干杯,

列维