My project implements core data synchronization with iCloud. When I try to remove duplicates I get following exception:
-[NSFunctionExpression _propertyType]: unrecognized selector sent to instance
I have read that the main cause of this sort of issue is that using the same managed object context on different threads. In my case, however, all processes are done on main thread. I have no clue how to fix this issue.
In initialization method I add following observer:
[[NSNotificationCenter defaultCenter] addObserverForName:NSPersistentStoreDidImportUbiquitousContentChangesNotification
object:self.managedObjectContext.persistentStoreCoordinator
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
NSLog(@"NSPersistentStoreDidImportUbiquitousContentChangesNotification received");
[self.managedObjectContext performBlock:^{
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:note];
[self filterDuplicates]; <-- Here I call method to call filtering duplicates
}];
}];
The duplicate method:
- (void) filterDuplicates {
NSString *uniquePropertyKey = @"name";
NSString *entityName = @"SFGallery";
NSExpression *countExpression = [NSExpression expressionWithFormat:@"count:(%@)", uniquePropertyKey];
NSExpressionDescription *countExpressionDescription = [[NSExpressionDescription alloc] init];
[countExpressionDescription setName:@"count"];
[countExpressionDescription setExpression:countExpression];
[countExpressionDescription setExpressionResultType:NSInteger64AttributeType];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:self.managedObjectContext];
NSAttributeDescription *uniqueAttribute = [[entity attributesByName] objectForKey:uniquePropertyKey];
// Fetch the number of times each unique value appears in the store.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
fetchRequest.propertiesToFetch = @[uniqueAttribute, countExpression];
fetchRequest.propertiesToGroupBy = @[uniqueAttribute];
fetchRequest.resultType = NSDictionaryResultType;
fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"order" ascending:YES]];
NSArray *fetchedDictionaries = [self fetchAlbumsWithFetchRequest:fetchRequest];
// Filter out unique values that have no duplicates.
NSMutableArray *valuesWithDupes = [NSMutableArray array];
for (NSDictionary *dict in fetchedDictionaries) {
NSNumber *count = dict[@"count"];
if ([count integerValue] > 1) {
[valuesWithDupes addObject:dict[uniquePropertyKey]];
}
}
NSFetchRequest *dupeFetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
dupeFetchRequest.includesPendingChanges = NO;
dupeFetchRequest.fetchBatchSize = 20;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name IN (%@)", valuesWithDupes];
dupeFetchRequest.predicate = predicate;
NSArray *dupes = [self fetchAlbumsWithFetchRequest:dupeFetchRequest];
SFGallery *prevObject;
for (SFGallery *duplicate in dupes) {
if (prevObject) {
if ([duplicate.name isEqualToString:prevObject.name]) {
if ([duplicate.timestamp compare:prevObject.timestamp] == NSOrderedAscending) {
[self.managedObjectContext deleteObject:duplicate];
} else {
[self.managedObjectContext deleteObject:prevObject];
prevObject = duplicate;
}
} else {
prevObject = duplicate;
}
} else {
prevObject = duplicate;
}
}
}
And the method when the exception takes place:
- (NSArray *)fetchAlbumsWithFetchRequest:(NSFetchRequest *)fetchRequest {
Exception Here ---> NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
NSError *error = nil;
if (![fetchedResultsController performFetch:&error]) {
NSLog(@"Error when fetching Albums: %@", [error userInfo]);
return nil;
}
return fetchedResultsController.fetchedObjects;
}
Any help?
Edit 1:
I am quite sure it is related to NSFetchRequest. The instance of NSFetchRequest has properties attributes set:
fetchRequest.propertiesToFetch = @[uniqueAttribute, countExpression];
And this is the countExpression
that brings problem.
On the exception breakpoint I have logged the fetch request object and I got following:
<NSFetchRequest: 0x10c58b4a0> (entity: SFGallery; predicate: ((null)); sortDescriptors: ((
"(timestamp, descending, compare:)"
)); type: NSDictionaryResultType; includesPendingChanges: NO; propertiesToFetch: ((
"(<NSAttributeDescription: 0x10c56e5c0>), name title, isOptional 1, isTransient 0, entity SFGallery, renamingIdentifier title, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}, attributeType 700 , attributeValueClassName NSString, defaultValue (null)",
"count:(\"title\")"
)); propertiesToGroupBy: ((
"(<NSAttributeDescription: 0x10c56e5c0>), name title, isOptional 1, isTransient 0, entity SFGallery, renamingIdentifier title, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}, attributeType 700 , attributeValueClassName NSString, defaultValue (null)"
)); )
This is thrown call stack:
0 CoreFoundation 0x0000000103692495 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x00000001033ca99e objc_exception_throw + 43
2 CoreFoundation 0x000000010372365d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x0000000103683d8d ___forwarding___ + 973
4 CoreFoundation 0x0000000103683938 _CF_forwarding_prep_0 + 120
5 CoreData 0x0000000100d7812d -[NSFetchRequest _newValidatedProperties:groupBy:error:] + 541
6 CoreData 0x0000000100d0da63 -[NSFetchRequest(_NSInternalMethods) _resolveEntityWithContext:] + 179
7 CoreData 0x0000000100e03c2b -[NSFetchedResultsController initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:] + 555
8 myAppli 0x000000010003d2d3 -[SFCoreDataManager fetchAlbumsWithFetchRequest:] + 163
9 myAppli 0x000000010003c901 -[SFCoreDataManager filterDuplicates] + 1073
10 myAppli 0x00000001000271cb -[SFDebugViewController removeDuplicates:] + 91
11 UIKit 0x0000000101d0af06 -[UIApplication sendAction:to:from:forEvent:] + 80
12 UIKit 0x0000000101d0aeb4 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 17
13 UIKit 0x0000000101de7880 -[UIControl _sendActionsForEvents:withEvent:] + 203
14 UIKit 0x0000000101de6dc0 -[UIControl touchesEnded:withEvent:] + 530
15 UIKit 0x0000000101d41d05 -[UIWindow _sendTouchesForEvent:] + 701
16 UIKit 0x0000000101d426e4 -[UIWindow sendEvent:] + 925
17 UIKit 0x0000000101d1a29a -[UIApplication sendEvent:] + 211
18 UIKit 0x0000000101d07aed _UIApplicationHandleEventQueue + 9579
19 CoreFoundation 0x0000000103621d21 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
20 CoreFoundation 0x00000001036215f2 __CFRunLoopDoSources0 + 242
21 CoreFoundation 0x000000010363d46f __CFRunLoopRun + 767
22 CoreFoundation 0x000000010363cd83 CFRunLoopRunSpecific + 467
23 GraphicsServices 0x000000010482cf04 GSEventRunModal + 161
24 UIKit 0x0000000101d09e33 UIApplicationMain + 1010
25 myAppli 0x0000000100010473 main + 115
26 libdyld.dylib 0x0000000103ea55fd start + 1
27 ??? 0x0000000000000001 0x0 + 1
Edit 2:
When I get the exeption:
[NSFunctionExpression _propertyType]: unrecognized selector sent to instance 0x10c433d90
After printing instance with po [0x10c433d90 class]
I get NSFunctionExpression
The code included above partially comes from apple sample code. Here is the source: https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/UsingSQLiteStoragewithiCloud/UsingSQLiteStoragewithiCloud.html
I can't believe apple engineers don't test the code before providing as a sample.
First of all the expression should be defined this way:
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:uniquePropertyKey];
NSExpression *countExpression = [NSExpression expressionForFunction: @"count:" arguments:@[keyPathExpression]];
In this case actually I am very surprised why this implementation doesn't work:
NSExpression *countExpression = [NSExpression expressionWithFormat:@"count:(%@)", uniquePropertyKey];
May be someone who knows why can share some knowledge
And apple's shiny flower:
fetchRequest.propertiesToFetch = @[uniqueAttribute, countExpression];
should be
fetchRequest.propertiesToFetch = @[uniqueAttribute, countExpressionDescription];
I hope this answer will save many hours
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With