Swift, why don't class methods need closure lists

814 views Asked by At

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.

2

There are 2 answers

1
Rob Napier On BEST ANSWER

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

1
zneak On

Closures may only cause reference cycles when the closure is kept alive. Consider this:

let foo = MyClass()
let bar: () -> () = { in
    print(foo)
}

The bar closure holds a reference to foo, but that reference goes away once nothing references bar anymore. For instance:

func f(foo: MyClass) {
    let bar: () -> () = { () in
        print(foo)
    }
}

This does not create a reference cycle, because when f returns, the closure in bar is destroyed. Similarly, when you call myFunc and otherFunc, you do need a strong reference to self (the compiler ensures that you have it), but as you no longer need it at the end of the function, no cycle is created.

In general, a closure will not systematically create a reference cycle, even if it is @escaping. Consider the case of Dispatch.async:

class MyClass {
    func foo() {
        DispatchQueue.main.async {
            print(self)
        }
    }
}

This does not actually create a reference cycle, because even though the closure references self for a while, self does not reference the closure.

The dangerous case is this one:

class MyClass {
    var closure: () -> ()

    func f() {
        self.closure = {
            print(self)
        }
    }
}

This one actually creates a reference cycle: self.closure has a strong reference to self, and self has a strong reference to self.closure.