【问题标题】:Referencing self in super.init在 super.init 中引用 self
【发布时间】:2018-08-06 08:46:11
【问题描述】:

我有以下代码(编辑:更新了代码,以便每个人都可以编译并查看):

import UIKit

struct Action
{
    let text: String
    let handler: (() -> Void)?
}

class AlertView : UIView
{
    init(actions: [Action]) {
        super.init(frame: .zero)

        for action in actions {
//            let actionButton = ActionButton(type: .custom)
//            actionButton.title = action.title
//            actionButton.handler = action.handler
//            addSubview(actionButton)
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class TextAlertView : AlertView
{
    init() {
        super.init(actions: [
            Action(text: "No", handler: nil),
            Action(text: "Yes", handler: { [weak self] in
                //use self in here..
            })
        ])
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class MyViewController : UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let alert = TextAlertView()
        view.addSubview(alert)
        self.view = view
    }
}

每次我实例化 TextAlertView 时,它都会在 super.init 上崩溃,并且访问权限不正确。但是,如果我改变:

Action(title: "Yes", { [weak self] in
    //use self in here..
})

到:

Action(title: "Yes", {
    //Blank.. doesn't reference `self` in any way (weak, unowned, etc)
})

有效!

有没有办法在超级初始化期间引用self 在动作块内是否弱(在上面我在super.init 的参数中执行它?

代码编译..它只是在运行时随机崩溃。

【问题讨论】:

  • 会不会是因为 Action 是一个结构体?
  • @MikeTaverne;我只是尝试将其设为class 而不是struct.. 同样的问题。我更新了代码,以便我们可以通过 Playground 或常规应用程序编译它并查看。
  • 这是一个非常可怕的错误,在调用self 之前,您不应该能够捕获super.init - 虽然它已在最新的 Swift 4.1 快照中修复,但您将获得预期的“' self' 在 'super.init' 调用之前使用”错误。

标签: swift closures swift4 initializer


【解决方案1】:

简答

您不能在super.init 返回之前捕获并使用self 作为值。在您的情况下,您正试图将self 作为参数“传递”到super.init

根据第二部分为什么起作用,仅仅是因为没有在其中使用self,它不会捕获self,因此它不会使用self作为值。

如果你不想在闭包中使用self,那么你不需要担心strong/weak在那里的引用,因为那里根本没有self的引用(因为它没有被捕获)。没有保留周期的危险。


关于“使用self 作为值”的简短旁注 - 您可以在赋值左侧使用self 来在初始化self 的属性时引用它们:

let myProperty: String

init(with myProperty: String) {
    // this usage of self is allowed
    self.myProperty = myProperty
    super.init(nibName: nil, bundle: nil)
}

参考文献和资料的更长答案

根据documentation

安全检查 4

在第一阶段初始化完成之前,初始化器不能调用任何实例方法、读取任何实例属性的值或将自身作为值引用

初始化的第一阶段通过调用super.init结束,当

来自相同的文档:

第一阶段

在类上调用指定的或便利的初始化器。

为该类的新实例分配内存。内存尚未初始化。

该类的指定初始化程序确认该类引入的所有存储属性都有一个值。这些存储属性的内存现在已初始化。

指定的初始化器交给一个超类初始化器来为它自己的存储属性执行相同的任务。

这会沿着类继承链向上继续,直到到达链的顶部。

一旦到达链的顶端,并且链中的最后一个类已确保其所有存储的属性都有值,则认为实例的内存已完全初始化,阶段 1 完成。

所以只有在调用super.init 之后,你才可以使用self 作为值:

第二阶段

从链的顶部向下工作,链中的每个指定初始化程序都可以选择进一步自定义实例。初始化器现在可以访问 self 并且可以修改它的属性、调用它的实例方法等等。

最后,链中的任何便利初始化程序都可以选择自定义实例并使用self

现在,当您尝试使用 self 作为闭包捕获列表中的值时,我一点也不感到惊讶,它会崩溃。我更惊讶的是编译器确实允许你这样做——现在我猜这是一个没有实现错误处理的边缘情况。

第二种情况:

Action(title: "Yes", {
    //Blank.. doesn't reference `self` in any way (weak, unowned, etc)
})

你并没有真正捕获self,这就是它被允许并且有效的原因。但是您在那里无权访问self。尝试在那里添加一些使用self 的代码,编译器会报错:

所以最后,如果你想在闭包中使用self,你必须找到一种方法,如何首先调用super.init,然后才能将self捕获闭包添加到属性中。

【讨论】:

  • 我知道为什么第二种情况有效(根据代码中的注释)。我没有意识到的是,第一个案例块内的捕获是不允许的,或者为什么编译器甚至允许它编译。
  • @Brandon 好吧.. 那真的是我的假设是错误的 :D.. 无论如何,现在你知道了
  • @Milan 我指的是您的评论:“尝试在那里添加一些使用 self 的代码,编译器会抱怨。”我假设当编译器抱怨时,这意味着代码无法编译。
  • @MikeTaverne 哦.. 请注意,OP 没有在那里使用self,这就是它为他编译的原因.. 我添加了一个屏幕截图,显示 xcode 确实在抱怨
  • @MikeTaverne 抱歉造成误会
猜你喜欢
  • 2015-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多