如何在“纯”Swift中创建一个弱协议引用(不使用@objc)

601

weak引用在Swift中似乎无法正常工作,除非声明一个@objcprotocol,但在纯Swift应用程序中我不想这样做。

这段代码会产生编译错误(weak不能应用于非类类型MyClassDelegate):

class MyClass {
  weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate {
}

我需要在协议前加上@objc,然后它就可以工作了。

问题:如何用“纯”的 Swift 实现弱引用代理?


请注意,这里的代码示例仅用于说明目的。在实际使用中,请遵循最佳实践并根据您的需求进行修改。 - Fattie
8个回答

1134
你需要将协议的类型声明为AnyObject
protocol ProtocolNameDelegate: AnyObject {
    // Protocol stuff goes here
}

class SomeClass {
    weak var delegate: ProtocolNameDelegate?
}

使用 AnyObject,你可以表达只有类才能符合此协议,而结构体或枚举则不能。


26
我对这个解决方案的问题是调用委托会导致崩溃 - EXC_BAD_ACCESS(正如其他地方的人所指出的)。这似乎是一个错误。我找到的唯一解决方案是使用@objc并从协议中消除所有Swift数据类型。 - Jim T
12
现在在Swift中进行弱引用委托的正确方式是什么?苹果文档在示例代码中没有显示或声明委托为弱引用:https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/Protocols.html - C0D3
2
这并不总是安全的 - 记住,只有在委托方也持有对代理方的引用并且您需要打破该强引用循环时,才需要使委托方变为弱引用。如果代理方不持有对委托方的引用,则代理方可能会超出范围(因为它是弱引用),您将遇到崩溃和其他问题 :/ 这是需要记住的事情。 - Trev14
5
顺便说一下,我认为“新式样”(Swift 5)是这样做的:protocol ProtocolNameDelegate: AnyObject,但无所谓。 - hnh
1
@C0D3,实际上在你提供的链接中确实有。也许是因为现在是2021年...但只需搜索该行代码weak var delegate: DiceGameDelegate?即可。 - Mykhailo Lysenko
显示剩余4条评论

326

补充答案

我一直困惑委托是否应该是弱引用。最近我学到了更多关于委托和何时使用弱引用的知识,所以让我在这里补充一些内容,以便未来的读者能够了解。

  • The purpose of using the weak keyword is to avoid strong reference cycles (retain cycles). Strong reference cycles happen when two class instances have strong references to each other. Their reference counts never go to zero so they never get deallocated.

  • You only need to use weak if the delegate is a class. Swift structs and enums are value types (their values are copied when a new instance is made), not reference types, so they don't make strong reference cycles.

  • weak references are always optional (otherwise you would used unowned) and always use var (not let) so that the optional can be set to nil when it is deallocated.

  • A parent class should naturally have a strong reference to its child classes and thus not use the weak keyword. When a child wants a reference to its parent, though, it should make it a weak reference by using the weak keyword.

  • weak should be used when you want a reference to a class that you don't own, not just for a child referencing its parent. When two non-hierarchical classes need to reference each other, choose one to be weak. The one you choose depends on the situation. See the answers to this question for more on this.

  • As a general rule, delegates should be marked as weak because most delegates are referencing classes that they do not own. This is definitely true when a child is using a delegate to communicate with a parent. Using a weak reference for the delegate is what the documentation recommends. (But see this, too.)

  • Protocols can be used for both reference types (classes) and value types (structs, enums). So in the likely case that you need to make a delegate weak, you have to make it an object-only protocol. The way to do that is to add AnyObject to the protocol's inheritance list. (In the past you did this using the class keyword, but AnyObject is preferred now.)

    protocol MyClassDelegate: AnyObject {
        // ...
    }
    
