Swift 委托 - 何时在委托上使用弱指针

Posted

技术标签:

【中文标题】Swift 委托 - 何时在委托上使用弱指针【英文标题】:Swift delegation - when to use weak pointer on delegate 【发布时间】:2015-07-15 09:31:30 【问题描述】:

有人能解释一下何时以及何时不使用 Swift 中的委托指针的“弱”赋值,为什么?

我的理解是,如果您使用未定义为类的协议,您不能也不想将委托指针分配给弱。

protocol MyStructProtocol
    //whatever


struct MyStruct 
    var delegate: MyStructProtocol?

但是,当您的协议被定义为类类型协议时,您是否希望将您的委托设置为弱指针?

protocol MyClassProtocol: class
    //whatever


class MyClass 
    weak var delegate: MyClassProtocol?

我说的对吗?在 Apple 的 swift guide 中,类协议示例没有使用弱分配,但在我的测试中,如果我的代表没有被弱引用,我会看到强引用循环。

【问题讨论】:

这似乎相关:blog.xebia.com/2014/10/09/… 如果你声明你的协议为protocol MyStructProtocol : class ... ,那么你可以让代理weak。见***.com/a/24104371/1271826。 @Rob 这是否意味着如果我不将我的协议声明为一个类,那么我的委托指针将导致一个保留周期? 未能让您的代表weak 并不总是会导致强引用循环,而只会增加这种可能性。 试试这个How can I make a weak protocol reference in 'pure' Swift (without @objc) 【参考方案1】:

您通常制定类协议weak 以避免“强引用循环”(以前称为“保留循环”)的风险。 (注意,我们现在通过将AnyObject 协议添加到协议的继承列表中来做到这一点;请参阅Class-Only Protocols;我们不再使用class 关键字。)未能创建代理weak 并不意味着您天生就有一个强大的参考循环,但仅仅是你可以拥有一个。

但是,使用struct 类型,强引用循环风险大大降低,因为struct 类型不是“引用”类型,因此更难创建强引用循环。但是如果委托对象是一个类对象,那么您可能希望将协议设为类协议并使其变弱。

在我看来,让班级代表weak 只是部分地减轻了强引用循环的风险。这也是所有权的问题。大多数委托协议都是这样的情况,即相关对象没有声称对委托拥有所有权的业务,而只是在相关对象提供通知委托某事(或请求某事)的能力的情况下。例如,如果您希望视图控制器具有一些文本字段委托方法,则文本字段无权声明对视图控制器的所有权。

【讨论】:

希望看到一个示例,说明结构协议何时创建强引用循环,何时不创建,但这个答案让我明白了很多。 所以根据您的要点,如果我理解正确,Apples Swift 文档中的 SnakesAndLadders 示例将通过它的 DiceGameDelegate 创建一个强大的参考循环? developer.apple.com/library/ios/documentation/Swift/Conceptual/… 那里没有强引用循环。考虑对象所有权图。想象一下拥有trackergame 的某个主对象(例如视图控制器或其他)。而game 也对tracker 有很强的引用。但是没有循环强引用循环。一切都好。要拥有强引用循环,您需要game 的委托来引用本身拥有game 的对象。但情况并非如此。因此没有强参考循环。 当然。在对象层次结构中,子对象不应维护对父对象的强引用。这是一个危险信号,表明一个强大的参考周期。请注意,在这个 VC 示例中,强引用循环并不总是表现为泄漏,但在特殊情况下可能会出现,因此建议通过将委托设置为弱属性来完全避免潜在问题。 @nwales 使用 Rob 的 cmets,我写了一个更清晰的示例 here,其中缺少 weak 不会产生强引用循环。【参考方案2】:

代表应该总是通常是弱的。

假设ba 的代表。现在adelegate 属性是b

如果您希望bc 消失时释放

如果c 持有对b 的强引用并且c 被解除分配,您希望bc 解除分配。但是,在a 中使用强委托属性,b 将永远不会被释放,因为a 会强烈地保留b。使用弱引用,一旦b 失去来自c 的强引用,b 将在c 释放时释放。

