So this question has an obvious answer: "because the compiler won't let you" but I'm hoping someone can explain to me why this part of Swift works the way it does.
I ran into this question because I was building a view controller that needed to satisfy a protocol (UIImagePickerControllerDelegate). The protocol requires a callback function to call after a user selected an image. I wanted to be able to change the callback behavior at runtime.
Coming from a Python background, I figured that should be easy: just define the callback method on my class to satisfy the protocol and then redefine it later by just reassigning to it. That works perfectly fine in Python:
class Foo(object):
def bar(self):
print "bar"
foo = Foo()
foo.bar() # output "bar"
def baz():
print "baz"
foo.bar = baz
foo.bar() # output "baz"
But it doesn't work in Swift (even though I can do very nearly the same thing by declaring a variable to hold a closure):
import UIKit
class Foo {
func bar() -> String {
return "bar"
}
var baz: ()-> String = {
return "baz"
}
}
let foo = Foo()
foo.bar() // output: bar
foo.baz() // output: baz
let gee = {
return "gee"
}
foo.baz = gee
foo.baz() // output: gee
foo.bar = gee // error: cannot assign to bar in foo
So the question...why does Swift work this way? It's clearly not because it's impossible to alter function routing at runtime (otherwise the closure assignment wouldn't work). My best guess is that it's analogous to the let/var distinction for variables and that using "func" is implicitly telling the compiler that a function should be immutable. And I grant that it may be better to make instance methods immutable by default. But it is annoying when we need to comply with rigid protocols from UIKit. At least it would be nice if I could use a variable to satisfy a function requirement in a protocol.
(For the curious: I worked around this issue by declaring an instance variable to hold a closure that can be reassigned. I then declared the required protocol function and made it do nothing but call the closure. Which might(?) cause a retain cycle, but works.)
The code the compiler is allowed to emit is fundamentally different.
Making a simple test, compiling with -Onone
for readability, and disassembling with Hopper, you can see what's going on (comments added manually):
In the case of an "instance method"/function, they can be called after being looked up in the vtable — in this example, *(*rax + 0x48)
and *(*rax + 0x70)
are pointers to the functions, and they're passed rax
(the object itself) as a parameter (this becomes self
).
However in the case of a closure variable, *(*rax + 0x50)
is a pointer to the getter for bar
. The getter is called first, and returns the closure which is then called — (rax)(rdx)
.
So these are simply different things. If you have a modifiable property that stores a closure, then certainly you need to call the getter before you can call the closure (since the value could have changed by being set elsewhere). But simple function dispatch doesn't require the extra level of indirection.