Search code examples
iosswiftfunctionmethodsstrong-reference-cycle

Swift, why don't class methods need closure lists


If functions are essentially closures. Why don't methods of a class need closure lists when referencing self or another instance property within the closure.

Is there a [unowned self] behind the scenes? For example:

class MyClass{
    func myFunc(){
        self.otherFunc()
    }

    func otherFunc(){
        print()
    }
}

Wouldn't there be a reference cycle within myFunc? Ie, the closure is pointing to self, and the instance is pointing to the function. Neither could be deallocated.


Solution

  • "If functions are essentially closures." This isn't true. Functions (and methods) are not the same thing as closures. Functions have all their free variables unbound. Closures have bound some or all of their free variables (closed over them, which is where the name "closure" comes from).

    A "free variable" is any variable defined outside the scope of the function (including its formal parameters). The top-level function func f(x: Int) has one free variable; when you call it, you must pass a parameter. A closure like { f(1) } has no free variables. When you call it, you do not pass any parameters.

    A method, like a function, does not capture anything. It is passed all of its free variables when it is executed. For example, when you make the call object.doThis(), this is the same as calling Type.doThis(object)().

    class X {
        func doThis() {}
    }
    
    let x = X()
    x.doThis()
    
    X.doThis(x)() // Same thing
    

    X.doThis(x) is a function that returns a function. There's no magic here. All the free variables are provided during the call. Nothing is captured. (The "free variable" in the case you describe is self, but that doesn't change anything. self is not special, except that it gets a little syntactic sugar around it.)

    This is different than a closure:

    let c = { x.doThis() }
    c()
    

    When I call c(), how does it know the value of x? I may have returned c and x may be out of scope now. The system has to keep track of x (including making a strong reference so it doesn't deallocate), and it does that by capturing it, or "closing over x" which raises the possibility of retain loops. So in c, x is bound. It is not free. You can't pass it when you call c().

    self is not special here. It's just another variable. [weak self] in closures isn't special either. You can write [weak x] just as well. The [...] syntax is just the capture list.