Objective C memory management with blocks, ARC and

2019-03-28 18:45发布

问题:

I have been using blocks for some time now, but I feel that there are things I miss about memory management in both ARC and non-ARC environments. I feel that deeper understanding will make me void many memory leaks.

AFNetworking is my main use of Blocks in a particular application. Most of the time, inside a completion handler of an operation, I do something like "[self.myArray addObject]".

In both ARC and non-ARC enabled environments, "self" will be retained according to this article from Apple.

That means that whenever a completion block of an AFNetworking network operation is called, self is retained inside that block, and released when that block goes out of scope. I believe that this applies to both ARC and non-ARC. I have ran both the Leaks tool and the Static Analyzer so that I may find any memory leaks. None showed any.

However, it wasn't until recently that I stumbled upon a warning which I couldn't figure out. I am using ARC in this particular example.

I have two instance variables that indicate the completion and failure of a network operation

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

Somewhere in the code, I do this:

[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 complains about the line that calls the failureBlock, with the message "Capturing "self" strongly in this block is likely to result in a retain cycle. I believe Xcode is right: the failure block retains self, and self holds its own copy of the block, so none of the two will be deallocated.

However, I have the following questions/observations.

1) If i change _failureBlock(error) to "self.failureBlock(error)" (without quotes) the compiler stops complaining. Why is that? Is this a memory leak the compiler misses?

2) In general, what is the best practice to work with blocks in both ARC and non-ARC enabled environments when using blocks that are instance variables? Seems that in the case of completion and failure blocks in AFNetworking, those two blocks are not instance variables so they probably don't fall into the category of retain cycles that I described above. But when using progress blocks into AFNetworking, what can be done to avoid retain cycles like the one above?

I would love to hear other people's thoughts on ARC and non-ARC with blocks and issues/solutions with memory management. I find these situations error-prone and I feel some discussion on this is necessary to clear things up.

I don't know if it matters, but I use Xcode 4.4 with the latest LLVM.

回答1:

1) If i change _failureBlock(error) to "self.failureBlock(error)" (without quotes) the compiler stops complaining. Why is that? Is this a memory leak the compiler misses?

The retain cycle exists in both cases. If you're targeting iOS 5+, you can pass in a weak reference to self:

__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");
}];

Now self won't be retained, and if it's deallocated before the callback is invoked, the callback is a no-op. However, it's possible that it could be undergoing deallocation while the callback is invoked on a background thread, so this pattern can create occasional crashes.

2) In general, what is the best practice to work with blocks in both ARC and non-ARC enabled environments when using blocks that are instance variables? Seems that in the case of completion and failure blocks in AFNetworking, those two blocks are not instance variables so they probably don't fall into the category of retain cycles that I described above. But when using progress blocks into AFNetworking, what can be done to avoid retain cycles like the one above?

Most of the time, I think it's better not to store blocks in instance variables. If you instead return the block from a method in your class, you'll still have a retain cycle, but it only exists from the time the method is invoked to the time the block is released. So it will prevent your instance from being deallocated during the block execution, but the retain cycle ends when the block is released:

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

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


回答2:

That means that whenever a completion block of an AFNetworking network operation is called, self is retained inside that block, and released when that block goes out of scope.

No, self is retained by the block when the block is created. And it is released when the block is deallocated.

I believe Xcode is right: the failure block retains self, and self holds its own copy of the block, so none of the two will be deallocated.

The block in question that retains self is the completion block passed to setCompletionBlockWithSuccess. self does not hold a reference to this block. Rather, self.operation (presumably some kind of NSOperation) retains the blocks while it is executing. So there is temporarily a cycle. However, when the operation is done executing, the cycle will be broken.

1) If i change _failureBlock(error) to "self.failureBlock(error)" (without quotes) the compiler stops complaining. Why is that? Is this a memory leak the compiler misses?

There should be no difference. self is captured in both cases. The compiler is not guaranteed to catch all cases of retain cycles.