    class SomeClass {
        weak var delegate: MyClassDelegate?
    }
    

深入学习

阅读以下文章有助于我更好地理解这个问题。它们还讨论了相关问题,如unowned关键字和在闭包中发生的强引用循环。

相关


5
这一切都很好和有趣,但与我的原始问题并没有真正的关联 - 它既不是关于weak/ARC本身,也不是关于为什么delegate通常是弱引用。我们已经知道这些,只是想知道如何声明一个弱协议引用(由@flainez完美地回答了)。 - hnh
33
你说得对。实际上,我之前也有同样的问题,但我缺少很多背景信息。我进行了以上阅读并做了补充笔记,以帮助自己理解与你的问题相关的所有问题。现在我认为我可以应用你接受的答案并知道自己为什么这样做。我希望这也能帮助未来的观众。 - Suragch
5
但我能否拥有一种不依赖于类型的弱协议呢?协议本身并不关心哪个对象符合它。因此,一个类或结构体都可以符合它。是否仍然有可能同时获得两者都能符合它的好处,但只有符合它的类类型是弱引用的呢? - Just a coder
因为大多数委托引用的是他们不拥有的类,所以我会将其重写为:大多数委托者。否则,非拥有对象就成为了所有者。 - Victor Jalencas
如果我有一个类和一个结构体需要实现相同的委托协议,那么我不能将协议遵循到 AnyObject,对吗? - Aswath

40

AnyObject 是在 Swift 中使用弱引用的官方方式。

class MyClass {
    weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate: AnyObject {
}

来自苹果公司:

  

为了避免强引用循环,代理应该声明为弱引用。有关弱引用的更多信息,请参阅类实例之间的强引用循环。将协议标记为仅限类将允许您稍后声明代理必须使用弱引用。您可以通过继承 AnyObject 来将协议标记为仅限类,如“仅限类协议”中所述。

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID276


8
有趣。在Swift 4.1中,class已经被弃用了吗? - hnh
@hnh 你仍然可以通过将其作为类来创建“伪协议”,但是 protocol: AnyObject 正好符合 OP 的要求,而且副作用比将其作为类更少。(你仍然无法在值类型中使用这样的协议,但声明它为类也无法解决这个问题) - Arru

8

更新: 看起来手册已经更新了,我所提到的示例已被删除。请参见上面@flainez答案的编辑。

原文: 即使您不与Obj-C进行交互,使用@objc也是正确的方法。它确保您的协议应用于类而不是枚举或结构体。请参见手册中的“检查协议一致性”。


正如所提到的,这并不是问题的答案。一个纯Swift程序应该能够独立存在,而不必依赖于NS'ism(这可能意味着不再使用delegate,而是使用其他设计构造)。我的纯Swift MyClass实际上并不关心目标是结构体还是对象,我也不需要可选项。也许他们以后会修复它,毕竟这是一种新语言。如果需要引用语义,可能会使用类协议XYZ之类的东西? - hnh
4
值得注意的是,@objc还具有额外的副作用——@eXhausted提到的NSObjectProtocol建议更好一些。使用@objc时,如果类委托需要一个对象参数,比如'handleResult(r: MySwiftResultClass)',那么MySwiftResultClass现在需要继承自NSObject!而且它可能不再有命名空间等等。简言之,@objc是一个桥接特性,而不是语言特性。 - hnh
我认为他们已经解决了这个问题。现在你可以写:protocol MyClassDelegate : class { } - user3675131
这个有文档吗?要么我眼瞎了,要么是我做错了什么,因为我找不到任何关于这个的信息... O_O - BastiBen
我不确定它是否回答了OP的问题,但如果你正在与Objc-C进行交互,这是非常有帮助的 ;) - Dan Rosenstark

1
弱引用只适用于引用对象。除非您在协议上添加@objcAnyObjectclass限定符,否则符合协议的对象可能不是引用对象。
因此,您需要其中一个限定符(建议使用AnyObject,因为class有望被弃用)。
顺便提一下,注意在“纯Swift”应用程序中有时需要将@objc添加到您的类和属性中。这与您的开发语言无关。它会导致编译器以与Objective-C运行时兼容的方式构建您的代码,这对于某些操作系统接口(例如目标/动作和旧样式键路径)是必需的。

0
protocol MyProtocol {
    func doSomething()
}

class MyClass: MyProtocol {
    func doSomething() {
        print("Doing something")
    }
}

var weakProtocol: Weak<MyProtocol>?
let myObject = MyClass()
weakProtocol = Weak(myObject)
weakProtocol?.doSomething() // Will print "Doing something"

1
我从未见过Weak作为默认的Swift语言的一部分,它是某个库的一部分吗?如果是,您可以在您的答案中添加这部分内容。 - Robin

-1

协议必须是AnyObject的子类,class

以下是示例

    protocol NameOfProtocol: class {
   // member of protocol
    }
   class ClassName: UIViewController {
      weak var delegate: NameOfProtocol? 
    }

-9

苹果使用“NSObjectProtocol”而非“class”。

public protocol UIScrollViewDelegate : NSObjectProtocol {
   ...
}

这对我也起作用了,并且消除了在尝试实现自己的委托模式时遇到的错误。


5
这个问题与构建纯Swift类有关(特别是没有NSObject),支持委托对象。它不涉及实现Objective-C协议,这是你正在做的事情。后者需要@ objc即NSObjectProtocol。 - hnh
好的,但不被推荐。 - DawnSong

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