弱引用和无主引用有什么区别?

264

Swift拥有以下内容:

  • 强引用
  • 弱引用
  • 无主引用

无主引用和弱引用有何不同?

在什么情况下使用无主引用是安全的?

无主引用是否像C/C++中的悬垂指针一样存在安全风险?


4
非常好的文章,位于http://www.andrewcbancroft.com/2015/05/08/strong-weak-and-unowned-sorting-out-arc-and-swift/。 - Zeeshan
我的经验是对于我们控制的类使用 unowned,对于苹果的类,使用 weak,因为我们无法确定它确切的作用。 - onmyway133
@NoorAli,或者说“ownedBy”作为“unowned”的引用通常指向所有者。 - Ian Ringrose
1
请注意,每个参考文献都有重要的性能影响:https://dev59.com/31MH5IYBdhLWcg3wtRcg - Epic Byte
@EpicByte 有时候像Java或C#这样的全GC机制是值得开销的。 - Ian Ringrose
@EpicByte https://dev59.com/U5Pea4cB1Zd3GeqP-x3u this states other wise - amar
7个回答

385

weakunowned 引用都不会对所引用的对象产生强引用(也就是说,它们不会增加引用计数以防止 ARC 释放所引用的对象)。

但是为什么有两个关键字呢?这与 Swift 语言内置的可选类型有关。简单来说,可选类型 提供了内存安全性(这与 Swift 的构造器规则 配合非常好,这些规则严格限制以提供此类好处)。

weak 引用允许其变为 nil 的可能性(当所引用的对象被释放时,这会自动发生),因此您的属性类型必须是可选的 - 因此,作为程序员,您必须在使用它之前检查它(基本上编译器尽可能地强制您编写安全代码)。

unowned引用假设在其生命周期内永远不会变为nilunowned引用必须在初始化期间设置-这意味着该引用将被定义为非可选类型,可以安全地使用而无需检查。 如果某种方式正在引用的对象被释放,则当使用unowned引用时,应用程序将崩溃。

来自Apple文档

每当该引用在其生命周期中的某个时间点可能变为nil时,请使用弱引用。 相反,当您知道引用在初始化后永远不会为nil时,请使用unowned引用。

在文档中,有一些示例讨论保留周期以及如何打破它们。 所有这些示例都是从文档中提取的。

weak关键字的示例:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
}
 
class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    weak var tenant: Person?
}

现在,为大家带来一些ASCII艺术(你应该去查看文档 - 他们有漂亮的图表):

Person ===(strong)==> Apartment
Person <==(weak)===== Apartment

“Person”和“Apartment”的示例展示了一种情况,其中两个属性都允许为nil,但可能会导致强引用循环。这种情况最好使用弱引用来解决。两个实体可以存在而不必严格依赖于对方。
“unowned”关键字的示例:
class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) { self.name = name }
}
 
class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) { self.number = number; self.customer = customer }
}

在这个例子中,一个顾客可能有或没有信用卡,但是一个信用卡总是与一个顾客相关联。为了表示这一点,Customer类具有可选的card属性,但CreditCard类具有非可选(且不拥有)的customer属性。
Customer ===(strong)==> CreditCard
Customer <==(unowned)== CreditCard

这个例子展示了一个情况:一个属性可以为nil,而另一个属性则不允许为空,这种情况可能会导致强引用循环。使用unowned引用可以最好地解决这种情况,使代码更加安全。
需要注意的是,苹果公司提醒我们:弱引用必须声明为变量,以表明它们的值可以在运行时更改。弱引用不能被声明为常量。
此外,还有第三种情况,即两个属性始终应该具有值,一旦初始化完成,这两个属性都不应该为nil。
在使用闭包时,也要避免经典的保留周期场景。
如果想了解更多,请参阅Apple文档或阅读书籍

