1

我有以下 SwiftUI 视图,其中包含一个在五秒后消失的子视图。淡入淡出是通过接收 Combine TimePublisher 的结果来触发的,但是更改发布者的 sink 块中的值会导致内存泄漏showRedViewsink

import Combine
import SwiftUI

struct ContentView: View {
    @State var showRedView = true

    @State var subscriptions: Set<AnyCancellable> = []
    
    var body: some View {
        ZStack {
            if showRedView {
                Color.red
                    .transition(.opacity)
            }
            Text("Hello, world!")
                .padding()
        }
        .onAppear {
            fadeRedView()
        }
    }
    
    func fadeRedView() {
        Timer.publish(every: 5.0, on: .main, in: .default)
            .autoconnect()
            .prefix(1)
            .sink { _ in
                withAnimation {
                    showRedView = false
                }
            }
            .store(in: &subscriptions)
    }
}

AnyCancellable我认为这是通过收藏在幕后以某种方式管理的。我对 SwiftUI 和 Combine 比较陌生,所以肯定我要么在这里搞砸了一些东西,要么没有正确地考虑它。避免这种泄漏的最佳方法是什么?

编辑:添加一些显示泄漏的图片。

内存泄漏图片 1

内存泄漏图片 2

4

2 回答 2

3

视图应该被认为是描述视图的结构,以及它如何对数据做出反应。它们应该是小型、单一用途、易于初始化的结构。他们不应该持有具有自己生命周期的实例(例如保持发布者订阅)——那些属于视图模型。

class ViewModel: ObservableObject {
   var pub: AnyPublisher<Void, Never> {
        Timer.publish(every: 2.0, on: .main, in: .default).autoconnect()
            .prefix(1)
            .map { _ in }
            .eraseToAnyPublisher()
    } 
}

并用于.onReceive对视图中的已发布事件做出反应:

struct ContentView: View {
    @State var showRedView = true

    @ObservedObject vm = ViewModel()
    
    var body: some View {
        ZStack {
            if showRedView {
                Color.red
                    .transition(.opacity)
            }
            Text("Hello, world!")
                .padding()
        }
        .onReceive(self.vm.pub, perform: {
            withAnimation {
                self.showRedView = false
            }
        })
    }
}

因此,似乎通过上述安排,TimerPublisherwith prefixpublisher 链导致了泄漏。它也不是用于您的用例的正确发布者。

以下实现了相同的结果,没有泄漏:

class ViewModel: ObservableObject {
   var pub: AnyPublisher<Void, Never> {
        Just(())
           .delay(for: .seconds(2.0), scheduler: DispatchQueue.main)
           .eraseToAnyPublisher()
    } 
}
于 2020-08-09T03:16:23.030 回答
0

我的猜测是你正在泄漏,因为你存储了一个AnyCancellable并且subscriptions你永远不会删除它。

操作员sink创建. AnyCancellable除非您将其存储在某处,否则订阅将被提前取消。但是如果我们Subscribers.Sink直接使用订阅者,而不是使用sink运营商,就没有AnyCancellable我们可以管理的了。

    func fadeRedView() {
        Timer.publish(every: 5.0, on: .main, in: .default)
            .autoconnect()
            .prefix(1)
            .subscribe(Subscribers.Sink(
                receiveCompletion: { _ in },
                receiveValue: { _ in
                    withAnimation {
                        showRedView = false
                    }
                }
            ))
    }

但这仍然是矫枉过正。为此,您不需要合并。您可以直接安排活动:

    func fadeRedView() {
        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
            withAnimation {
                showRedView = false
            }

        }
    }
于 2020-08-12T14:50:19.363 回答