在Swift中,我们是否总是在闭包中使用[无主的自我]

在WWDC 2014年会议403 中间迅速成绩单中,有以下幻灯片

enter image description here

演讲者说,在这种情况下,如果我们不使用[unowned self],它将是一个内存泄漏。这是否意味着我们应该总是在闭包中使用[unowned self] ?

Swift Weather应用的ViewController.swift的第64行上,我不使用[unowned self]。但是我通过使用一些__abc1来更新UI,比如self.temperatureself.loadingIndicator。这可能没问题,因为我定义的所有__abc1都是weak。但是为了安全起见,我们应该总是使用[unowned self]吗?

class TempNotifier {
var onChange: (Int) -> Void = {_ in }
var currentTemp = 72
init() {
onChange = { [unowned self] temp in
self.currentTemp = temp
}
}
}
171466 次浏览

不,肯定有一些时候你不想使用[unowned self]。有时你想让闭包捕获self以确保在闭包被调用时它仍然存在。

示例:发起异步网络请求

如果你正在发出一个异步网络请求,你希望闭包在请求完成时保留self。该对象可能已经被释放,但您仍然希望能够处理请求的完成。

何时使用unowned selfweak self

真正需要使用[unowned self][weak self]的唯一情况是创建强参考循环。强引用循环是指存在一个所有权循环,其中对象最终拥有彼此(可能通过第三方),因此它们永远不会被释放,因为它们都确保彼此存在。

在闭包的特定情况下,您只需要意识到在闭包中引用的任何变量都被闭包“拥有”。只要闭包存在,这些对象就一定会存在。停止该所有权的唯一方法是执行[unowned self][weak self]。因此,如果一个类拥有一个闭包,而这个闭包捕获了对该类的强引用,那么在闭包和类之间就有了一个强引用循环。这也包括类是否拥有闭包。

特别是在视频中的例子中

在幻灯片上的例子中,TempNotifier通过onChange成员变量拥有闭包。如果它们没有将self声明为unowned,闭包也将拥有self,从而创建一个强引用循环。

unownedweak的区别

unownedweak之间的区别是weak被声明为可选的,而unowned不是。通过将它声明为weak,你可以处理闭包中某个时刻它可能为nil的情况。如果你试图访问一个unowned变量,而它恰好是nil,它将使整个程序崩溃。因此,只有当你确信闭包存在时,该变量将始终存在时,才使用unowned

如果自我在闭包中可以为nil,则使用(弱自我)

如果自我在闭包中永远不会为nil,则使用(无主的自我)

Apple Swift文档中有一个很棒的部分,用图片解释了在闭包中使用<强大的>强大的无主的区别:

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html

更新11/2016

我写了一篇关于扩展这个答案的文章(查看SIL以了解ARC的功能),检查在这里

原来的答案

前面的答案并没有给出什么时候使用一种而不是另一种的直接规则,所以让我补充一些东西。

无主或弱的讨论归结为变量和引用它的闭包的一生问题。

swift weak vs un主 .

场景

你可以有两个可能的场景:

  1. 闭包具有相同的变量生命周期,因此闭包将可到达直到变量可达为止。变量和闭包具有相同的生命周期。在这种情况下,您应该将引用声明为无主。一个常见的例子是在许多小闭包的例子中使用的[unowned self],这些小闭包在它们的父闭包的上下文中做一些事情,并且没有在其他任何地方被引用,但它们的父闭包的寿命不会超过它们的父闭包。

  2. 闭包的生命周期独立于变量之一,当变量不再可用时,闭包仍然可以被引用。在这种情况下,你应该将引用声明为,并在使用它之前验证它不是nil(不要强制展开)。一个常见的例子是[weak delegate],你可以在一些闭包引用完全不相关(生命周期方面)委托对象的例子中看到。

实际使用

那么,大多数时候你会/应该使用哪种呢?

引用乔·格罗夫在推特上的话:

无主更快,并且允许不可变性和非可选性。

如果你不需要弱,就不要用它。

你会发现更多关于unnowned__abc0内部工作的在这里

* 通常也称为无主(safe),表示在访问无主引用之前执行运行时检查(导致无效引用崩溃)。

下面是来自苹果开发者论坛的精彩语录,描述了美味的细节:

unowned vs unowned(safe) vs unowned(unsafe)

unowned(safe)是一个非拥有的引用,在访问时断言 这个物体还活着。它有点像一个弱可选引用 它每次被访问时都隐式地用x!展开。 unowned(unsafe)类似于arc中的__unsafe_unretained——它是非拥有的 引用,但没有运行时检查对象是否仍然活动 在访问时,因此悬空引用将进入垃圾内存。 unowned目前一直是unowned(safe)的同义词,但是 目的是它将在-Ofast中优化为unowned(unsafe)

