5

I am trying to understand how escaping closures work in Swift 3? Coming from the Objective-C world, for scenarios where the closure could escape the return of its enclosing function you had to do something along these lines:

@property (nonatomic, copy/strong) void (^callback)(NSData *rawData);

-(BOOL)someFunctionThatConsumesABlock:(void (^)(NSData *rawData))block 
{
  if (callback) {
      self.callback = block;
      return YES;
  }
  return NO;
}

-(void)someFunctionThatExecutesAtSomeLaterPoint
{
  NSData *data = [self bytesFromSomeProcess];
  self.callback(data);
}

Basically in the above code in objective-C, the object consuming the block performs a heap copy and strongly retains the block. This strong retaining makes sense because the block needs to stick around beyond the scope of the function it is passed as an argument too (since its an escaping closure/block).

In Swift 3, it seems the above is no longer necessary. All one needs to do is just annotate the closure as "escaping." So what is Swift 3 doing under the hood? Is it doing the same thing as objective-C? In essence do escaping closures in Swift 3 get implicitly strongly retained by the object they are being passed too? I dont see how else the language could achieve this and have the closures "stick around."

AyBayBay
  • 1,726
  • 4
  • 18
  • 37
  • Compare https://stackoverflow.com/questions/43171341/swift-function-object-wrapper-in-apple-swift – Hamish Jun 28 '17 at 15:47

1 Answers1

3

It is true that closures are implicitly retained (strongly) when you save them as properties or otherwise. From The Swift Programming Language, Automatic Reference Counting:

… closures, like classes, are reference types. When you assign a closure to a property, you are assigning a reference to that closure.

(That's why capture lists exist: to help avoid accidental retain cycles.)

However, I think you may be misunderstanding the purpose of @escaping (or the absence of @noescape in older version of Swift). This does not automatically save the closure for you. Rather, it just indicates to the caller that you might save the closure (that the closure might escape the execution of the function). This allows the compiler to perform extra optimizations, such as skipping the retain. It also allows callers to omit self. inside the closure:

class Foo {
    var x = 3

    func test() {
        [1, 2, 3].map { $0 + x }
        // `self.x` is not necessary, because the map function's
        // closure is non-escaping
    }
}

(If you're interested in learning what's really going on under the hood with @escaping, I don't know of a definitive source for this kind of information, but you might find some useful things in this talk about SIL, the SIL.rst docs in the open-source project, and perhaps Understanding Swift Performance from WWDC16.)

Graham
  • 7,431
  • 18
  • 59
  • 84
jtbandes
  • 115,675
  • 35
  • 233
  • 266
  • 1
    This does not really answer my question. I know that closures are "object-like" and I did not ask how to solve block retain cycles. Asking about a particular case with escaping closures. In objC if a closure could be escaped, we would have to explicitly retain it first (via a strong/copy property.). In Swift annotating it as "escaping" is all one needs to do, so my question is what exactly is Swift doing in this case? How come in objective-C we had to explicitly store it as a property (retain it) but in Swift we do not have to? Under the hood is swift basically doing it for us? – AyBayBay Dec 21 '16 at 07:47
  • 1
    I'm not sure what you mean by "explicitly retain it first and store it as a property". In modern Obj-C, as in Swift, blocks stored to regular properties are implicitly retained. @escaping/@noescape is just further information for the compiler about whether the block *might* be retained by the callee and stored elsewhere. – jtbandes Dec 21 '16 at 07:49
  • 1
    I am saying if a closure could be escaped from a function in objC you had to save/retain the closure first (look at my example). In Swift it seems all you need to do is annotate it as "escaping" so is Swift doing the saving/retaining for us? If you read my entire post it should make sense. – AyBayBay Dec 21 '16 at 07:54
  • 2
    I think you misunderstand what @escaping does. It does **not** automatically save the given closure for you. It just tells your callers that you *might* save it. The advantage of *non*-escaping closures is that the caller is allowed to use `self` implicitly, because the compiler knows it will not lead to a retain cycle. – jtbandes Dec 21 '16 at 07:57
  • Hmm I see.... if what you are saying is indeed true, then if you edit your answer to provide the above explanation I can then upvote your answer back up again and accept it. After downvoting Stack overflow wont let me upvote again unless u edit it. – AyBayBay Dec 21 '16 at 08:13