28

Working on an iOS project that targets 4.0 and 5.0, using ARC.

Running into an issue related to blocks, ARC and referencing an object from outside the block. Here's some code:

 __block AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
   [operation setCompletionBlock:^ {
       if ([operation isCancelled]) {
           return;
       }

... do stuff ...

operation = nil;
}];

In this case, the compiler gives a warning that using 'operation' in the block is going to lead to a retain cycle. Under ARC, __block now retains the variable.

If I add __unsafe_unretained, the compiler releases the object immediately, so obviously that won't work.

I'm targeting 4.0 so I can't use __weak.

I tried doing something like this:

AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
__block __unsafe_unretained AFHTTPRequestOperation *weakOperation = operation;

but while weakOperation isn't nil, none of it's properties are populated when inside the block.

What's the best way to handle this situation given the project constraints listed above?

Hunter
  • 4,343
  • 5
  • 42
  • 44

2 Answers2

23

Assuming progress guarantees, a retain cycle might be exactly what you want. You explicitly break the retain cycle at the end of the block, so it's not a permanent retain cycle: when the block is called, the cycle is broken.

If you have something else keeping the operation around, though, you can store a reference into either a __weak or __unsafe_unretained variable and then use that from within your block. There's no need to __block-qualify the variable unless you for some reason need to change the variable's binding during the block; since you don't have a retain cycle to break any more, you shouldn't need to assign anything to the weak variable.

Jeremy W. Sherman
  • 35,901
  • 5
  • 77
  • 111
  • 1
    I've got the 'no retain cycle' thing hammered into my mind, I didn't even think about it the way you described. Duh. Next question - any way to silence the compiler warning? It'll drive me mad. – Hunter Oct 13 '11 at 23:08
  • 1
    See ["Controlling Diagnostics via Pragmas"](http://clang.llvm.org/docs/UsersManual.html#diagnostics_pragmas) in the Clang user's manual. You'll just need to figure out which warning flag to ignore. – Jeremy W. Sherman Oct 14 '11 at 16:30
  • 4
    It's `#pragma clang diagnostic ignored "-Warc-retain-cycles"`, by the by. – Charlie Groves Nov 24 '11 at 01:14
  • 7
    Sorry, I know I'm late to the party, but it is important to pay close attention to @JeremyW.Sherman's opening statement ("Assuming progress guarantees"), especially with AFNetworking, because this is **not** the case. In your sample, you are returning before setting the operation to nil if the operation is canceled, and similarly in AFHTTPRequestOperation.m:setCompletionBlockWithSuccess:... if the operation is canceled neither the completion or error blocks are called, leaving your operation retained. – levigroker May 09 '12 at 19:30
  • Even assuming progress guarantees, a reference cycle is usually not "exactly what you want", though it might be acceptable. – tc. Jun 03 '13 at 16:07
1

This appears to be the problem described by Conrad Stoll in Blocks, Operations, and Retain Cycles, but his writeup misses a few important points:

  • __block looks like the Apple-recommended way of avoiding a strong reference to captured variables in MRC mode but is completely unnecessary in ARC mode. In this case, it is completely unnecessary in ARC mode; it is also unnecessary in MRC mode though the lighter-weight workaround is much more verbose: void * unretainedOperation = operation; ... ^{ AFHTTPRequestOperation * op = unretainedOperation; }
  • In ARC mode, you need both a strong reference (so you can add it to the queue) and a weak/unsafe_unretained reference

The simplest solution looks like this:

AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
AFHTTPRequestOperation * __unsafe_unretained unretainedOperation = operation;

[operation setCompletionBlock:^ {
  if ([unretainedOperation isCancelled]) {
    return;
  }
  ... do stuff ...
}];

Even if you break the reference cycle, there is no reason for the Block to retain the AFHTTPRequestOperation in the first place (assuming the operation keeps itself alive until the completion handler completes, which isn't always guaranteed but is generally true and assumed by ARC if it is referred to using self further up the call stack).

The best fix appears to be to update to the latest AFNetworking, which passes the operation into the block as an argument.

tc.
  • 33,468
  • 5
  • 78
  • 96
  • __block isn't completely unnecessary. By default, all variables, that were retained by block will be const inside of it, so you can't change their values . Here __block comes to the rescue. – Yurii Romanchenko Jan 31 '15 at 18:44