在ARC下,SecItemCopyMatching仍然在osx上泄漏

时间:2013-05-27 20:33:00

标签: iphone macos cocoa automatic-ref-counting

我在SecItemCopyMatching上发现了内存泄漏。在对SF i进行调查后,找到了解决方案:

__block NSString *certificateName = nil;
SecKeychainRef keychain;
SecKeychainCopyDefault(&keychain);
NSMutableDictionary *attributeQuery = [NSMutableDictionary dictionary];
[attributeQuery setObject: (id) kSecClassIdentity forKey:(__bridge_transfer id) kSecClass];
[attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnRef];
[attributeQuery setObject: (id) kSecMatchLimitAll forKey:(__bridge_transfer id) kSecMatchLimit];
CFTypeRef attrResult = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) attributeQuery,(CFTypeRef *) &attrResult);<------- here is a leak according Instruments


if (status != errSecItemNotFound) {
    NSArray *attributeResult = (__bridge_transfer NSArray *)attrResult;
    [attributeResult enumerateObjectsUsingBlock:^(id identityFromArray, NSUInteger idx, BOOL *stop) {
        OSStatus status;
        SecCertificateRef cert = NULL;
        status = SecIdentityCopyCertificate((__bridge SecIdentityRef)identityFromArray, &cert);
        if (!status)
        {

或其他解决方案:

NSMutableDictionary *attributeQuery = [NSMutableDictionary dictionary];
[attributeQuery setObject: (id) kSecClassIdentity forKey:(__bridge_transfer id) kSecClass];
[attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnRef];
[attributeQuery setObject: (id) kSecMatchLimitAll forKey:(__bridge_transfer id) kSecMatchLimit];
CFTypeRef attrResult = NULL;
CFDictionaryRef cfquery = (__bridge_retained CFDictionaryRef)attributeQuery;
OSStatus status = SecItemCopyMatching(cfquery,(CFTypeRef *) &attrResult);<------- here is a leak according Instruments


if (status != errSecItemNotFound) {
    NSArray *attributeResult = (__bridge_transfer NSArray *)attrResult;
    [attributeResult enumerateObjectsUsingBlock:^(id identityFromArray, NSUInteger idx, BOOL *stop) {
        OSStatus status;
        SecCertificateRef cert = NULL;
        status = SecIdentityCopyCertificate((__bridge SecIdentityRef)identityFromArray, &cert);
        if (!status)
        {
            char *nameBuf = NULL;
            CFStringRef nameRef = NULL;
            OSStatus statusNew = SecCertificateInferLabel(cert, &nameRef);
            .....
CFRelease(cfquery)

但他们两人仍为我泄漏。

任何其他想法

1 个答案:

答案 0 :(得分:8)

  • 您从CF样式的函数中收到钥匙串对象,其名称中包含Copy。因此它具有+1引用计数,您有责任在使用它时明确释放它。它从未被您的示例代码发布,因此它正在泄漏。钥匙串对象从未在您发布的代码中使用,因此可以完全取消。
  • 在第一个解决方案中,您使用简单的attributeQuery演员传递__bridge(一个局部变量),这不是一个好主意; ARC可能会过早地从你身下释放它。您应该使用__bridge_retained(或CFBridgingRetain)将其转换为具有+1保留计数的CF国家/地(稍后明确释放)。
  • 在第二个解决方案中,您使用__bridge_retained,但是您没有发布结果,这解释了泄漏。
  • 如果通话成功,SecItemCopyMatching的返回值为零。您不应仅与errSecItemNotFound进行比较;查询失败可能有许多其他原因。

更新的代码:

NSMutableDictionary *attributeQuery = [NSMutableDictionary dictionary];
[attributeQuery setObject:(id)kSecClassIdentity forKey:(__bridge id)kSecClass];
[attributeQuery setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnRef];
[attributeQuery setObject:(id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
CFTypeRef attrResult = NULL;
CFDictionaryRef cfquery = (CFDictionaryRef)CFBridgingRetain(attributeQuery);
OSStatus status = SecItemCopyMatching(cfquery, &cfresult);
CFRelease(cfquery);

if (status == errSecSuccess) {
    NSArray *attributeResult = CFBridgingRelease(cfresult);
    [attributeResult enumerateObjectsUsingBlock:^(id value, NSUInteger idx, BOOL *stop) {
        OSStatus status;
        SecCertificateRef cert = NULL;
        SecIdentityRef identity = CFBridgingRetain(value);
        status = SecIdentityCopyCertificate(identity, &cert);
        CFRelease(identity);
        if (!status)
        {
           ...
           CFRelease(cert);
        }];
 }

我发现Core Foundation / Cocoa桥接版本有点难以阅读,所以我个人觉得跳过Cocoa级别并直接在CF级别创建查询字典更加清晰:

CFMutableDictionaryRef cfquery = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(cfquery, kSecClass, kSecClassIdentity);
CFDictionarySetValue(cfquery, kSecReturnRef, kCFBoolenTrue);
CFDictionarySetValue(cfquery, kSecMatchLimit, kSecMatchLimitAll);

CFArrayRef cfidentities = NULL;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)cfquery, (CFTypeRef *)&cfidentities);
CFRelease(cfquery);

if (status == errSecSuccess) {
    NSArray *identities = CFBridgingRelease(cfidentities);
    for (id value in identities) {
        SecCertificateRef cfcertificate;
        SecIdentityRef cfidentity = (SecIdentityRef)CFBridgingRetain(value);
        status = SecIdentityCopyCertificate(cfidentity, &cfcertificate);
        if (status == errSecSuccess) {
            // ...
            CFRelease(cfcertificate);
        }
    }
 }