3
这个例子比较简单,但是我觉得关于公寓和人的例子有些令人困惑,同时也提供了另一种打破强引用循环的解决方法。一个人的公寓可以选择不设置,因此可能为空值,同样,一个公寓的租户也可以选择不设置,也可能为空值,因此这两个属性都可以定义为弱引用。 - Justin Levi Winter
类 Person { let name: String init(name: String) { self.name = name } weak var apartment: Apartment? }类 Apartment { let number: Int init(number: Int) { self.number = number } weak var tenant: Person? } - Justin Levi Winter
3
weak var Person?var Person?之间的区别是什么? - netwire
4
如果你将这两个属性都声明为weak,它们有可能被解除分配。Person对Apartment保持强引用,因此Apartment不会被解除分配。如果Apartment也对Person保持相同的强引用,它们将创建一个保留循环 - 程序员在运行时可以知道如何打破循环,否则它就是一个内存泄漏。这就是强、弱和无主引用的烦恼所在:更高层次的内存管理,因为ARC为我们完成了所有麻烦的事情。避免保留循环是我们的工作。 - Ilea Cristian
1
使用未拥有的唯一好处是不需要解包并且可以使用常量吗?是否存在无法使用弱引用而只能使用未拥有引用的情况? - Alan
显示剩余5条评论

37

Q1. “未拥有引用(Unowned Reference)”与“弱引用(Weak Reference)”有什么不同?

弱引用:

弱引用是一种不会对所指实例产生强引用的引用,因此不会阻止 ARC 销毁被引用的实例。由于弱引用允许具有“无值”,您必须将每个弱引用声明为可选类型。 (Apple 文档)

未拥有引用:

与弱引用类似,未拥有引用也不会对所指实例产生强引用。但与弱引用不同的是,未拥有引用始终假定具有值。因此,未拥有引用始终被定义为非可选类型。 (Apple 文档)

何时使用:

只有在该引用在其生命周期内的某些时间点上可以成为 nil 时,才使用弱引用。相反,在初始化完成后,您知道引用永远不会是 nil 时,请使用未拥有引用。 (Apple 文档)


Q2. 何时使用“未拥有引用”是安全的?

如上所述,未拥有引用始终假定具有值。因此,只有在您确定该引用永远不会是 nil 时,才应使用它。Apple 文档通过以下示例阐述了未拥有引用的使用案例。

设想我们有两个类 Customer 和 CreditCard。客户可以没有信用卡,但信用卡没有客户就不存在,即可以认为信用卡始终有客户。因此,它们应该具有以下关系:

class Customer {
    var card: CreditCard?
}

class CreditCard {
    unowned let customer: Customer
}
Q3. “无主引用”像C/C++的“悬空指针”一样存在安全风险吗? 我认为不会。
由于无主引用只是弱引用,保证有一个值,所以它在任何方面都不应该构成安全风险。但是,如果您尝试在它所引用的实例被释放后访问无主引用,您将触发运行时错误,应用程序将崩溃。
这是我所见到的唯一风险。 链接至 Apple 文档

你的Q2示例程序很容易理解关于unowned..谢谢..你能否添加相同类型的示例来说明weak和strong.. - Ranjithkumar
你能提供一個未擁有或弱引用的常見例子嗎? - mfaani
考虑对象的父子关系,如果子对象不能存在于没有父对象的情况下,则在子类中使用unowned来表示父对象的属性。反之则使用weak。@myxtic解释得很好!unowned引用只是保证有值的weak引用! - Saif

27

8
为什么不使用“weak”,即使“self”永远不可能为“nil”,也没有任何损害,对吗? - Boon
5
嗨@Boon - 那确实是一个关键问题。 - Fattie
@HassanTareq,我认为这篇文章中提到了一些很好的例子。请查看“解决闭包的强引用循环”部分,特别是引用:“当您在闭包中引用self的成员时,Swift要求您编写self.someProperty或self.someMethod()(而不仅仅是someProperty或someMethod())。这可以帮助您记住可能会意外捕获self。” - Nick Entin
@HassanTareq 当闭包执行时,可能会在未来很长一段时间内(例如HTTP请求)用户已经关闭了视图控制器。如果在这种情况下使用unowned,将会导致应用程序崩溃。 - Rafael Nobre
1
@Boon 如果你总是使用 weak,编译器会强制在使用前进行可选项检查。如果没有进行这种检查,将会产生编译时错误。这并不会带来其他的不利影响。 - Vikas Mishra
显示剩余2条评论

6

链接中提取出以下内容:

几点结论

  • 如果你在处理结构体或枚举等值类型,那么不需要指定常量或变量的弱引用或无主引用,因为 ARC 不会管理这些类型的内存。
  • 强引用在父子关系中非常适用,即父对象持有对其子对象的强引用。实际上,大多数情况下,强引用是最合适的引用方式。
  • 当两个实例之间可以互相关联时,请确保其中一个实例对另一个实例持有弱引用。
  • 当两个实例之间存在必要的依赖关系时,具有强制依赖性的实例需要对另一个实例持有无主引用。

