Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iPhone fetch data dictionary from keychain

So I'm trying to convert an old project to Automatic Reference Counting. I'm trying to use the conversion tool that xCode has but it says to fix a couple things before it can convert. I have no idea how to fix this error. It's in the implementation of the keychain file. This method is the one that returns the error, specifically the line with the SecItemCopyMatching. The error I am getting says: " Cast of an indirect pointer to an Objective-C pointer to 'CFTypeRef*' (aka 'const void**') is disallowed with ARC. I've been looking all over google, apple docs, and a bunch of other crap and can't find a better way to fetch an existing data dictionary in the keychain. Any help appreciated. Thanks!

-(NSMutableDictionary*)fetchDictionary {

NSMutableDictionary *genericPasswordQuery = [self buildSearchQuery];

NSMutableDictionary *outDictionary = nil;
OSStatus status = SecItemCopyMatching((__bridge_retained  CFDictionaryRef)genericPasswordQuery, (CFTypeRef*)&outDictionary);

if (DEBUG) printf("FETCH: %s\n", [[self fetchStatus:status] UTF8String]);

if (status == errSecItemNotFound) return NULL;
return outDictionary;

}

like image 207
Luke Brigan Avatar asked Oct 19 '11 20:10

Luke Brigan


2 Answers

You don't need to disable ARC for this; you just need to declare the query's result as a CFDictionaryRef, then cast it to an NSDictionary after the call.

/*1*/ CFDictionaryRef cfquery = (__bridge_retained CFDictionaryRef)genericPasswordQuery;
/*2*/ CFDictionaryRef cfresult = NULL;
/*3*/ OSStatus status = SecItemCopyMatching(cfquery, (CFTypeRef *)&cfresult);
/*4*/ CFRelease(cfquery);
/*5*/ NSDictionary *result = (__bridge_transfer NSDictionary *)cfresult;

Couple of remarks:

  • In line 1, we convert the query from Cocoa land to Core Foundation country. We use __bridge_retained to make sure ARC does not release and deallocate the object while we are working with it. This kind of bridging cast retains the object, so to prevent leaks, it must be followed with a corresponding CFRelease somewhere. SecItemCopyMatching definitely will not release the query for us, so if we use a retained bridge, then we need to release the resulting Core Foundation object ourself. (Which we do in line 4.)
  • Lines 2, 3, and 4 are pure C code using Core Foundation types, so ARC will have nothing to do or complain about them.
  • In line 5, we tell ARC that SecItemCopyMatching has created its result with a retain count of 1, which we are responsible for releasing. (We know this because it has "Copy" in its name.) __bridge_transfer lets ARC know about this responsibility, so it will be able to do it for us automatically.
  • Don't cast the immutable Core Foundation dictionary returned by SecItemCopyMatching to NSMutableDictionary; that's just wrong. Also, it is against general Cocoa style conventions that buildSearchQuery returns an NSMutableDictionary. Simple NSDictionarys would work fine in both cases.

The rule of thumb here is that __bridge_retained needs to be followed by a CFRelease, while the result from a "Copy" or "Create" function must be cast into Cocoa-land using __bridge_transfer.

like image 121
Karoy Lorentey Avatar answered Oct 30 '22 19:10

Karoy Lorentey


Method 3: Let ARC do the heavy lifting (or a combination of method 1 and method 2):

NSMutableDictionary* query = [NSMutableDictionary dictionaryWithDictionary:
@{
    (__bridge id) kSecClass : (__bridge id) kSecClassGenericPassword,
    (__bridge id) kSecAttrService : nssService,
#if ! TARGET_IPHONE_SIMULATOR
    (__bridge id) kSecAttrAccessGroup : @"PRODUCT.com.COMPANY.GenericKeychainSuite",
#endif

    (__bridge id) kSecMatchLimit : (__bridge id) kSecMatchLimitOne,
    (__bridge id) kSecReturnAttributes : (__bridge id) kCFBooleanTrue,
}];

if ( [nssAccount length] != 0 )
    [query setObject:nssAccount forKey:(__bridge id) kSecAttrAccount];

CFDictionaryRef cfresult;
auto err =  ::SecItemCopyMatching((__bridge CFDictionaryRef)query,
                                  (CFTypeRef*)&cfresult);
if ( err == errSecItemNotFound )
    return std::wstring();
else if ( err != noErr)
    throw std::exception();

NSDictionary* result = (__bridge_transfer NSDictionary*) cfresult;

SecItemCopyMatching doesn't need to own the incoming dictionary, so __bridge is adequate and then ARC continues to manage the lifetime of query.

And by transferring ownership of the result to arc, it will manage the lifetime of result as well and free us of the need to remember to CFRelease it on all code paths.

like image 40
Joseph Galbraith Avatar answered Oct 30 '22 19:10

Joseph Galbraith