Hi, some DB code in my apps uses core data API which changed it's behavior in iOS 10 runtime (even for apps already in the app store compiled with iOS 9 SDK).
I reduced the problem and seems setting NSFetchRequest resultType property to NSManagedObjectIDResultType and then to NSDictionaryResultType (before executing the fetch, even in the next line), makes the fetch to not work correctly (returns empty dictionaries).
Already filed a bug report (27331859), but mention this here in case someone finds the same bug.
A simpler sample code to reproduce the bug is:
Being this the Core Data used in the test:
- (void)testExample {
NSString *sqlitePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"test.sqlite"];
NSString *sqlStore_WAL_UrlPath = [sqlitePath stringByAppendingString:@"-wal"];
NSString *sqlStore_SHM_UrlPath = [sqlitePath stringByAppendingString:@"-shm"];
NSURL *sqlStoreUrl = [NSURL fileURLWithPath:sqlitePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:sqlitePath]) {
[[NSFileManager defaultManager] removeItemAtPath:sqlitePath error:NULL];
[[NSFileManager defaultManager] removeItemAtPath:sqlStore_WAL_UrlPath error:NULL];
[[NSFileManager defaultManager] removeItemAtPath:sqlStore_SHM_UrlPath error:NULL];
}
XCTAssertTrue(![[NSFileManager defaultManager] fileExistsAtPath:sqlitePath]);
XCTAssertTrue(![[NSFileManager defaultManager] fileExistsAtPath:sqlStore_WAL_UrlPath]);
XCTAssertTrue(![[NSFileManager defaultManager] fileExistsAtPath:sqlStore_SHM_UrlPath]);
NSManagedObjectModel *mom = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
NSError *error;
[psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:sqlStoreUrl
options:nil
error:&error];
XCTAssertNil(error);
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[moc setUndoManager:nil];
[moc setPersistentStoreCoordinator:psc];
NSUInteger count = 10;
NSMutableArray<EntityA *> *allA = [NSMutableArray arrayWithCapacity:count];
NSMutableArray<EntityC *> *allC = [NSMutableArray arrayWithCapacity:count];
for (NSUInteger i = 0; i < count; i++) {
EntityA *a = [NSEntityDescription insertNewObjectForEntityForName:@"EntityA" inManagedObjectContext:moc];
EntityC *c = [NSEntityDescription insertNewObjectForEntityForName:@"EntityC" inManagedObjectContext:moc];
a.entityAValue = [NSString stringWithFormat:@"EntityA %lu", (unsigned long)i];
c.entityCValue = [NSString stringWithFormat:@"EntityC %lu", (unsigned long)i];
[allA addObject:a];
[allC addObject:c];
}
for (NSUInteger i = 0; i < count; i++) {
EntityB *b = [NSEntityDescription insertNewObjectForEntityForName:@"EntityB" inManagedObjectContext:moc];
b.entityA = allA[i];
b.entityC = allC[i];
}
[moc save:&error];
XCTAssertNil(error);
NSFetchRequest *fetchA = [[NSFetchRequest alloc] init];
[fetchA setEntity:[NSEntityDescription entityForName:@"EntityA" inManagedObjectContext:moc]];
[fetchA setFetchLimit:1];
[fetchA setResultType:NSManagedObjectIDResultType];
/
NSPredicate *predicate;
predicate = [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:@"entityAValue"]
rightExpression:[NSExpression expressionForConstantValue:@"EntityA 3"]
modifier:NSDirectPredicateModifier
type:NSEqualToPredicateOperatorType
options:0];
[fetchA setPredicate:predicate];
NSArray *fetchResult = [moc executeFetchRequest:fetchA error:&error];
XCTAssertNotNil(fetchResult);
XCTAssertTrue([fetchResult count]);
XCTAssertNil(error);
NSManagedObjectID *restultAObjID = [fetchResult lastObject];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
[fetch setEntity:[NSEntityDescription entityForName:@"EntityB" inManagedObjectContext:moc]];
[fetch setResultType:NSManagedObjectIDResultType]; /
[fetch setResultType:NSDictionaryResultType];
XCTAssertTrue(fetch.resultType == NSDictionaryResultType);
/
predicate = [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:@"entityA"]
rightExpression:[NSExpression expressionForConstantValue:restultAObjID]
modifier:NSDirectPredicateModifier
type:NSEqualToPredicateOperatorType
options:0];
[fetch setPredicate:predicate];
NSString *objectIDExpName = @"objectID";
NSExpression *objectIDExp = [NSExpression expressionForKeyPath:@"entityC"];
NSExpressionDescription *objectIDExpDesc = [[NSExpressionDescription alloc] init];
[objectIDExpDesc setName:objectIDExpName];
[objectIDExpDesc setExpression:objectIDExp];
[objectIDExpDesc setExpressionResultType:NSObjectIDAttributeType];
[fetch setPropertiesToFetch:[NSArray arrayWithObject:objectIDExpDesc]];
fetchResult = [moc executeFetchRequest:fetch error:&error];
XCTAssertNotNil(fetchResult);
XCTAssertTrue([fetchResult count]);
XCTAssertNil(error);
for (NSDictionary *fetchResultItem in fetchResult) {
NSManagedObjectID *itemObjectID = [fetchResultItem objectForKey:objectIDExpName];
XCTAssertNotNil(itemObjectID);
}
}Also note that commenting line 70 ([fetch setResultType:NSManagedObjectIDResultType]; // PROBLEM LINE) makes the test pass in iOS 10, however all my apps in the AppStore uses this code which used to work in iOS 9, so I expect this HAS TO BE FIXED before iOS 10 ships.