使用 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