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

问题描述

我决心完全理解为什么这没有引起参考周期。通常,这里的内存管理每个阶段都会发生什么情况。

我有以下设置:

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->订阅和订阅-> DataListViewModeldeinit如何被触发?总的来说,有一种很好的方法来了解这种情况下是否存在保留周期?

解决方法

如您所料,闭包确实保留了对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

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...