AlexR
AlexR

Reputation: 5634

Core Data: Using a transient property decrypt and cache another value

I have created a transient property in Core Data which holds the decrypted string formula of a non-transient encrypted property formulaEnc.

I have defined a local variable NSString *tmpFormula to cache the decrypted formula in order to avoid decrypting the value each time the managed object is accessed.

This is the custom accessor I have added to the managed object:

- (NSString*)formula
{
    [self willAccessValueForKey:@"formula"];
    NSString *tmpFormula = [self primitiveValueForKey:@"formula"];
    [self didAccessValueForKey:@"formula"];
    
    [self willAccessValueForKey:@"formulaEnc"];
    NSString *formulaEnc = [self primitiveValueForKey:@"formulaEnc"];
    [self didAccessValueForKey:@"formulaEnc"];
    
    if(!tmpFormula && formulaEnc) {
        NSLog(@"Decryption started.");
        NSData *encryptedData = [NSData dataFromBase64String:self.formulaEnc];
        NSError *decryptionError;
        NSString *password = @"password";
        
        NSData *decryptedData = [RNDecryptor 
              decryptData:encryptedData
              withPassword:password
              error:&decryptionError];

        if (decryptionError) NSLog(@"Decryption 
              failed with error: %@",decryptionError);
        
        NSString *decryptedString = [[NSString alloc] 
                   initWithData:decryptedData encoding:NSUTF8StringEncoding];
        tmpFormula = decryptedString;
        [self setPrimitiveValue:tmpFormula forKey:@"formula"];
    }
    return tmpFormula;
}

I store all managed objects in an NSArray to cache it in memory. However, even though the objects are cached, whenever I read-access the managed objects, the accessor formula (please refer to the code snippet above) is accessed and the decryption occurs again.

BTW: This is how I create a NSArray of the managed objects in my main code:

- (NSArray*)allFigures
{   if (!_allFigures) {
        NSLog(@"Recalculating allFigures.");
        NSFetchRequest *request = [NSFetchRequest 
                fetchRequestWithEntityName:@"Figure"];
        request.sortDescriptors = @[[NSSortDescriptor 
                sortDescriptorWithKey:@"order" ascending:YES ]];
        request.returnsObjectsAsFaults = NO;
        NSError *fetchError;
        NSArray *result = [self.managedDocument.managedObjectContext 
               executeFetchRequest:request error:&fetchError];
        _allFigures = result;
    }
    return _allFigures;
}

This is how the previous snippet is called:

NSArray *figures = [self figuresForCategory:category
inManagedObjectContext:self.managedDocument.managedObjectContext];

[self calculateValuesForFigures:figures withCompletion:
    ^(NSDictionary *values, NSArray *figures) {
    dispatch_async(dispatch_get_main_queue(), ^{

       self.figures = figures;
    });
}];

- (NSArray *)figuresForCategory:(NSString*)category 
      inManagedObjectContext:(NSManagedObjectContext*)context
{  
    NSPredicate *predicate = [NSPredicate predicateWithFormat:
            @"xyz CONTAINS[cd] %@ ",category];   
    return [self.allFigures filteredArrayUsingPredicate:predicate];
}
   
- (void)calculateValuesForFigures:(NSArray*)figures 
        withCompletion:(void(^)(NSDictionary* valueDict, 
        NSArray* figures))completionBlock;
{
   NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc]
        initWithConcurrencyType:NSPrivateQueueConcurrencyType];    
   backgroundContext.parentContext = 
        self.managedDocument.managedObjectContext;
   [backgroundContext performBlock:^{
          NSArray *figuresInBackgroundContext = 
          [self managedObjectsInArray:figures passedToContext:backgroundContext];
// …
     

 
    

When debugging the code, the local variable tmpFormula in the first code snippet is nil but should have same value as it should have been cached.

Any thoughts or ideas why my caching does not work?

Update:

As I found out - thanks to your help, my managed objects get faulted by a unknown method. The backtrace for -willTurnIntoFault (command bt in the debugger) yields:

thread #24: tid = 0x8f50b, 0x000b7eb4 Demo`-[Figure willTurnIntoFault](self=0x17f18610, _cmd=0x3432fe08) + 20 at Figure.m:104, queue = 'NSManagedObjectContext Queue, stop reason = breakpoint 6.1
    frame #0: 0x000b7eb4 Demo`-[Figure willTurnIntoFault](self=0x17f18610, _cmd=0x3432fe08) + 20 at Figure.m:104
    frame #1: 0x2d8fd6ea CoreData`-[NSFaultHandler turnObject:intoFaultWithContext:] + 66
    frame #2: 0x2d9648f8 CoreData`-[NSManagedObjectContext(_NSInternalAdditions) _disposeObjects:count:notifyParent:] + 320
    frame #3: 0x2d964c16 CoreData`-[NSManagedObjectContext(_NSInternalAdditions) _dispose:] + 682
    frame #4: 0x2d95f8d2 CoreData`-[NSManagedObjectContext _dealloc__] + 402
    frame #5: 0x2d95fb2e CoreData`internalBlockToDeallocNSManagedObjectContext + 70
    frame #6: 0x38818e7a libdispatch.dylib`_dispatch_queue_drain + 374
    frame #7: 0x38815f92 libdispatch.dylib`_dispatch_queue_invoke + 42
    frame #8: 0x38819744 libdispatch.dylib`_dispatch_root_queue_drain + 76
    frame #9: 0x388199c4 libdispatch.dylib`_dispatch_worker_thread2 + 56
    frame #10: 0x38943dfe libsystem_pthread.dylib`_pthread_wqthread + 298

Any idea on how to find the caller of ´-willTurnIntoFault´?

Upvotes: 0

Views: 502

Answers (1)

Martin R
Martin R

Reputation: 539915

Your code for the transient property looks quite OK. tmpFormula should be a local variable, and it can be simplified slightly:

- (NSString*)formula 
{
    NSString *tmpFormula;
    [self willAccessValueForKey:@"formula"];
    tmpFormula = [self primitiveValueForKey:@"formula"];
    [self didAccessValueForKey:@"formula"];

    if (tmpFormula == nil) {
        NSString *formulaEnc = self.formulaEnc;
        NSLog(@"Decryption started.");
        // ... decrypt ...
        tmpFormula = decryptedString;
        self.formula = tmpFormula;
    }
    return tmpFormula;
}

The issue is also unrelated to decryption.

The problem is that managed objects can only live in the managed object context where they were created.

It seems that you create the objects on a background MOC and put them into a global array for caching. But as soon as the background MOC is deallocated, all objects are turned into faults automatically, and you cannot access their properties anymore. (You will get nil or may be a runtime exception). So this kind of caching cannot work.

Upvotes: 1

Related Questions