簡體   English   中英

ReceiveValue 塊合並訂閱中為什么沒有保留周期

[英]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($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()
    }
}

所以當我創建一個 PresenterView 然后關閉它時,我得到了一個成功的 deinit 打印。

但是我不明白為什么它們在這里沒有參考循環。 DataListViewModelcancellables ,它有一個可以捕獲自我的訂閱。 所以DataListViewModel -> 訂閱和訂閱 -> DataListViewModel 如何觸發deinit 一般來說,是否有一種很好的方法來理解在這種情況下是否存在保留周期?

正如您所料,閉包確實保留了對self的強引用。 閉包本身由Sink訂閱者維護。

如果沒有其他事情發生,這就是內存泄漏,因為訂閱者永遠不會被取消,因為AnyCancellable永遠不會被釋放,因為self永遠不會取消初始化,而self永遠不會取消初始化,因為訂閱者持有對它的引用。

但是,在您的情況下,發布者完成了,這是訂閱者釋放其閉包的另一種方式。 所以, self只有在管道完成后才會釋放。

為了說明這一點,我們可以使用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()
}

因為self被捕獲了,所以直到 2 秒后發送完成后才會釋放它:

sync
async
async 2 sec
deinit 

如果您注釋掉subject.send(completion: .finished) ,則不會有deinit

sync
async
async 2 sec

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

sync
deinit

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM