1

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

__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)

但是他们两个仍然对我泄漏。

任何其他想法

4

1 回答 1

10
  • 您从 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 cfresult = 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);
        }
    }
 }
于 2013-06-03T16:35:54.800 回答