【问题标题】:SwiftUI - ObservedObject is never deallocatedSwiftUI - ObservedObject 永远不会被释放
【发布时间】:2021-07-28 13:54:42
【问题描述】:

我的应用程序正在泄漏模型对象,因为对象保留了保留视图本身的闭包。 最好举个例子来说明。 在下面的代码中,ModelContentView 消失后不会被释放。

//
// Content View is an owner of `Model`
// It passes it to `ViewB`
//
// When button is tapped, ContentView executes action
// assigned to Model by the ViewB
//
struct ContentView: View {
    
    @StateObject private var model = Model()
    
    var body: some View {
        VStack {
            Button(action: {
                model.action?()
            }) {
                Text("Tap")
            }
            ViewB(model: model)
        }
        .frame(width: 100, height: 100)
        .onDisappear {
            print("CONTENT DISAPPEAR")
        }
    }
}

struct ViewB: View {
    
    @ObservedObject var model: Model
    
    var body: some View {
        Color.red.frame(width: 20, height: 20)
            .onAppear {
//
// DANGER:
// Assigning this makes a leak and Model is never deallocated.
// This is because the closure is retaining 'self'
// But since it's a struct, how can we break the cycle here?
//
                model.action = { bAction() }
            }
    }
    
    private func bAction() {
        print("Hey!")
    }
    
}


class Model: ObservableObject {
    
    var action: (() -> Void)?
    
    deinit {
        print("MODEL DEINIT")
    }
}

我不确定为什么这里会出现某种保留周期。 由于 View 是一个结构体,所以在闭包中引用它应该是安全的,对吧?

【问题讨论】:

  • 我会让模型成为可选的和@ObservedObject,而不是状态对象。我认为这会解决你的问题。

标签: ios macos swiftui


【解决方案1】:

Model 不是一个结构,它是一个ObservableObject,它的类型是AnyObject,它是一个Object

你应该在.onAppear的捕获列表中应用weak to

.onAppear { [weak model] }

认为你也可以只捕获模型,以防问题所在

.onAppear { [model] }

【讨论】:

  • 不幸的是,它没有帮助。请注意,问题在于在分配给model.action 的块中捕获self。在onAppear 中捕获模型很好,不会导致泄漏。
【解决方案2】:

Ahoy @msmialko,虽然我无法对我所观察到的情况给出太多理由,但希望这将是朝着正确方向迈出的一步。

我决定从等式中删除 SwiftUI 的内存管理,并使用简单的值和引用类型进行测试:

private func doMemoryTest() {
  struct ContentView {
    let model: Model

    func pressButton() {
      model.action?()
    }
  }

  struct ViewB {
    let model: Model

    func onAppear() {
      model.action = action
      // { [weak model] in
      //   model?.action = action
      // }()
    }

    func onDisappear() {
      print("on ViewB's disappear")
      model.action = nil
    }

    private func action() {
      print("Hey!")
    }
  }

  class Model {
    var action: (() -> Void)?
    deinit {
      print("*** DEALLOCATING MODEL")
    }
  }


  var contentView: ContentView? = .init(model: Model())
  var viewB: ViewB? = .init(model: contentView!.model)

  contentView?.pressButton()

  viewB?.onAppear()
  contentView?.pressButton()

  // viewB?.onDisappear()
  print("Will remove ViewB's reference")
  viewB = nil
  print("Removed ViewB's reference")

  contentView?.pressButton()

  print("Will remove ContentView's reference")
  contentView = nil
  print("Removed ContentView's reference")
}

当我运行上面的代码时,这是控制台输出(如您所见,没有释放模型):

Hey!
Will remove ViewB's reference
Removed ViewB's reference
Hey!
Will remove ContentView's reference
Removed ContentView's reference

在上面的示例中,我似乎完全控制了 Model 上的引用计数,但是当我检查 Xcode 中的内存图时,我可以确认 Model 正在通过 action.context 保留自己(我不确定那是什么意思):

要以最小的更改修复保留周期,您可能需要考虑使用ViewB.onDisappear 删除模型的操作分配,就像我在示例中所做的那样。当我取消注释 viewB?.onDisappear() 时,我看到了以下控制台输出:

Hey!
on ViewB's disappear
Will remove ViewB's reference
Removed ViewB's reference
Will remove ContentView's reference
*** DEALLOCATING MODEL
Removed ContentView's reference

祝你好运!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-24
    • 2015-12-18
    相关资源
    最近更新 更多