简体   繁体   English

ReceiveValue 块合并订阅中为什么没有保留周期

[英]Why Is There No Retain Cycle In ReceiveValue Block Combine Subscription

I'm determined to fully understand why this isn't causing a reference cycle.我决心完全理解为什么这不会导致参考周期。 And in general what is happening at each stage of memory management here.总的来说,这里内存管理的每个阶段都在发生什么。

I have the following setup:我有以下设置:

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($0) }
        })
    }
    
    deinit {
        print("deinit")
    }
    
}

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

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

So when I create a PresenterView and then dismiss it I get a successful deinit print.所以当我创建一个 PresenterView 然后关闭它时,我得到了一个成功的 deinit 打印。

However I don't understand why they is no reference cycle here.但是我不明白为什么它们在这里没有参考循环。 DataListViewModel has cancellables which has a subscription that captures self. DataListViewModelcancellables ,它有一个可以捕获自我的订阅。 So DataListViewModel -> subscription and subscription -> DataListViewModel .所以DataListViewModel -> 订阅和订阅 -> DataListViewModel How can deinit be triggered?如何触发deinit In general is there a good approach to understanding whether there is a retain cycle in these kinds of situation?一般来说,是否有一种很好的方法来理解在这种情况下是否存在保留周期?

The closure, as you expected, does retain a strong reference to self .正如您所料,闭包确实保留了对self的强引用。 The closure itself is maintained by the Sink subscriber.闭包本身由Sink订阅者维护。

If nothing else happens, this is a memory leak because the subscriber is never cancelled, because AnyCancellable is never released, because self never de-inits, and self never de-inits because the subscriber is holding a reference it.如果没有其他事情发生,这就是内存泄漏,因为订阅者永远不会被取消,因为AnyCancellable永远不会被释放,因为self永远不会取消初始化,而self永远不会取消初始化,因为订阅者持有对它的引用。

However, in your case, the publisher completes, and that's another way for the subscriber to release its closures.但是,在您的情况下,发布者完成了,这是订阅者释放其闭包的另一种方式。 So, self is only released after the pipeline completes.所以, self只有在管道完成后才会释放。

To illustrate, we can use a PassthroughSubject to explicitly send a completion:为了说明这一点,我们可以使用PassthroughSubject显式发送完成:

class Foo {
   var c: AnyCancellable? = nil

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

      c = subject.sink {
         self.c // capture self
         print($0)
      }

      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()
}

Because self is captured, it's not released until after a completion is sent 2 seconds later:因为self被捕获了,所以直到 2 秒后发送完成后才会释放它:

sync
async
async 2 sec
deinit 

If you comment out the line subject.send(completion: .finished) , there will not be a deinit :如果您注释掉subject.send(completion: .finished) ,则不会有deinit

sync
async
async 2 sec

If you use [weak self] in the closure, the pipeline would cancel:如果在闭包中使用[weak self] ,管道将取消:

sync
deinit

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM