How are escaping closures implemented in Swift 3 (

2019-01-26 00:40发布

问题:

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."

回答1:

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.)