CoreData: Setting NSFetchRequest resultType to NSManagedObjectIDResultType and then NSDictionaryResultType does not work anymore

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.

Answered by finthamoussu in 159071022

This has been fixed in Beta 4 🙂

Sorry by the screenshot of the Core Data model for the test was not uploaded, it's like:


EntityA

- Attributes

entityAValue (String)

- Relationships

entitiesB (Destination EntityB, ToMany)


EntityB

- Relationships

entityA (Destination EntityA, ToMany)

entityC (Destination EntityC, ToOne)


EntityA

- Attributes

entityCValue (String)

- Relationships

entitiesB (Destination EntityB, ToMany)

Still happens in Beta 3

Accepted Answer

This has been fixed in Beta 4 🙂

CoreData: Setting NSFetchRequest resultType to NSManagedObjectIDResultType and then NSDictionaryResultType does not work anymore
 
 
Q