通过objectID获取NSManagedObjects数组将返回空数组 [英] Fetching array of NSManagedObjects by objectID returns empty array
问题描述
我正在尝试执行提取操作,以使用从不同上下文中收集的对象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错误:&错误];
解决方法
我已验证了以下两种解决方案:
-
使用相同的持久性存储协调器(PSC )在上下文之间传递
NSManagedObjectID
时(并确保它驻留在内存中)。如果这样做,NSManagedObjectID
将永远不会丢失其实体
,并且一切都会按预期进行。 -
而不是在上下文之间传递
NSManagedObjectID
,而是使用它们的URIRepresentation
。即,而不是传递数组NSManagedObjectID
对象,而是传递数组NSURL
s表示每个
。一旦准备好使用这些对象ID执行访存,请使用NSManagedObjectID
的URIRepresentationmanagedObjectIDForURIRepresentation:
消息从当前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 backnil
(rather than a bogus object) but it too returns a fault.objectRegisteredForID:
will returnnil
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
NSManagedObjectID
s whose persistent store coordinator is no longer in memory lose their NSEntityDescription
(entity
) and do not equate (isEqual:
returns NO
) to NSManagedObjectID
s 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 NSManagedObjectID
s 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:
Use the same Persistent Store Coordinator (PSC) when communicating
NSManagedObjectID
s between contexts (and make sure it stays resident in memory). If this is done, theNSManagedObjectID
s never lose theirentity
and everything works as expected.Instead of passing
NSManagedObjectID
s around from context to context, use theirURIRepresentation
. i.e. instead of passing an array ofNSManagedObjectID
objects, pass an array ofNSURL
s representing theURIRepresentation
of eachNSManagedObjectID
. Once ready to perform a fetch with those objectIDs use themanagedObjectIDForURIRepresentation:
message to get aNSManagedObjectID
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
NSURL
s to NSManagedObjectID
s 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屋!