Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

[NSFunctionExpression _propertyType]: unrecognized selector sent to instance

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

like image 221
sumofighter666 Avatar asked Aug 23 '14 21:08

sumofighter666


1 Answers

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

like image 143
sumofighter666 Avatar answered Nov 19 '22 14:11

sumofighter666