[英]Why does this combine subscription not deallocate in custom ViewModifier?
[英]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 打印。
但是我不明白為什么它們在這里沒有參考循環。 DataListViewModel
有cancellables
,它有一個可以捕獲自我的訂閱。 所以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.