【问题标题】:Returning Objective-C object with Core Foundation attribute返回具有 Core Foundation 属性的 Objective-C 对象
【发布时间】:2013-06-23 13:31:57
【问题描述】:

我有一个方法,它返回一个名为“Credential”的自定义对象的 NSArray,它有两个属性:一个 NSString 和一个 CFDataRef。

您已经注意到该对象有两种类型的属性,一个 NS Objective-C 属性和一个 Core-Foundation 属性。

对象在每次交互循环时都会被初始化,因为它会像这样填充 NSArray:

cred = [[Credential alloc] init];
cred.cn = [NSString stringWithString:(__bridge NSString *)(summary)];
cred.serialNumber = CFDataCreateCopy(kCFAllocatorDefault, serialNumber);

当我运行分析器时,我收到以下消息:

Object leaked: allocated object is not referenced later in this 
execution path and has a retain count of +1

我假设发生此警告是因为我正在初始化一个 CF 对象并从该方法返回而不释放它,但是负责释放该对象的 new 应该 是调用该方法的代码。

我应该在哪里调用 Credential 类的 CFDataRef 属性的 CFRelease?

编辑:

我正在使用 ARC,所以我将释放 NSString (cred.cn) 的责任留给他。但是,关于 CFDataRef (cred.serialNumber),我没有发布它,因为稍后我会从另一个类和部分代码中需要它。然后,我不确定如何管理它。当对象“凭据”被释放时,ARC 是否会释放它?如果没有,我可以覆盖 Credential 的 dealloc 方法来做 serialNumber 的 CFRelease 吗?

下面是初始化并返回 Credential 对象的 NSArray 的完整方法:

- (NSArray *) retrieveIdentities
{
    CFArrayRef identities = NULL;
    NSMutableArray *returnIdentities = nil;
    OSStatus sanityCheck = NULL;

    const void *keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef, kSecReturnData, kSecReturnAttributes};
    const void *values[] = {kSecClassIdentity, kSecMatchLimitAll, kCFBooleanTrue, kCFBooleanTrue, kCFBooleanTrue};
    CFDictionaryRef query = CFDictionaryCreate(NULL, keys, values, sizeof(values)/sizeof(const void *), NULL, NULL);

    sanityCheck = SecItemCopyMatching(query, (CFTypeRef *)&identities);

    if (query)
        CFRelease(query);
    if (sanityCheck == errSecItemNotFound)
        return nil;
    if (sanityCheck != noErr)
        @throw [[KeychainException alloc] initWithName:@"KeychainException" reason:@"ERROR_LISTING_IDENTITIES" userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithLong: sanityCheck], @"osstatus", nil]];

    CFDictionaryRef result = NULL;
    CFStringRef summary = NULL;
    SecCertificateRef certificate = NULL;
    CFDataRef serialNumber = NULL;
    Credential *cred = nil;
    CFIndex resultCount = CFArrayGetCount(identities);

    returnIdentities = [[NSMutableArray alloc] init];

    for (CFIndex i = 0; i<resultCount; i++)
    {
        result = CFArrayGetValueAtIndex(identities,i);
        SecIdentityRef identity = (SecIdentityRef) CFDictionaryGetValue(result, kSecValueRef);

        if ((sanityCheck = SecIdentityCopyCertificate(identity, &certificate)) != noErr)
             @throw [[KeychainException alloc] initWithName:@"KeychainException" reason:@"ERROR_EXTRACTING_CERTIFICATE" userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithLong: sanityCheck], @"osstatus", nil]];

        CFTypeRef keyClass = CFDictionaryGetValue(result, kSecAttrKeyClass);
        if ([[(__bridge id)keyClass description] isEqual:(__bridge id)(kSecAttrKeyClassPrivate)])
        {
            summary = SecCertificateCopySubjectSummary(certificate);
            serialNumber = CFDataCreateCopy(NULL, CFDictionaryGetValue(result, kSecAttrSerialNumber));
            cred = [[Credential alloc] init];
            cred.cn = [NSString stringWithString:(__bridge NSString *)(summary)];
            cred.serialNumber = CFDataCreateCopy(kCFAllocatorDefault, serialNumber);
            [returnIdentities addObject:cred];

            if (summary)
                CFRelease(summary);

            if (serialNumber)
                CFRelease(serialNumber);
        }
    }

    if (certificate)
        CFRelease(certificate);


    return returnIdentities;
}

