【问题标题】:Why would I ever use unowned self?为什么我会使用无主的自我?
【发布时间】:2018-03-06 13:18:51
【问题描述】:

以下模式在 iOS 应用中经常发生:

class MyViewController: UIViewController {
    let myModel = MyModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        myModel.foo() { [***] in
            // use self here
        }
    }
}

class MyModel {
    public func foo(complete: () -> Void) {
        // do something
        complete()
    }
}

共识是使用[unowned self][weak self]代替[***],当你可以保证self在完成时不会为nil时为unowned,当你不确定引用时为weak仍然有效。
我不明白为什么我会冒险使用无主,也许我确信现在引用永远不会为零,但将来可能会改变。我也可能忽略了一个极端情况,错误发生了。我可以很容易地总是使用弱,并在闭包的顶部放置一个守卫,以便能够在没有 ! 的情况下使用 self或?.
无主有什么用?比weak+guard快吗?是语法糖吗?这似乎违背了 Swift 保护开发人员免受可能导致崩溃的常见错误的理念。

【问题讨论】:

  • 我不太了解 swift,但是在其他语言中,像 [unowned] 这样的概念对于与缺乏引用计数的其他语言(如 C)进行交互非常重要。虽然我不知道为什么要在你呈现的环境中使用它。

标签: ios swift swift3


【解决方案1】:

根据Swit Programming Guide: Automatic Reference Counting

一个无主的引用应该总是有一个值。因此,ARC 永远不会将无主引用的值设置为 nil,这意味着无主引用是使用非可选类型定义的。

简而言之,weak 是可选的,而 unowned 不是。

【讨论】:

  • 但是为什么呢?似乎使用无主只会带来风险而没有收益。我宁愿安全行事,总是使用weak,顶部有一个守卫 self != nil。
  • @kevin 如果您的程序中有一个前提条件,即对象 A 不得比对象 B 存活,那么对象 A 保留 unowned 是完全合理的,而不是weak,对对象 B 的引用。如果前提条件被破坏,您可能不希望您的应用程序在无效状态下静默继续;您希望它捕获,因此您可以修复错误。这不是一个风险案例,而是一个确保您拥有一个格式良好的程序的案例。
  • @Hamish +1 这很有道理。
【解决方案2】:

unownedweak 相比具有边际性能优势,因为运行时不必跟踪引用以在对象消失时将其转换为nil

关于保留周期(好吧,强引用周期),weakunowned 都不会创建强引用(在 ARC 之前,不会增加保留计数),因此不存在引用周期的危险,事实上,这就是为什么你需要在闭包中为self 指定weakunowned

此外,使用unowned,您可以将引用用作非可选,因此您不必在闭包中放入任何代码来解包它。

我总是使用weak,除非有非常好的性能理由不这样做。

NB 在您的代码中,我认为两者都没有必要,因为闭包没有转义,即在函数调用foo 中对它的引用在foo 的范围结束后不会持续存在。

【讨论】:

  • 两种情况下的保留周期如何? , 有什么区别吗?
  • 就我而言,对于弱自我而言,Retain 循环没有单一的可能性,但我不知道 unowned
  • @JonSnow unowned 也不是强引用。
  • "因为运行时不需要跟踪引用以在对象消失时将其变为 nil" - 实际上 weak 引用不是这样工作的在 Swift 运行时中(尽管它确实是带有 Obj-C 运行时的)。基本上,当对对象形成弱引用时,对象会获得一个“边表”来跟踪其引用计数(并保持对象的 ptr)。然后弱引用指向this side table。即使在对象被释放后,弱引用仍然指向那个边表......
  • ... 在加载弱引用时,检查强保留计数;如果为零,则返回 nil。然后当弱保留计数达到零时,将释放边表。这篇博文是一个很好的阅读主题:mikeash.com/pyblog/…。尽管如此,unowned 引用仍然可能具有边际性能优势,因为 weak 引用将需要额外的取消引用(从边表到对象)。
【解决方案3】:

我相信使用unowned 比使用弱自我增加了风险。确实会发生错误,其中一个示例是在视图控制器中启动 API 调用,让它等待响应到达,然后突然弹出该视图控制器可能会导致它被释放(如果没有对它的强引用)。当响应到达时,我们的视图控制器对象将消失,我们的应用程序将在我们面前崩溃。由我们决定在哪个地方使用哪个。

正如 Jeremy 所指出的,unowned 没有责任跟踪引用计数,因此它相对于强和弱的性能优势微乎其微。

【讨论】:

    【解决方案4】:

    在导航控制器中使用无主引用的简单示例:

    class RegisterViewController: UIViewController {
        lazy var navigatinRightBarItemsCount: Int = { [unowned self] in
            guard let navigationController = self.navigationController else { return 0 }
            guard let topItem = navigationController.navigationBar.topItem else { return 0 }
            guard let rightBarButtonItems = topItem.rightBarButtonItems else { return 0 }
            return rightBarButtonItems.count
            }()
    
        override func viewWillAppear(_ animated: Bool) {
            print("Navigation bar right side items count: ", navigationRightBarItemsCount)
        }
    }
    

    这意味着必须初始化 RegisterViewController,然后我们才能安全地获取 self. navigationController 并从这里获取项目计数。如果你尝试将其设为 [weak self],XCode 会报错,因为 self(viewcontroller) 可以为 nil,所以我们必须使用 unowned。

    UIBarButtonItem 的另一个例子

    lazy final private var filterNavigationBarItem: UIBarButtonItem = { [unowned self] in
        return UIBarButtonItem(image: #imageLiteral(resourceName: "filter_icon"),
                               style: .plain,
                               target: self,
                               action: #selector(segueToFilterJobs))
        }()
    

    我们在这里看到了什么?参见 target 代表“接收动作消息的对象。”表示接收动作消息的对象不能为 nil,必须初始化。

    drewagthis article. 中的一个非常好的解释

    你真正想要使用 [unowned self] 或 [weak self] 是您创建强参考循环的时候。一个强壮的 引用周期是指对象结束的所有权循环 彼此拥有(可能通过第三方),因此他们 永远不会被释放,因为它们都确保每个 其他人坚持。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-15
      • 2011-04-03
      • 1970-01-01
      • 2021-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多