为什么receiveValue block Combine订阅没有retain cycle

Why Is There No Retain Cycle In ReceiveValue Block Combine Subscription

我决心完全理解为什么这不会导致引用循环。总的来说,这里内存管理的每个阶段都发生了什么。

我有以下设置:

struct PresenterView: View {
    @State private var isPresented = false
    var body: some View {
        Text("Show")
            .sheet(isPresented: $isPresented) {
                DataList()
            }
            .onTapGesture {
                isPresented = true
            }
    }
}

struct DataList: View {

    @StateObject private var viewModel = DataListViewModel()
    
    var body: some View {
        NavigationView {
            List(viewModel.itemViewModels, id: \.self) { itemViewModel in
                Text(itemViewModel.displayText)
            }.onAppear {
                viewModel.fetchData()
            }.navigationBarTitle("Items")
        }
    }
}

class DataListViewModel: ObservableObject {
    
    private let webService = WebService()

    @Published var itemViewModels = [ItemViewModel]()
    
    private var cancellable: AnyCancellable?
    
    func fetchData() {
        cancellable = webService.fetchData().sink(receiveCompletion: { _ in
            //...
        }, receiveValue: { dataContainer in
            self.itemViewModels = dataContainer.data.items.map { ItemViewModel([=10=]) }
        })
    }
    
    deinit {
        print("deinit")
    }
    
}

final class WebService {
    
    var components: URLComponents {
        //...
        return components
    }

    func fetchData() -> AnyPublisher<DataContainer, Error> {
        return URLSession.shared.dataTaskPublisher(for: components.url!)
            .map { [=10=].data }
            .decode(type: DataContainer.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
}

因此,当我创建 PresenterView 然后关闭它时,我得到了成功的 deinit 打印。

但是我不明白为什么他们在这里没有引用循环。 DataListViewModelcancellables 有一个捕获自我的订阅。所以DataListViewModel -> 订阅和订阅 -> DataListViewModeldeinit怎么触发?一般来说,有没有一种好的方法来理解在这种情况下是否存在保留循环?

如您所料,闭包确实保留了对 self 的强引用。闭包本身由 Sink 订阅者维护。

如果没有其他事情发生,这是一个内存泄漏,因为订阅者永远不会被取消,因为 AnyCancellable 永远不会被释放,因为 self 永远不会 de-inits,并且 self 永远不会 de-inits 因为订阅者正在引用它。

但是,在您的情况下,发布者完成,这是订阅者发布其关闭的另一种方式。因此,self 仅在管道完成后发布。

为了说明,我们可以使用 PassthroughSubject 显式发送完成:

class Foo {
   var c: AnyCancellable? = nil

   func fetch() {
      let subject = PassthroughSubject<String, Never>()

      c = subject.sink {
         self.c // capture self
         print([=10=])
      }

      subject.send("sync")

      DispatchQueue.main.async { subject.send("async") }

      DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 
         subject.send("async 2 sec")
         subject.send(completion: .finished)
      }
   }

   deinit { print("deinit") }
}


do {
   Foo().fetch()
}

因为self被捕获,所以直到2秒后发送完成后才释放:

sync
async
async 2 sec
deinit 

如果注释掉subject.send(completion: .finished)行,就不会有deinit:

sync
async
async 2 sec

如果在闭包中使用[weak self],管道将取消:

sync
deinit