使用 Apple 的新 Combine 框架时如何防止强引用循环(.assign 导致问题)
How to prevent strong reference cycles when using Apple's new Combine framework (.assign is causing problems)
我不太明白如何将订阅者正确存储在 class 中,以便它们持续存在但不会阻止对象被取消初始化。这是对象不会取消初始化的示例:
import UIKit
import Combine
class Test {
public var name: String = ""
private var disposeBag: Set<AnyCancellable> = Set()
deinit {
print("deinit")
}
init(publisher: CurrentValueSubject<String, Never>) {
publisher.assign(to: \.name, on: self).store(in: &disposeBag)
}
}
let publisher = CurrentValueSubject<String, Never>("Test")
var test: Test? = Test(publisher: publisher)
test = nil
当我用 sink
替换 assign
时(我在其中正确声明了 [weak self]
),它实际上确实正确地 deinit (可能是因为 assign
访问 self
导致问题的方式)。
例如,在使用 .assign
时如何防止强引用循环?
谢谢
我不知道你有什么反对闭包的,但解决方案是不要在赋值中使用 self:
import Combine
import SwiftUI
class NameStore {
var name: String
init() { name = "" }
deinit { print("deinit NameStore") }
}
class Test {
private var nameStore = NameStore()
public var name: String { get { return nameStore.name } }
var subscriber: AnyCancellable? = nil
deinit { print("deinit Test") }
init(publisher: CurrentValueSubject<String, Never>) {
subscriber = publisher.print().assign(to: \NameStore.name, on: nameStore)
}
}
let publisher = CurrentValueSubject<String, Never>("Test")
var test: Test? = Test(publisher: publisher)
struct ContentView : View {
var body: some View {
Button(
action: { test = nil },
label: {Text("test = nil")}
)
}
}
据我所知,弱引用只允许在闭包中使用,所以这不是答案。将引用放入另一个对象意味着两者都可以被释放。
我添加了一个 ContentView,因为它更易于使用,并且我向管道添加了一个打印以查看发生了什么。计算出的名字可能不是必需的,它只是让它看起来和你的一样。我也删除了Set,它可能有用,但我还没有弄清楚什么时候。
您可以将 .asign(to:) 替换为 sink,其中 [weak self] 在其闭包中阻止了内存循环。在 Playground 中尝试一下,看看有什么不同
final class Bar: ObservableObject {
@Published var input: String = ""
@Published var output: String = ""
private var subscription: AnyCancellable?
init() {
subscription = $input
.filter { [=10=].count > 0 }
.map { "\([=10=]) World!" }
//.assignNoRetain(to: \.output, on: self)
.sink { [weak self] (value) in
self?.output = value
}
}
deinit {
subscription?.cancel()
print("\(self): \(#function)")
}
}
// test it!!
var bar: Bar? = Bar()
let foo = bar?.$output.sink { print([=10=]) }
bar?.input = "Hello"
bar?.input = "Goodby,"
bar = nil
它打印
Hello World!
Goodby, World!
__lldb_expr_4.Bar: deinit
所以我们没有内存泄漏!
终于在 forums.swift.org 有人做了一个不错的小东西
extension Publisher where Self.Failure == Never {
public func assignNoRetain<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable where Root: AnyObject {
sink { [weak object] (value) in
object?[keyPath: keyPath] = value
}
}
}
您应该从 disposeBag
中删除存储的 AnyCancellable
以释放 Test
实例。
import UIKit
import Combine
private var disposeBag: Set<AnyCancellable> = Set()
class Test {
public var name: String = ""
deinit {
print("deinit")
}
init(publisher: CurrentValueSubject<String, Never>) {
publisher.assign(to: \.name, on: self).store(in: &disposeBag)
}
}
let publisher = CurrentValueSubject<String, Never>("Test")
var test: Test? = Test(publisher: publisher)
disposeBag.removeAll()
test = nil
或使用可选的 disposeBag
import UIKit
import Combine
class Test {
public var name: String = ""
private var disposeBag: Set<AnyCancellable>? = Set()
deinit {
print("deinit")
}
init(publisher: CurrentValueSubject<String, Never>) {
guard var disposeBag = disposeBag else { return }
publisher.assign(to: \.name, on: self).store(in: &disposeBag)
}
}
let publisher = CurrentValueSubject<String, Never>("Test")
var test: Test? = Test(publisher: publisher)
test = nil
怎么样:
class Test {
@Published var name: String = ""
deinit {
print("deinit")
}
init(publisher: CurrentValueSubject<String, Never>) {
publisher.assign(to: &$name)
}
}
此版本的 assign
操作在内部管理内存(不 return AnyCancellable
),因为它与对象一起消亡。请注意,您需要将 属性 转换为 @Published
。
我不太明白如何将订阅者正确存储在 class 中,以便它们持续存在但不会阻止对象被取消初始化。这是对象不会取消初始化的示例:
import UIKit
import Combine
class Test {
public var name: String = ""
private var disposeBag: Set<AnyCancellable> = Set()
deinit {
print("deinit")
}
init(publisher: CurrentValueSubject<String, Never>) {
publisher.assign(to: \.name, on: self).store(in: &disposeBag)
}
}
let publisher = CurrentValueSubject<String, Never>("Test")
var test: Test? = Test(publisher: publisher)
test = nil
当我用 sink
替换 assign
时(我在其中正确声明了 [weak self]
),它实际上确实正确地 deinit (可能是因为 assign
访问 self
导致问题的方式)。
例如,在使用 .assign
时如何防止强引用循环?
谢谢
我不知道你有什么反对闭包的,但解决方案是不要在赋值中使用 self:
import Combine
import SwiftUI
class NameStore {
var name: String
init() { name = "" }
deinit { print("deinit NameStore") }
}
class Test {
private var nameStore = NameStore()
public var name: String { get { return nameStore.name } }
var subscriber: AnyCancellable? = nil
deinit { print("deinit Test") }
init(publisher: CurrentValueSubject<String, Never>) {
subscriber = publisher.print().assign(to: \NameStore.name, on: nameStore)
}
}
let publisher = CurrentValueSubject<String, Never>("Test")
var test: Test? = Test(publisher: publisher)
struct ContentView : View {
var body: some View {
Button(
action: { test = nil },
label: {Text("test = nil")}
)
}
}
据我所知,弱引用只允许在闭包中使用,所以这不是答案。将引用放入另一个对象意味着两者都可以被释放。
我添加了一个 ContentView,因为它更易于使用,并且我向管道添加了一个打印以查看发生了什么。计算出的名字可能不是必需的,它只是让它看起来和你的一样。我也删除了Set,它可能有用,但我还没有弄清楚什么时候。
您可以将 .asign(to:) 替换为 sink,其中 [weak self] 在其闭包中阻止了内存循环。在 Playground 中尝试一下,看看有什么不同
final class Bar: ObservableObject {
@Published var input: String = ""
@Published var output: String = ""
private var subscription: AnyCancellable?
init() {
subscription = $input
.filter { [=10=].count > 0 }
.map { "\([=10=]) World!" }
//.assignNoRetain(to: \.output, on: self)
.sink { [weak self] (value) in
self?.output = value
}
}
deinit {
subscription?.cancel()
print("\(self): \(#function)")
}
}
// test it!!
var bar: Bar? = Bar()
let foo = bar?.$output.sink { print([=10=]) }
bar?.input = "Hello"
bar?.input = "Goodby,"
bar = nil
它打印
Hello World!
Goodby, World!
__lldb_expr_4.Bar: deinit
所以我们没有内存泄漏!
终于在 forums.swift.org 有人做了一个不错的小东西
extension Publisher where Self.Failure == Never {
public func assignNoRetain<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable where Root: AnyObject {
sink { [weak object] (value) in
object?[keyPath: keyPath] = value
}
}
}
您应该从 disposeBag
中删除存储的 AnyCancellable
以释放 Test
实例。
import UIKit
import Combine
private var disposeBag: Set<AnyCancellable> = Set()
class Test {
public var name: String = ""
deinit {
print("deinit")
}
init(publisher: CurrentValueSubject<String, Never>) {
publisher.assign(to: \.name, on: self).store(in: &disposeBag)
}
}
let publisher = CurrentValueSubject<String, Never>("Test")
var test: Test? = Test(publisher: publisher)
disposeBag.removeAll()
test = nil
或使用可选的 disposeBag
import UIKit
import Combine
class Test {
public var name: String = ""
private var disposeBag: Set<AnyCancellable>? = Set()
deinit {
print("deinit")
}
init(publisher: CurrentValueSubject<String, Never>) {
guard var disposeBag = disposeBag else { return }
publisher.assign(to: \.name, on: self).store(in: &disposeBag)
}
}
let publisher = CurrentValueSubject<String, Never>("Test")
var test: Test? = Test(publisher: publisher)
test = nil
怎么样:
class Test {
@Published var name: String = ""
deinit {
print("deinit")
}
init(publisher: CurrentValueSubject<String, Never>) {
publisher.assign(to: &$name)
}
}
此版本的 assign
操作在内部管理内存(不 return AnyCancellable
),因为它与对象一起消亡。请注意,您需要将 属性 转换为 @Published
。