SwiftUI - ObservedObject 永远不会被释放

问题描述 投票:0回答:3

我的应用程序正在泄漏模型对象,因为对象正在保留保留视图本身的闭包。 最好通过例子来说明。 在下面的代码中,在

Model
消失后,
ContentView
不会被释放。

//
// 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 是一个结构体,因此在闭包中引用它应该是安全的,对吧?

ios macos swiftui
3个回答
2
投票

啊@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
保留自身(我'我不确定这是什么意思):

Model's retain cycle1

要以最小的更改修复保留周期,您可能需要考虑使用

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

祝你好运!


1
投票

Model
不是一个结构体,它是一个
ObservableObject
,其类型为
AnyObject
,它是一个
Object

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

.onAppear { [weak model] }

认为你也可以捕获模型,以防问题本身出现

.onAppear { [model] }

0
投票

我也遇到过同样的问题。我想从我的

View
打开
ObservableObject
上的 URL。看起来当你将
View
的方法传递到
ObservableObject
的闭包中时,你实际上创建了循环引用(说实话,我不知道为什么,因为
View
是结构体,但它必须是一些内部 SwiftUI 机制)。


我为解决问题所做的:我用

PassthroughSubject
替换了闭包,它使用
ObservableObject
将值从
View
发送到我的
onReceive(_:perform:)

在您的特定情况下,它可能看起来像这样

class Model: ObservableObject {
    
    let action = PassthroughSubject<Void, Never>()
    
    deinit {
        print("MODEL DEINIT")
    }

    func triggerAction() {
        action.send()
    }

}

struct ViewB: View {

    @ObservedObject var model: Model
    
    var body: some View {
        Color.red.frame(width: 20, height: 20)
            .onReceive(model.action) { bAction() }
    }
    
    private func bAction() {
        print("Hey!")
    }
    
}
© www.soinside.com 2019 - 2024. All rights reserved.