.当禁用运行时检查时构建

unowned vs weak

unowned实际上使用了比weak更简单的实现。 原生Swift对象携带两个引用计数,和unowned 引用碰撞 un主引用计数而不是强大的 引用计数> < /强。当对象的强大的引用时,该对象被去初始化 Count 达到0,但直到 un主引用计数也等于0。这会导致内存被占用 当有无主引用时,保留的时间稍长,但是 当使用unowned时通常不是问题,因为相关的 无论如何,对象的生命期应该是接近相等的,而且这样做要简单得多 并且开销比使用的基于边表的实现要低

在现代Swift中__ABC0内部使用与unowned相同的机制。所以这个比较是不正确的,因为它比较了Objective-C weak和Swift unonwed

原因

在拥有的引用达到0后,保持内存存活的目的是什么?如果代码试图对 对象使用无主引用后,它被去初始化?< / em > < / p > < p > 内存保持活动状态,以便其保留计数仍然可用。 类的强引用时,可以使用这种方法 对象,运行时可以检查强引用计数 是大于零才能保证是安全保留的呢 对象。< / p >

对象拥有的或无主引用会发生什么?当对象被去初始化时,它们的生命周期是否与对象解耦 它们的内存也会保留到对象被释放之后吗 最后一个无主引用是否被释放?< / em > < / p >

该对象拥有的所有资源将在该对象释放时立即释放 最后一个强引用被释放,并运行它的deinit。无主 引用只使内存保持活动状态——除了带有

.引用计数,其内容为垃圾

兴奋,是吗?

根据Apple-doc

    弱引用总是可选类型,并且是自动的

    .当它们引用的实例被释放时变为nil
  • 如果捕获的引用永远不会变成nil,那么它应该始终被捕获为无主引用,而不是弱引用

的例子,

    // if my response can nil use  [weak self]
resource.request().onComplete { [weak self] response in
guard let strongSelf = self else {
return
}
let model = strongSelf.updateModel(response)
strongSelf.updateUI(model)
}


// Only use [unowned self] unowned if guarantees that response never nil
resource.request().onComplete { [unowned self] response in
let model = self.updateModel(response)
self.updateUI(model)
}

我想我应该为视图控制器添加一些具体的例子。很多解释,不仅仅是在Stack Overflow上,真的很好,但我用现实世界的例子工作得更好(@drewag在这方面有一个很好的开始):

    如果你有一个闭包来处理来自网络请求的响应,请使用weak,因为它们是长生命的。视图控制器之前可以关闭 当调用闭包时,请求完成,因此self不再指向有效对象。李< / >
  • 如果你有一个闭包处理一个按钮上的事件。它可以是unowned,因为一旦视图控制器消失,按钮和它可能从self引用的任何其他项也会同时消失。关闭块也会同时消失。

    class MyViewController: UIViewController {
    @IBOutlet weak var myButton: UIButton!
    let networkManager = NetworkManager()
    let buttonPressClosure: () -> Void // closure must be held in this class.
    
    
    override func viewDidLoad() {
    // use unowned here
    buttonPressClosure = { [unowned self] in
    self.changeDisplayViewMode() // won't happen after vc closes.
    }
    // use weak here
    networkManager.fetch(query: query) { [weak self] (results, error) in
    self?.updateUI() // could be called any time after vc closes
    }
    }
    @IBAction func buttonPress(self: Any) {
    buttonPressClosure()
    }
    
    
    // rest of class below.
    }
    

这里有一些很好的答案。但是最近对Swift实现弱引用方式的改变应该会改变每个人对弱self和无主self的使用决策。以前,如果你需要最好的性能,使用无主的自我优于弱自我,只要你能确定自我永远不会为nil,因为访问无主的自我比访问弱自我快得多。

但是Mike Ash已经记录了Swift如何更新弱变量的实现以使用侧边表,以及这如何极大地提高弱自我性能。

https://mikeash.com/pyblog/friday-qa-2017-09-22-swift-4-weak-references.html

既然weak self没有显著的性能损失,我相信我们应该默认使用它。weak self的好处是它是可选的,这使得编写更正确的代码更容易,这基本上是Swift是如此伟大的语言的原因。您可能认为您知道哪些情况下使用无主self是安全的,但是根据我检查许多其他开发人员代码的经验,大多数人不知道。我已经修复了很多崩溃,其中无主的自我被释放,通常在背景线程完成后,控制器被释放的情况下。

bug和崩溃是编程中最耗时、最痛苦、最昂贵的部分。尽最大努力编写正确的代码并避免它们。我建议制定一条规则,永远不要强制打开可选项,永远不要使用无主self而不是弱self。你不会失去任何东西,失去时代的力量和无主的自我其实是安全的。但是,您将从消除难以发现和调试的崩溃和错误中获益良多。