2
“weak”和“unowned”引用都不会影响对象的引用计数。但是,弱引用始终是可选的,即它可以为nil,而“unowned”引用永远不会为nil,因此它们永远不会是可选的。使用可选引用时,您总是必须处理对象可能为nil的情况。在使用“unowned”引用时,您必须确保对象永远不会为nil。将“unowned”引用用于nil对象将类似于强制解包为nil的可选项。
话虽如此,在确信对象的生命周期大于引用的情况下,使用“unowned”引用是安全的。如果不是这种情况,最好使用弱引用。
至于问题的第三部分,我认为“unowned”引用与悬空指针并不相似。当我们谈论引用计数时,通常是指对象的强引用计数。同样,Swift维护对象的“unowned”引用计数和弱引用计数(弱引用指向一个称为“side table”的东西,而不是对象本身)。当强引用计数达到零时,对象被析构,但如果“unowned”引用计数大于零,则不能将其释放。
现在,悬空指针是指指向已被释放的内存位置的指针。但是在 Swift 中,由于只要对象存在未拥有的引用,内存就可以被释放,因此它不会导致悬空指针。
有很多文章详细讨论了 Swift 的内存管理。这里 是其中之一。

0

未拥有引用是一种在两个对象之间存在相同生命周期关系的情况下使用的弱引用,当一个对象只能被另一个对象拥有时。这是一种创建对象与其属性之间不可变绑定的方式。

在中级 Swift WWDC 视频中给出的示例中,一个人拥有一张信用卡,而一张信用卡只能有一个持有者。在信用卡上,人不应该是一个可选属性,因为你不希望有一张只有一个所有者的信用卡漂浮在外。你可以通过将信用卡上的持有者属性设置为弱引用来打破这个循环,但这也需要将其设置为可选的,并且是可变的(而不是常量)。在这种情况下,未拥有引用意味着虽然信用卡对于人没有所有权,但它的生命周期取决于人。

class Person {
    var card: CreditCard?
}

class CreditCard {

    unowned let holder: Person

    init (holder: Person) {
        self.holder = holder
    }
}

链接到WWDC视频或标题? - Osa

-1

当您确定您在访问self时,self永远不会是nil时,请使用unowned

例如(当然,您可以直接从MyViewController添加目标,但这只是一个简单的例子):

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

        let myButton = MyButton { [unowned self] in
            print("At this point, self can NEVER be nil. You are safe to use unowned.")
            print("This is because myButton can not be referenced without/outside this instance (myViewController)")
        }
    }
}

class MyButton: UIButton {
    var clicked: (() -> ())

    init(clicked: (() -> ())) {
        self.clicked = clicked

        // We use constraints to layout the view. We don't explicitly set the frame.
        super.init(frame: .zero)

        addTarget(self, action: #selector(clicked), for: .touchUpInside)
    }

    @objc private func sendClosure() {
        clicked()
    }
}

当你访问 self 时,存在可能它为 nil 的情况下,请使用 weak

例如:

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

        NetworkManager.sharedInstance.receivedData = { [weak self] (data) in
            print("Can you guarentee that self is always available when the network manager received data?")
            print("Nope, you can't. Network manager will be alive, regardless of this particular instance of MyViewController")
            print("You should use weak self here, since you are not sure if this instance is still alive for every")
            print("future callback of network manager")
        }
    }
}

class NetworkManager {

    static let sharedInstance = NetworkManager()

    var receivedData: ((Data) -> ())?

    private func process(_ data: Data) {
        // process the data...

        // ... eventually notify a possible listener.
        receivedData?(data)
    }
}

unowned 的缺点:

  • weak 更高效。
  • 你可以(或者说你被迫)将实例标记为不可变的(自 Swift 5.0 起不再需要)。
  • 向代码读者表明:这个实例与 X 有关系,如果 X 消失了,我也会消失。

weak 的缺点:

  • unowned 更安全(因为它不会崩溃)。
  • 可以创建一个双向关系,但两者都可以独立存在。

如果你不确定,使用 weak。等等,我的意思是在 StackOverflow 上询问你的情况下应该怎么做!在不应该使用 weak 的情况下始终使用 weak 只会让你和代码读者感到困惑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接