使用块,ARC和非ARC进行目标C内存管理

时间:2012-07-29 16:07:38

标签: objective-c memory-management automatic-ref-counting objective-c-blocks afnetworking

我一直在使用块一段时间了,但我觉得在ARC和非ARC环境中都有一些我想念的内存管理。我觉得更深刻的理解会让我失去许多内存泄漏。

AFNetworking是我在特定应用程序中对Blocks的主要用法。大多数情况下,在操作的完成处理程序中,我执行类似“[self.myArray addObject]”的操作。

在启用ARC和非ARC的环境中,将根据this article from Apple保留“self”。

这意味着每当调用AFNetworking网络操作的完成块时,self将保留在该块内,并在该块超出范围时释放。我相信这适用于ARC和非ARC。我已经运行了泄漏工具和静态分析器,以便我可以发现任何内存泄漏。没有显示任何。

然而,直到最近,我偶然发现了一个我无法弄清楚的警告。我在这个特定的例子中使用ARC。

我有两个实例变量,表明网络操作的完成和失败

@property (nonatomic, readwrite, copy) SFCompletionBlock completionBlock;
@property (nonatomic, readwrite, copy) SFFailureBlock failureBlock;
@synthesize failureBlock = _failureBlock;
@synthesize operation = _operation;

代码中的某处,我这样做:

[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id
                                                    responseObject) {
NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}];
            _failureBlock(error);
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"nothing");
        }];

Xcode抱怨调用failureBlock的行,在此块中强烈地“捕获”自我“消息”很可能导致保留周期。我相信Xcode是正确的:失败块保留自己,并且自我保持自己的块副本,所以这两个都不会被释放。

但是,我有以下问题/意见。

1)如果我将_failureBlock(错误)更改为“self.failureBlock(error)”(不带引号),编译器会停止抱怨。这是为什么?这是编译器错过的内存泄漏吗?

2)通常,在使用块作为实例变量时,在ARC和非ARC启用的环境中使用块的最佳做法是什么?似乎在AFNetworking中的完成和故障块的情况下,这两个块是实例变量,因此它们可能不属于我上面描述的保留周期类别。但是当将进度块用于AFNetworking时,可以采取哪些措施来避免像上面那样的保留周期?

我很想听听其他人对ARC和非ARC的看法,包括带有内存管理的块和问题/解决方案。我发现这些情况容易出错,我觉得有必要对此进行一些讨论以便清理。

我不知道它是否重要,但我使用Xcode 4.4和最新的LLVM。

2 个答案:

答案 0 :(得分:6)

  

1)如果我将_failureBlock(错误)更改为“self.failureBlock(error)”   (没有引号)编译器停止抱怨。这是为什么?这是   编译器错过了内存泄漏?

两种情况都存在保留周期。如果您的目标是iOS 5+,则可以向自我传递弱引用:

__weak MyClass *weakSelf;
[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}];
    if (weakSelf.failureBlock) weakSelf.failureBlock(error);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"nothing");
}];

现在不会保留self,如果在调用回调之前将其释放,则回调是无操作。但是,当在后台线程上调用回调时,它可能正在进行释放,因此这种模式可能会偶尔发生崩溃。

  

2)一般来说,在两者中使用块的最佳做法是什么   使用块时的ARC和非ARC环境   实例变量?似乎在完成和失败的情况下   AFNetworking中的块,这两个块不是实例变量   它们可能不属于我的保留周期范畴   如上所述。但是当使用进度块进入AFNetworking时,   可以采取哪些措施来避免像上面那样的保留周期?

大多数时候,我认为it's better not to store blocks in instance variables。如果你从类中的方法返回块,你仍然会有一个保留周期,但它只存在从调用方法到释放块的时间。因此,它会阻止您的实例在块执行期间被释放,但保留周期在块释放时结束:

-(SFCompletionBlock)completionBlock {
    return ^(AFHTTPRequestOperation *operation , id responseObject ) {
        [self doSomethingWithOperation:operation];
    };
}

[self.operation setCompletionBlockWithSuccess:[self completionBlock]
                                      failure:[self failureBlock]
];

答案 1 :(得分:2)

  

这意味着每当AFNetworking网络的完成块时   调用操作,self保留在该块内,并释放   当那个区块超出范围时。

否,创建块时块保留self。并且在释放块时释放它。

  

我相信Xcode是对的:失败块保留了自我和自我   拥有自己的块副本,所以两者都不会   解除分配。

保留self的问题块是传递给setCompletionBlockWithSuccess的完成块。 self没有对此块的引用。相反,self.operation(可能是某种NSOperation)在执行时保留块。所以暂时有一个循环。但是,当操作完成后,循环将被中断。

  

1)如果我将_failureBlock(错误)更改为“self.failureBlock(error)”   (没有引号)编译器停止抱怨。这是为什么?这是   编译器错过了内存泄漏?

应该没有区别。在这两种情况下都会捕获self。不保证编译器能够捕获所有保留周期的情况。