如果以上都说不通:

博士tl;

就像implicitly unwrapped optional,如果你能保证 引用在它的使用点不会为nil,使用无主。 如果不是,那么你应该使用weak.

解释:

我在弱无主链路检索了以下内容。从我收集到的,无主self 不能是nil,但弱self可以是,并且无主self可以导致悬空指针…这在Objective-C中是臭名昭著的。希望能有所帮助

“无主弱引用和无主引用行为相似,但并不相同。”

无主引用,就像弱引用,不要增加被引用对象的保留计数。然而,在Swift中,无主引用添加了不是可选项目的好处。这使得它们易于管理,而不是使用可选绑定。这与隐式打开可选选项没什么不同。此外,无主引用是非零。这意味着在某些情况下,使用无主引用可以导致悬空指针。对于那些像我一样还记得Objective-C时代的书呆子来说,无主引用映射到unsafe_unretained引用。

这就是让人有点困惑的地方。

弱引用和无主引用都不会增加保留计数。

它们都可以用来打破保留循环。那么我们什么时候使用它们呢?

根据苹果公司的文档:

当引用为有效的时,使用弱引用,以便该引用在其生命周期的某个时刻变为nil。相反,当你知道在初始化过程中设置的引用永远不会为nil时,使用无主引用。”

import UIKit


class ViewController: UIViewController {


override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.


let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "AnotherViewController")
self.navigationController?.pushViewController(controller, animated: true)


}


}






import UIKit
class AnotherViewController: UIViewController {


var name : String!


deinit {
print("Deint AnotherViewController")
}


override func viewDidLoad() {
super.viewDidLoad()


print(CFGetRetainCount(self))


/*
When you test please comment out or vice versa


*/


//        // Should not use unowned here. Because unowned is used where not deallocated. or gurranted object alive. If you immediate click back button app will crash here. Though there will no retain cycles
//        clouser(string: "") { [unowned self] (boolValue)  in
//            self.name = "some"
//        }
//




//
//        // There will be a retain cycle. because viewcontroller has a strong refference to this clouser and as well as clouser (self.name) has a strong refferennce to the viewcontroller. Deint AnotherViewController will not print
//        clouser(string: "") { (boolValue)  in
//            self.name = "some"
//        }
//
//




//        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser (self.name) has a weak refferennce to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)
//
//        clouser(string: "") { [weak self] (boolValue)  in
//            self?.name = "some"
//        }




// no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser nos refference to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)


clouser(string: "") {  (boolValue)  in
print("some")
print(CFGetRetainCount(self))


}


}




func clouser(string: String, completion: @escaping (Bool) -> ()) {
// some heavy task
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
completion(true)
}


}


}

__abc2 __abc0 __abc3 __abc1

无主变量和弱变量类似,它们不会被销毁,但当对象的引用不再存在时,弱变量就会变成nil,我们可以用nils的正常检查来处理,无主变量就会变成垃圾,你无法判断它们不再是垃圾,使用它们会崩溃。用weak的问题是,如果一个对象有弱变量对它的引用,当它被销毁时,它必须遍历对它的每一个引用并将那个变量设为nil,这显然是很昂贵的,用un主来代替会崩溃,找到这种错误会很困难。使用无主的一个地方是如果你仔细创建一些包含数据类型,它有一个清晰的接口,和它的内部是不能直接访问,为你实现它可能是有用的循环引用,但有很多的自我控制,你可以使用无主让你打破这些循环引用,引用和弱变量的费用,例如,您可能有一个节点树,每个节点,需要有一个参考母公司,删除一个节点会删除它所有的子节点,所以没有必要把所有的子节点的父节点引用都设为nil。

为了避免循环引用,有些引用不希望是强引用。所以在某一时刻,当一个对象的最后一个强引用被移除时,对象本身也被移除。

其他非强引用会发生什么?显然它们不再指向那个对象了,这是有问题的。有两种方法来处理这个问题:

  1. < p >弱引用。当对一个对象的最后一个强引用消失时,所有弱引用都被设置为nil,这样开发人员就可以检查被引用的对象是否还在那里。很明显,弱引用必须是可选的,否则它不能被设置为nil。使用弱引用的策略:你写“if let ref = weakref”。如果引用仍然存在,并且由于您刚刚将它分配给了强引用,那么它将一直存在,直到“if let”结束。如果你不这样做,那么你可能会访问相同的弱引用两次,它可能(意外地)在第一次访问时不是nil,但在第二次访问时是nil。

  2. 创建一个无主引用。如果物体消失了,没有人会告诉你。当被引用的对象消失时,它看起来就像你有一个引用。只有当您100%确定引用的对象不会提前消失时,才必须使用此方法。

使用无主的,如果你已经测量,它是更快的,当你是100%,你不使用垃圾时,当对象消失。