iOS Swift 组合:取消 Set<AnyCancellable>

And*_*tto 25 ios swift ios13 swiftui combine

如果我已将可取消集存储到 ViewController 中:

private var bag = Set<AnyCancellable>()
Run Code Online (Sandbox Code Playgroud)

其中包含多个订阅。

1 - 我应该在 deinit 中取消订阅吗?或者它会自动完成工作?

2 - 如果是这样,我如何取消所有存储的订阅?

bag.removeAll() is enough?
Run Code Online (Sandbox Code Playgroud)

还是我应该遍历集合并一一取消所有订阅?

for sub in bag {
   sub.cancel()
}
Run Code Online (Sandbox Code Playgroud)

Apple 表示订阅一直有效,直到存储的 AnyCancellable 在内存中。所以我想解除分配可取消的bag.removeAll()应该足够了,不是吗?

And*_*ndy 13

deinit您的视图控制器会从内存中删除。它的所有实例变量都将被释放。

文档Combine > Publisher > assign(to:on:)说:

AnyCancellable 实例。当您不再希望发布者自动分配属性时,在此实例上调用 cancel()。取消初始化此实例也将取消自动分配。

1 - 我应该在 deinit 中取消订阅吗?或者它会自动完成工作?

你不需要,它会自动完成这项工作。当您的 ViewController 被释放时,实例变量bag也将被释放。由于不再引用您的AnyCancellable's,作业将结束。

2 - 如果是这样,我如何取消所有存储的订阅?

不是这样。但通常您可能有一些想要开始和停止的订阅,例如viewWillAppear/ viewDidDissapear。在这种情况下,您的 ViewController 仍在内存中。

因此,在 中viewDidDissappear,您可以bag.removeAll()按自己的想法行事。这将删除引用并停止分配。

以下是一些您可以运行以查看.removeAll()实际操作的代码:

var bag = Set<AnyCancellable>()

func testRemoveAll() {
  Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    .sink { print("===== timer: \($0)") }
    .store(in: &bag)

  Timer.publish(every: 10, on: .main, in: .common).autoconnect()
    .sink { _ in self.bag.removeAll() }
    .store(in: &bag)
}
Run Code Online (Sandbox Code Playgroud)

第一个计时器将每隔一秒触发一次并打印出一行。第二个计时器将在 10 秒后触发,然后调用bag.removeAll()。然后两个计时器发布者都将停止。

https://developer.apple.com/documentation/combine/publisher/3235801-assign


Mos*_*sam 11

如果你碰巧从你的 View 控制器订阅了一个发布者,你可能会捕获selfin sink,它会引用它,并且如果订阅者没有完成,不会让 ARC 稍后删除你的视图控制器,所以这是可取的弱地捕捉自我


所以而不是:

   ["title"]
      .publisher
      .sink { (publishedValue) in
        self.title.text = publishedValue
    }

.store(in: &cancellable)
Run Code Online (Sandbox Code Playgroud)

你应该使用[weak self]

   ["title"]
      .publisher
      .sink { [weak self] (publishedValue) in
        self?.title.text = publishedValue
    }

.store(in: &cancellable)
Run Code Online (Sandbox Code Playgroud)

因此,当删除视图控制器时,您将不会有任何保留周期或内存泄漏。

  • @giorashc 用户询问他是否应该手动取消订阅或“它自动完成工作”,我的回答显示了如果没有任何对 self 的强引用,您如何避免考虑订阅。通过这种方式,订阅将被自动删除。 (14认同)
  • 但这如何回答他的问题呢? (4认同)

sus*_*i99 10

我测试这段代码

let cancellable = Set<AnyCancellable>()

Timer.publish(every: 1, on: .main, in: .common).autoconnect()
  .sink { print("===== timer: \($0)") }
  .store(in: &cancellable)
  
cancellable.removeAll() // just remove from Set. not cancellable.cancel()
Run Code Online (Sandbox Code Playgroud)

所以我使用这个扩展。

import Combine

typealias CancelBag = Set<AnyCancellable>

extension CancelBag {
  mutating func cancelAll() {
    forEach { $0.cancel() }
    removeAll()
  }
}
Run Code Online (Sandbox Code Playgroud)


Gil*_*man 8

尝试创建一个管道,而不是将可取消存储在某个状态变量中。您会发现管道在遇到异步操作时立即停止。那是因为 Cancelable 被 ARC 清理了,因此它被自动取消了。因此,如果您释放对管道的所有引用,则无需在管道上调用取消。

文档

AnyCancellable 实例在取消初始化时会自动调用 cancel()。