为什么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 打印。
但是我不明白为什么他们在这里没有引用循环。 DataListViewModel
有 cancellables
有一个捕获自我的订阅。所以DataListViewModel
-> 订阅和订阅 -> DataListViewModel
。 deinit
怎么触发?一般来说,有没有一种好的方法来理解在这种情况下是否存在保留循环?
如您所料,闭包确实保留了对 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
我决心完全理解为什么这不会导致引用循环。总的来说,这里内存管理的每个阶段都发生了什么。
我有以下设置:
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 打印。
但是我不明白为什么他们在这里没有引用循环。 DataListViewModel
有 cancellables
有一个捕获自我的订阅。所以DataListViewModel
-> 订阅和订阅 -> DataListViewModel
。 deinit
怎么触发?一般来说,有没有一种好的方法来理解在这种情况下是否存在保留循环?
如您所料,闭包确实保留了对 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