通过objectID获取NSManagedObjects数组将返回空数组 [英] Fetching array of NSManagedObjects by objectID returns empty array

查看:67
本文介绍了通过objectID获取NSManagedObjects数组将返回空数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试执行提取操作,以使用从不同上下文中收集的对象ID数组从上下文中检索托管对象。但是,获取时返回的是一个空数组。



来自核心数据编程指南的检索特定对象部分
链接


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



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



NSPredicate * predicate = [NSPredicate predicateWithFormat:@ self IN %@,
arrayOfManagedObjectIDs];


虽然这是关于对象删除的测试,但我假设对象是否没有删除,则结果数组不为空,并且将包含实际的NSManagedObjects。但是,当我执行以下代码时:

 - (NSArray *)managedObjectsOfEntityName:(NSString *)entityName fromObjectIDs:(NSArray *)objectIDsmanagedObjectContext:(NSManagedObjectContext *)managedObjectContext 
{
NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityNa我:entityName];
request.predicate = [NSPredicate predicateWithFormat:@ self IN%@,objectIDs];

__自动释放NSError * error = nil;

NSManagedObjectID * testID = objectIDs [0];
NSManagedObject * obj = [managedObjectContext existingObjectWithID:testID error:& error];
if(!obj)
{
NSLog(@无法执行抓取。错误:%@,错误);
}

error = nil;
NSArray * results = [managedObjectContext executeFetchRequest:request error:& error];
if(!results)
{
NSLog(@无法执行读取。错误:%@,错误);
}

返回结果;
}

结果数组是非零且为空,而 obj 已正确填充。



我已将显式调用添加到 existingObjectWithID:作为健全性检查,它随预期对象一起返回,没有错误。



这是调试器的输出有关变量:

 (lldb)po实体名称
Foo

(lldb) po对象ID
< __ NSArrayI 0x1170d4950>(
0xd0000000055c0082< x-coredata:// CEB1EDA5-7F7A-4342-85E5-6C2E261308CC / Foo / p343> ;,
0xd000000008e80082< x-core :// 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请求
< NSFetchRequest:0x10f338840> (实体:Foo;谓词:(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>,
0xd000000006040 < 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));类型: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 / / p570>,
0xd00000 0008840082< 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 =假

(lldb)p obj
(NSManagedObject *)$ 9 = 0x000000010f338630

(lldb)po结果
< __ NSArrayI 0x10f205c70>(



(lldb)po testID.entity
nil

nil testID 上的>实体很奇怪,但是我不确定那是不好的。



因此,对于提取为何会变空的原因,我显然感到茫然。实体名称正确,提取和谓词看起来不错,并且对象位于上下文中,但结果仍为零。



其他上下文:



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




  • objectWithID :
  • <$:如果该对象不在上下文中,则该对象可能是假的,并且如果该对象不在上下文中,则会返回错误。

  • existingObjectWithID:error:很棒,因为我们会返回 nil (而不是伪造的对象),但它也会返回一个错误。

  • objectRegisteredForID:如果对象不在其中,将返回 nil



因此,如果我使用 objectWithID: existingObjectWithID:error:循环获取一堆对象,这有可能 n 到持久化存储中进行故障处理对象,这意味着性能可能会很糟糕。如果我使用 objectRegisteredForID:如果恰好不在主队列的MOC中,则可能根本无法获得该对象。



因此,我希望不要尝试遍历数组,而是希望获取请求会限制与持久性存储进行交互的开销,并一次又一次地返回我需要的所有对象。



加法:



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

解决方案

好吧,那么准备去Core Data兔子洞了吗?



TL; DR

永久存储协调器不再位于内存中的

NSManagedObjectID s丢失其 NSEntityDescription entity )并且不等于( isEqual:返回 NO )到另一个持久存储协调器的 NSManagedObjectID ,即使它们的 URIRepresentation 是相同的。 / p>

沿着兔子洞



甜... p>

还记得吗,我是在单独的线程上从单独的受管对象上下文(MOC)收集objectID数组吗?好。但是,我没有提到MOC使用其自己的持久性存储协调器(PSC)(当然,指向同一文件)。 PSC和MOC寿命很短,因为一旦完成工作,它们就会自动发布,但是当它们仍然存在时,我将 NSManagedObjectID 的实例保存到 NSArray (将其传递给另一个线程)。



在单独的线程中收到 NSManagedObjectID 有一个 nil 实体。似乎正在发生这种情况(在这里进行了合理的猜测...),因为这些objectID所来自的PSC现在不再在内存中,并且 NSManagedObjectID 必须保留一周的引用时间 NSEntityDescription 实体),必须由PSC持有。 nil entity 似乎引起了问题,因为评论者怀疑...



我不确定内部结构,但似乎没有实体 NSManagedObjectID 等于另一个具有相同 URIRepresentation NSManagedObjectID 。让我说明一下:



在以下代码中:
* objectID 是一个 NSManagedObjectID * 来自不再位于内存中且其实体属性为 nil 的持久性存储。
* managedObjectContext 是我们当前线程上的MOC(当然)

  NSURL * URIRep = objectID.URIRepresentation; 
NSManagedObjectID * newObjectID = [managedObjectContext.persistentStoreCoordinatormanagedObjectIDForURIRepresentation:URIRep];

如果我们在调试器中查看此内容,请猜测...

 (lldb)p [ojectID isEqual:newObjectID] 
(bool)$ 0 =假

即使这两个objectID的 URIRepresentation 必须相同,对象不等于。



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



有趣的是,在以下代码中,其中 testID 是来自持久性存储的 NSManagedObjectID * ,该存储不再位于内存中并且其实体属性为 nil ,则可以从MOC检索对象而不会出现以下问题:

  NSManagedObject * obj = [managedObjectContext existingObjectWithID:testID错误:&错误]; 

解决方法



我已验证了以下两种解决方案:


  1. 使用相同的持久性存储协调器(PSC )在上下文之间传递 NSManagedObjectID 时(并确保它驻留在内存中)。如果这样做, NSManagedObjectID 将永远不会丢失其实体,并且一切都会按预期进行。


  2. 而不是在上下文之间传递 NSManagedObjectID ,而是使用它们的 URIRepresentation 。即,而不是传递数组 NSManagedObjectID 对象,而是传递数组 NSURL s表示每个 NSManagedObjectID 的URIRepresentation 。一旦准备好使用这些对象ID执行访存,请使用 managedObjectIDForURIRepresentation:消息从当前MOC的PSC中获取 NSManagedObjectID


选项1比较简单,但是在并发计时方面可能有一些缺点,如果您说



根据我的经验(相对有限),调用 managedObjectIDForURIRepresentation:



code>似乎表现出色,因此将 URIRepresentation NSURL s转换为 NSManagedObjectID 似乎并没有增加太多开销。



谢谢



感谢您的跟进...希望这有助于清楚地说明问题和解决方法。通过挖掘所有这些,我当然感觉像是在Core Data对象ID和持久性存储协调员中赢得了荣誉徽章。



干杯,



李维


I'm trying to perform a fetch to retrieve managed objects from the context using an array of object IDs I gathered from a separate context. However, the fetch comes back with an empty array.

From the "Retrieving Specific Objects" section of the "Core Data Programming Guide" 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];

While this is talking about testing for object deletion, I presumed if the objects are not deleted then the results array is not empty, and will contain the actual NSManagedObjects. However, when I execute this code:

- (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;
}

The results array is non-nil and empty, while obj is properly populated.

I've added the explicit call to existingObjectWithID: as a sanity check, and it comes back with the expected object, without error.

Here's the debugger output for the pertinent variables:

(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

The nil entity on the testID is strange, but I'm not sure that's "bad."

So, I'm obviously at a loss as to why the fetch is coming back empty. The entity name is correct, the fetch and predicate look good, and the object is in the context, but still zero results.

Additional Context:

Essentially, the larger picture is that I have a background operation which uses its own Managed Object Context (MOC) to perform some work. The results of that work are needed on the main queue, so I package up the objectIDs resulting from the work and pass those to the main queue. On the main queue I need the actual managed objects back, so I'm trying to fetch them, by objectID, from the main queue's MOC. I realize I can use objectWithID: or existingObjectWithID:error: or even objectRegisteredForID: on the MOC to get these objects, but each have their own special issues:

  • objectWithID: might return an object which is bogus if the object is not in the context, and if it is in the context it will return a fault.
  • existingObjectWithID:error: is great, since we'll get back nil (rather than a bogus object) but it too returns a fault.
  • objectRegisteredForID: will return nil if the object is not in the context already.

So, if I use either objectWithID: or existingObjectWithID:error: in a loop to get a bunch of objects, that's potentially n trips to the persisted store to fault the objects, meaning performance is going to be potentially awful. If I use objectRegisteredForID: I might not get the object at all if it happens to not already be in the main queue's MOC.

So, rather than try to iterate over the array, I expected a fetch request would limit the overhead of interacting with the persistance store and return all the objects I needed in one fell swoop.

Addition:

As an aside, the issue really feels like it has to do with the @"self IN %@" predicate, since I can remove that predicate and fetch all objects of entityName without issue. I have also tried @"objectID IN %@" as the predicate with the same (zero) results.

解决方案

Okay, so ready for a trip down the Core Data rabbit hole?

TL;DR

NSManagedObjectIDs whose persistent store coordinator is no longer in memory lose their NSEntityDescription (entity) and do not equate (isEqual: returns NO) to NSManagedObjectIDs from a different persistent store coordinator even though their URIRepresentation is the same.

Down the Rabbit Hole

Sweet... here we go.

Remember, I'm gathering the array of objectIDs from a separate Managed Object Context (MOC) on a separate thread? Good. I didn't mention, however, that that MOC is using its own Persistent Store Coordinator (PSC) (pointing at the same file, of course). That PSC and MOC are short lived since once the work is done they are autoreleased, but while they are alive, I save off instances of NSManagedObjectID into an NSArray (which gets passed to a different thread).

Once received in the separate thread, the NSManagedObjectIDs have a nil entity. This seems to be happening (educated guess here...) because the PSC from which these objectIDs came from is now no longer in memory and the NSManagedObjectID must keep a week reference to the NSEntityDescription (entity) which must be held by the PSC. The nil entity seems to cause issues, as commenters suspected...

I can't be sure of the internals, but it would appear that without the entity the NSManagedObjectID is not equal to another NSManagedObjectID with the same URIRepresentation. Let me illustrate:

In the following code: * objectID is an NSManagedObjectID * from a persistant store which is no longer in memory and whose entity property is nil. * managedObjectContext is our MOC on our current thread (of course)

NSURL *URIRep = objectID.URIRepresentation;
NSManagedObjectID *newObjectID = [managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:URIRep];

If we look at this in the debugger... guess what:

(lldb) p [ojectID isEqual:newObjectID]
(bool) $0 = false

Even though the URIRepresentation of these two objectIDs must be the same, the objects do not equate.

This is almost certainly why the predicate [NSPredicate predicateWithFormat:@"self IN %@", objectIDs] is failing to match any objects and causing the fetch request to return zero objects.

Interestingly, however, in the following code, where testID is an NSManagedObjectID * from a persistant store which is no longer in memory and whose entity property is nil, the object is retrieved from the MOC without issue:

NSManagedObject *obj = [managedObjectContext existingObjectWithID:testID error:&error];

Workaround

There are two things which I've verified as a workaround for this:

  1. Use the same Persistent Store Coordinator (PSC) when communicating NSManagedObjectIDs between contexts (and make sure it stays resident in memory). If this is done, the NSManagedObjectIDs never lose their entity and everything works as expected.

  2. Instead of passing NSManagedObjectIDs around from context to context, use their URIRepresentation. i.e. instead of passing an array of NSManagedObjectID objects, pass an array of NSURLs representing the URIRepresentation of each NSManagedObjectID. Once ready to perform a fetch with those objectIDs use the managedObjectIDForURIRepresentation: message to get a NSManagedObjectID from the current MOC's PSC.

Option 1 is simpler, but might have some disadvantages with concurrency timing and may be too restrictive if, say, you wanted to have a separate PSC for your operations which was read only, for instance.

In my (relatively limited) experience calling managedObjectIDForURIRepresentation: it appears to be very performant, so converting URIRepresentation NSURLs to NSManagedObjectIDs doesn't seem to add that much overhead.

Thanks

Thanks for following along... I hope this helps explain the issue and workarounds clearly. I certainly feel like I earned a merit badge in Core Data object IDs and persistent store coordinators by digging through all this.

Cheers,

Levi

这篇关于通过objectID获取NSManagedObjects数组将返回空数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