【问题讨论】:

  • 为什么要转换(桥接)刺痛而不是数据?
  • 因为我将数据存储为 CFDataRef 对象,因为我以后使用它时需要它。
  • 因此,您需要展示如何处理字符串和数据的所有权以及如何释放 CF 对象。
  • 我已经用更多(希望有用的)信息编辑了这个问题。

标签: ios objective-c memory-management automatic-ref-counting core-foundation


【解决方案1】:

如果使用 CF 函数创建的字符串意味着您拥有它,您应该将所有权转移给 ARC。目前,您只是桥接对 ARC 的引用,不会获得所有权。

对于数据,您需要覆盖 dealloc 并在数据上调用 CFRelease


分析仪并不完美。有些事情它觉得很困难,所以它会谨慎行事并告诉你可能有问题。

可以肯定的是,特别是在泄漏和内存管理方面,您应该使用Instruments 来检查发生了什么。

【讨论】:

  • 按照您的建议进行操作后,我不断收到潜在的泄漏警告。虽然这可能是因为分析器无法知道从方法返回后对象会发生什么(无论它是否被 CFRelease 数据覆盖)。正如苹果文档所说:“静态分析会产生误报。误报不太可能是分析器识别为问题的源代码中的问题。源代码注释有助于减少误报”所以我猜这可能是误报。
  • 签入仪器是最好的选择。
  • 按照您的建议,我使用了仪器(在部分了解了它的工作原理之后)来检测代码中的泄漏。如果我不使用 CFRelease 覆盖 dealloc 方法,它会在分配 cn.serialNumber 的行中检测到泄漏,而如果我覆盖它,泄漏就会消失。所以简而言之,分析仪一定给出了误报。如果您使用结论编辑您的答案,我会将其标记为正确。非常感谢:)
  • 谢谢,但在阅读了其他答案后,我认为 Gabriele 的答案更准确,因为它解决了分析器的困惑。但是,我会给您的答案 +1,因为它也很有用。
【解决方案2】:

随行

cred.serialNumber = CFDataCreateCopy(kCFAllocatorDefault, serialNumber);

CFDataCreateCopy 创建一个保留计数为 +1 的对象,之后您不会在任何地方释放该对象。这就是分析仪警告您的原因。

用以下代码替换该行应该可以解决它

CFDataRef sn = CFDataCreateCopy(kCFAllocatorDefault, serialNumber);
cred.serialNumber = sn;
CFRelease(sn);

关键是你搞乱了所有权,因此混淆了分析器。

您应该在当前范围释放使用 New、Copy 或 Retain 函数创建的任何对象,因此您确实应该释放使用 CFDataCreateCopy 创建的对象。

如果Credential 实例需要保留您分配给serialNumber 的值,他应该对此负责,而不是调用者。 为此,只需将CredentialserialNumber 属性声明为strongcopy,然后让ARC 发挥它的魔力。

编辑
由于从 cmets 看来 serialNumber 属性具有 CFDataRef 类型,您仍然可以通过将其转换为可保留对象指针来保留 Credential 对象,如下所示

@property (nonatomic, strong) __attribute__((NSObject)) CFDataRef serialNumber;

NSObject 属性将使编译器将其视为一个对象,内存管理明智。这在clang docs 中有很好的解释。

【讨论】:

  • 但是,请注意,Credential 的 serialNumber 属性不能声明为强/复制,因为它不是对象类型
  • 您可以将其声明为@property(retain) __attribute__((NSObject)) CFDataRef serialNumber;,您可能需要查看stackoverflow.com/questions/9274397/…
  • 这很有启发性!根本不知道。再次感谢!
【解决方案3】:

正如其他人所指出的,您创建了两个序列号副本,但只发布了一个。

您可以将 CF 对象转换为其对应的 NS 对象,并使用宏 CFBridgingRelease 来释放 CF 对象。释放发生在语句的末尾,在 ARC 保留对象之后(如果需要)。

NSString *summary = CFBridgingRelease(SecCertificateCopySubjectSummary(certificate));
NSData *serialNumber = CFBridgingRelease(CFDictionaryGetValue(result, kSecAttrSerialNumber));
cred = [[Credential alloc] init];
cred.cn = [summary copy];
cred.serialNumber = [serialNumber copy];

基本规则是您可以使用CFBridgingRelease 而不是CFRelease。它平衡 CF Create 或 Copy 函数的保留,并返回一个由 ARC 处理的 Objective-C 对象引用。

【讨论】:

  • 抱歉,我想将序列号保留为核心基础对象,以避免在访问时发生转换。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-25
  • 2021-07-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多