通常这是预期的行为,这就是您要使用 weak 属性的原因。

【讨论】:

我还是一头雾水。如果我不能将弱分配给非类类型协议,这是否意味着它会导致保留周期?何时使用类协议与非类协议?如果我使用结构,我只使用非类协议与类协议与类? @nwales 我知道这是一条旧评论,但是如果两者(a)您都使用参考(class)类型,则您使用class 协议; (b) 您需要weak 参考。否则,没有必要将其声明为class 协议。仅在您的协议需要时指定 class(例如,它是委托协议)。所以,如果你使用struct(一个值类型)或者你使用class,但不需要担心强引用循环(例如,用于定义委托接口以外的其他东西的协议),那么就不要让它成为class 协议。 FWIW,说代表应该总是软弱有点强。考虑URLSession,它会强烈引用其delegate,直到会话失效。但是,这仅适用,因为它们在会话无效时手动解析强引用。但作为一般经验法则,这是正确的,委托通常是弱属性。 +1【参考方案3】:

正如罗布所说:

真的是“所有权”的问题

确实如此。 “强大的参考周期”就是要获得所有权。

在以下示例中,我们没有使用weak var。然而,这两个对象都会解除分配。为什么?

protocol UserViewDelegate: class 
    func userDidTap()


class Container 
    let userView = UserView()
    let delegate = Delegate()
    init() 
        userView.delegate = delegate
    

    deinit 
        print("container deallocated")
    


class UserView 
    var delegate: UserViewDelegate?

    func mockDelegatecall() 
        delegate?.userDidTap()
    

    deinit 
        print("UserView deallocated")
    


class Delegate: UserViewDelegate 
    func userDidTap() 
        print("userDidTap Delegate callback in separate delegate object")
    

用法:

var container: Container? = Container()
container?.userView.mockDelegatecall()
container = nil // will deallocate both objects

内存所有权图(没有循环)

    +---------+container +--------+
    |                             |
    |                             |
    |                             |
    |                             |
    |                             |
    |                             |
    v                             v
userView +------------------> delegate

为了创建一个强引用循环,循环需要是完整的。 delegate 需要指向 container 但它没有。所以这不是问题。但纯粹是出于所有权原因,正如 Rob 所说here:

在对象层次结构中,子对象不应维护对对象的强引用。那是一个红旗,表示强引用循环

因此,无论是否泄漏,仍将weak 用于您的委托对象。


在以下示例中,我们没有使用weak var。因此,这两个类都不会释放。

protocol UserViewDelegate: class 
    func userDidTap()


class Container: UserViewDelegate 
    let userView = UserView()

    init() 
        userView.delegate = self
    

    func userDidTap() 
        print("userDidTap Delegate callback by Container itself")
    
    deinit 
        print("container deallocated")
    


class UserView 
    var delegate: UserViewDelegate?

    func mockDelegatecall() 
        delegate?.userDidTap()
    

    deinit 
        print("UserView deallocated")
    

用法:

var container: Container? = Container()
container?.userView.mockDelegatecall()
container = nil // will NOT deallocate either objects

内存所有权图(有周期)

     +--------------------------------------------------+
     |                                                  |
     |                                                  |
     +                                                  v
 container                                           userview
     ^                                                  |
     |                                                  |
     |                                                  |
     +------+userView.delegate = self //container+------+

使用weak var 将避免强引用循环

【讨论】:

以上是关于Swift 委托 - 何时在委托上使用弱指针的主要内容,如果未能解决你的问题,请参考以下文章

iOS Swift:闭包(回调)与委托,何时使用? [关闭]

翻译: Swift 中的委托保留周期 如何在“纯”Swift(没有@objc)中进行弱协议引用

Swift-使用委托在 tableView 上显示

弱委托和 .xib 文件

在 Swift 上委托 Objective-C 协议

使用 ARC 声明委托属性的推荐方法