Swift中的self类型及其在两阶段初始化中的使用

时间:2017-10-17 18:37:59

标签: swift initialization self

请考虑以下代码,该代码会向视图添加手势识别器。

class ViewController: UIViewController {
    @IBOutlet weak var imageView: UIImageView!

    let gesture = UITapGestureRecognizer(target: self, action: #selector(handleGesture(gesture:)))
    let test1 = self

    @objc func handleGesture(gesture: UITapGestureRecognizer) {
        // some code
        print("hello")
    }
    override func viewDidLoad() {
        let test2 = self
        super.viewDidLoad()
        imageView.addGestureRecognizer(gesture)
    }
}

根据this question,上面的代码不起作用,因为我没有完全初始化时尝试使用self(在手势识别器的初始化程序中),这是因为Swift的两个 - 阶段初始化。

我对轻松修复这项工作并不感兴趣,但这引发了一些问题:

1)如果self未准备好使用,为什么编译器允许我们在这里使用self?如果我过早地尝试使用self,我不应该收到编译器错误吗?

2)我们无法在XCode中使用alt +单击直接检查self的类型。但是,我们可以检查我的即席变量test1test2的类型。虽然test2的类型为ViewController,但正如预期的那样,test1的类型为(ViewController) -> () -> ViewController(即,一个带有ViewController的闭包并返回一个闭包什么都不需要,并返回ViewController)。那是什么以及为什么self在同一个类中有两种不同的类型?

2 个答案:

答案 0 :(得分:4)

1)

如果我过早地尝试使用self,我是否会遇到编译错误?

我同意。您可以发送bug report to swift.org

如果self尚未准备好使用,为什么编译器允许我们在此使用self

不幸的是,selfthe method self() of NSObject的后代还有另一个NSObject

2)

这是什么?为什么自我在同一个班级中有两种不同的类型?

当前的Swift解释class上下文中的初始值表达式,而不是实例上下文。

您知道方法名称可以在Swift中用作闭包:

class ViewController: UIViewController {
    //..

    func aMethod() {
        //...
    }

    func anInstanceMethod() {
        let meth = aMethod // () -> ()
    }
}

Swift还可以引用class上下文中的实例方法,该方法生成所谓的未应用方法引用(请参阅SE-0042),该引用当前返回一个curried函数:

class ViewController: UIViewController {
    //...

    func aMethod() {
        //...
    }

    class func aClassMethod() {
        let meth = aMethod // (ViewController) -> () -> ()
    }
}

方法self()

通常我们不需要self()方法,我认为应该改变这种行为。

答案 1 :(得分:2)

这是一种适用于Objective-C对象的有趣行为。让我们来看看这三个例子:

class Object: NSObject {
  let test = self // compiles
}

class NonNSObject {
//  let test = self // errors
  lazy var lazyTest = self // compiles

}

struct NonClass {
//  let test = self // errors
  lazy var lazyTest = self // errors
}

NonNSObject展示了你逃脱的东西:

在完全初始化之前,对象无法引用自身,并且let绑定必须在完全初始化之前全部初始化,因此失败。

然而,NSObject碰巧有一个Objective-C方法- (instancetype)self;返回self。我们可以在NonNSObject上对此进行建模:

func returnSelf() -> NonNSObject {
    return self
}

这是我们开始看到2)的答案。

如果我们在 Class 上引用此方法returnSelf,我们会获得签名(NonNSObject) -> () -> NonNSObject。您可以使用任何实例方法执行此操作:

let test = NonNSObject.returnSelf 

签名在这种情况下有意义:

  1. 参数是我们实际想要在
  2. 上调用方法的对象
  3. 然后我们"申请"函数(在这种情况下没有参数)
  4. 我们终于得到了返回值
  5. let curriedFunction = NonNSObject.returnSelf // (Self) -> () -> Self let readyToCall = curriedFunction(NonNSObject()) // () -> Self let finallyApplied = readyToCall() // Self

    将所有部分放在一起,我们可以看到,在ViewController的情况下(从UIViewController继承链的哪个方向继承自NSObject),有一个实例方法self,编译器假设你的意思,所以它使用它而不是实例本身(因为这将是一个错误)。因此,它的签名是在类本身上使用实例方法的自然结果 - 它需要一个实例,这是第一个参数。

    总结:

    1)Swift编译器在NSObject上找不到函数self而不是假设你犯了错误,并返回curried表单。 2)这是函数的curry形式,特别是返回其自身类型的实例方法。 2.5)它仍然以粉红色突出显示,因为Swift-ObjC互操作是温和的hacky,self既是一种方法又是self

    作为奖励,结构根本不能引用自己,甚至懒惰。