Crash using Aggregate Operation: “ALL” in a Core Data iOS Application

时光怂恿深爱的人放手 提交于 2019-11-28 11:15:41

The Core Data Programming Guide says

There are some interactions between fetching and the type of store. In the XML, binary, and in-memory stores, evaluation of the predicate and sort descriptors is performed in Objective-C with access to all Cocoa's functionality, including the comparison methods on NSString. The SQL store, on the other hand, compiles the predicate and sort descriptors to SQL and evaluates the result in the database itself.

It goes on to describe some other limitations on the use of NSPredicate with NSSQLiteStoreType, but your use of "ALL" here is an (undocumented) limitation that has to do with how the fetch request emits SQL.

Under the hood, CoreData generates three tables for your schema:

  • a table for Contact
  • a table for Group
  • a join table that relates them

And so when you call myGroup.contacts, something like this gets run:

select * from Group join JOIN_TABLE on Group.pk == JOIN_TABLE.group_pk join Contact on JOIN_TABLE.contact_pk == Contact.pk where Group.pk == 12

There's a lot going on behind one dot character!

Anyway, to actually fulfill your query, you'd need something like this. I tested this on an actual SQLite CD database, so the table names look strange, but it should still be comprehensible:

select ZGROUP.Z_PK as outer_pk from ZGROUP where "myUID" not in 
(select ZCONTACT.ZUID as contact_uid from ZGROUP join Z_1GROUPS on Z_1GROUPS.Z_2GROUPS == ZGROUP.Z_PK join ZCONTACT on Z_1GROUPS.Z_1CONTACTS == ZCONTACT.Z_PK where ZGROUP.Z_PK == outer_pk)

I'm no SQL expert, but my observations are first of all that this query is going to be slow, and second of all that it is kind of a long ways from the NSPredicate that we started with. So it would be only through a great deal of effort that CD could up with an SQL query for what you want to do, and the query that it would come up with would not be much better than a naive implementation in ObjC.

For whatever it's worth, an Apple developer says here that ALL is unsupported in SQLite and the documentation to the contrary is wrong. That documentation is still there in 2013 though, so nobody seems to have done anything about it.

Anyway, what you should actually do is something like this:

NSFetchRequest *fetchRequest = ...
NSArray *result = [moc executeFetchRequest:fetchRequest error:&err];
result = [result filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"ALL contacts.uid != %@", contactUId]];

This will evaluate the predicate in software.

The basic syntax is fine. This compiles and runs in my test:

NSString *contactUId=@"steve";
NSPredicate *p=[NSPredicate predicateWithFormat:@"ALL contacts.uid != %@", contactUId];
NSLog(@"p = %@",p);

//=> p = ALL contacts.uid != "steve"

Most likely the problem is with the contactUid variable. If it has no value or a value that does not cleanly convert to a string that NSPredicate understands, then it will cause the crash. E.g.

NSString *contactUId;
NSPredicate *p=[NSPredicate predicateWithFormat:@"ALL contacts.uid != %@", contactUId];
NSLog(@"p = %@",p);

... causes exactly the crash you describe.

I would log contactUid immediately before assigning it to the predicate to see what its actual, string convertible value is. The way it displays in NSLog is the way it will appear in the predicate because both use the same string formatting.

Aggregate expressions are not supported by Core Data.

For the record, the warning above is from the (2013) NSExpression Class Reference, in the paragraph relative to Aggregate Expressions. In fact some operators are supported (ANY, NONE) when they are translatable in SQL.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!