为什么我不能在Swift中重新分配实例方法?

时间:2015-09-09 06:50:13

标签: ios swift compiler-construction functional-programming

所以这个问题有一个明显的答案:“因为编译器不会让你”,但我希望有人可以向我解释为什么Swift的这部分工作原理。

我遇到了这个问题,因为我正在构建一个需要满足协议的视图控制器(UIImagePickerControllerDelegate)。该协议要求在用户选择图像后调用回调函数。我希望能够在运行时更改回调行为。

来自Python背景,我认为这应该很简单:只需在我的类上定义回调方法以满足协议,然后通过重新分配它来重新定义它。这在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"

但它在Swift中不起作用(尽管我可以通过声明一个变量来保存闭包来做几乎相同的事情):

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

所以问题是......为什么Swift以这种方式工作?这显然不是因为在运行时改变函数路由是不可能的(否则闭包赋值不起作用)。我最好的猜测是它类似于变量的let / var区别,并且使用“func”隐含地告诉编译器函数应该是不可变的。我承认默认情况下使实例方法不可变是更好的。但是当我们需要遵守UIKit的严格协议时,这很烦人。至少如果我可以使用变量来满足协议中的函数要求,那将是很好的。

(对于好奇:我通过声明一个实例变量来保存一个可以重新分配的闭包来解决这个问题。然后我声明了所需的协议函数并使它除了调用闭包之外什么都不做。哪个可能(?)导致保留周期,但有效。)

2 个答案:

答案 0 :(得分:2)

允许编译器发出的代码根本不同。

进行简单测试,使用-Onone进行编译以获得可读性,并使用Hopper进行反汇编,您可以看到正在进行的操作(手动添加评论):

a confusing mess

对于"实例方法" / function,可以在vtable中查找后调用它们 - 在此示例中,*(*rax + 0x48)*(*rax + 0x70)是指向函数,他们将rax(对象本身)作为参数传递(这变为self)。

但是在闭包变量的情况下,*(*rax + 0x50)是指向 bar 的getter的指针。首先调用getter,然后返回闭包,然后调用它 - (rax)(rdx)

所以这些只是不同的东西。如果你有一个存储闭包的可修改属性,那么你肯定需要在调用闭包之前调用getter(因为可以通过在其他地方设置来改变值)。但是简单的函数调度并不需要额外的间接级别。

答案 1 :(得分:1)

我不确定函数和闭包在swift中如何在幕后工作,但我认为swift中的函数基本上就像c中的函数,它在运行时定义,就是这样。它编译并存在于内存中的某个地址,任何引用该函数的东西都必须查看该内存地址,并且在运行时无法更改。

我会看到一个闭包,就像c中的function pointer +正常函数组合一样。所以它可能是他们在swift中实现函数的方式的限制。在python中可能在幕后一切都像函数指针+正常函数一样实现。

至于为什么swift没有像python一样实现它,我认为只有在Apple工作的人可以告诉你,但也许有一些开销使用像闭包而不是简单的函数,所以他们只让你使用需要时关闭,其余应该是功能。

也有函数不可变可能是协议在幕后工作的原因,也许允许你在运行时更改函数会破坏协议系统。

我不确定这里是否有人真的适合回答这个问题(除了可能潜伏在这里的苹果员工),但这是我最好的猜测