这两个代表声明有什么区别?

What's the difference between these two declarations of delegates?

我正在学习委托,但我不明白为什么

let friendsFunctionsDelegate = FriendsFunctionsDelegate()
let friendsFunctions = FriendsFunctions()

friendsFunctionsDelegate.delegate = friendsFunctions

是正确的,而

let friendsFunctionsDelegate = FriendsFunctionsDelegate()
friendsFunctionsDelegate.delegate = FriendsFunction()

错了。

完整代码如下:

protocol FriendsDelegate: AnyObject {
    func orderPizza()
    func takeABreak()
}

class FriendsFunctionsDelegate {
    weak var delegate: FriendsDelegate? = nil
    
    func buyPizza() {
        delegate?.orderPizza()
    }
    
    func sleep() {
        delegate?.takeABreak()
    }
}

class FriendsFunctions: FriendsDelegate {
    func orderPizza() {
        print("I ordered a pizza")
    }
    
    func takeABreak() {
        print("I'm going to sleep")
    }
}

let friendsFunctionsDelegate = FriendsFunctionsDelegate()
let friendsFunctions = FriendsFunctions()

friendsFunctionsDelegate.delegate = friendsFunctions

那个

let friendsFunctions = FriendsFunctions() // holds a strong reference
friendsFunctionsDelegate.delegate = friendsFunctions

有效,因为它拥有强大的参考,而这个

friendsFunctionsDelegate.delegate = FriendsFunction()

不要工作,因为两个部分 (lhs & rhs) 都是 weak 所以没有保留发生 ( delegateweak 属性 )

注意 FriendsFunctionsDelegate.delegate 属性 是弱引用:

weak var delegate: FriendsDelegate? = nil
^^^^

如果你这样做了

friendsFunctionsDelegate.delegate = FriendsFunction()

您创建了一个 FriendsFunction 对象,唯一引用它的对象是 friendsFunctionsDelegate,通过其 delegate 属性。但这是一个弱参考。新的 FriendsFunction 对象没有强引用!因此,它会在创建后立即被释放。

您应该会在此处看到一条警告:

Instance will be immediately deallocated because property 'delegate' is 'weak'

另一方面,如果您先将新创建的 FriendsFunction 对象放入一个 let 常量中,

let friendsFunctions = FriendsFunctions()

let 常量 friendsFunctions 将是对 FriendsFunctions 对象的强引用,因为它不是 weak。并且由于至少有一个强引用指向它,所以在所有强引用都消失之前,该对象不会被释放。

有关详细信息,请参阅 Swift 指南中的 Automatic Reference Counting

ARC(用于自动引用计数)

ARC 是自动管理对象的内存分配/释放的系统。

它的工作方式是,只要您有 AT LEAST 1 对代码中对象的强引用,它就会 保留在内存中请注意,默认情况下,任何未定义为弱的 属性 定义都隐含地是强的。

当将 属性 指定为弱时,它不会将“持有引用计数”增加一。

您遇到的问题...

在您的 first 示例中,当您第一次创建一个 let 常量时,您持有一个 strong 引用,然后将其分配给弱变量。 然后你有 =>

  1. 1 对对象的弱引用(存储在friendsFunctionsDelegate.delegate
  2. 1 对对象的强引用(let 常量let friendsFunctions = FriendsFunctions()

因此,ARC NOT 释放对象(强引用 >= 1)=> 它正在工作 ✅

在您的 second 示例中,当您直接实例化 + 将委托分配给弱变量时 WITHOUT 首先创建一个常量。 然后你有 =>

  1. 1 对对象的弱引用(存储在friendsFunctionsDelegate.delegate
  2. 0 对对象的强引用

结果,ARC在赋值后直接解除分配(从内存中释放)对象(强引用==0)=>不工作❌

结论

作为结论,您需要在某处保留对该委托对象的强引用。

委托模式中的弱使用

使用委派时,我们使用弱 属性 来防止内存保留循环。当您有一个对象(对象 A)持有对另一个对象(对象 B)的强引用,而该对象也对第一个对象(对象 A)具有强引用时,就会发生这种情况。

  • A => B 强
  • B => A 强

=> ⚠️保留周期⚠️

当您尝试从内存中删除对象 A 或 B 时,强引用计数仍为 1,然后您将遇到内存泄漏,内存中充满了未使用的对象,这可能会导致应用可用.解决方案是将这两个引用之一定义为弱引用(而不是两者)。应用委托模式时,您可以将持有对委托的引用的 属性 定义为弱。

一些额外的评论:

  1. 小心可能会产生误导的命名。 delegate 关键字应该仅附加到协议而不是 class 名称。如果您将其删除,尽管会有重叠,这暗示命名可以更恰当地定义。
  2. 当您将 属性 定义为 Optional 并且希望它默认为 nil 时,您不必明确指定 = nil。虽然你仍然可以做到 ;)
  3. 最佳实践告诉您,当您希望 class 符合委托/协议时,您应该使用扩展而不是直接符合 class 定义。
  4. 委托模式应该是一种盲目的通信模式,因此您的 class 不应该知道超出范围的功能。然后委托方法的命名应修改为类似 func didBuyPizza()func didTakeABreak() 或类似的东西。