简体   繁体   中英

SwiftUI - ObservedObject is never deallocated

My app is leaking model objects because it the objects are keeping closures that are retaining the view itself. It's better to show by an example. In the code below, Model is not deallocated after the ContentView disappears.

//
// 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")
    }
}

I'm not sure why there's some kind of retain cycle occurring here. Since View is a struct, referencing it in a closure should be safe, right?

Model is not a struct, it is an ObservableObject which is of type AnyObject which is an Object

you should apply weak to in the capture list for .onAppear

.onAppear { [weak model] }

I think you could also just capture model incase its self that the issue is on

.onAppear { [model] }

Ahoy @msmialko, while I can't give much reasoning for what I've observed, hopefully this will be a step in the right direction.

I decided to remove SwiftUI's memory management from the equation and tested with simple value and reference types:

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")
}

When I ran the code above, this was the console output (no deallocation of Model, as you observed):

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

In the above example it looks like I'm in complete control of the reference count on Model , however when I inspected the memory graph in Xcode, I could confirm that Model was retaining itself via action.context (I'm not sure what that means):

模型的保留周期1

To fix the retain cycle with minimal changes, you might want to consider removing Model's action assignment using ViewB.onDisappear as I've done in my example. When I uncommented viewB?.onDisappear() then I saw the following console output:

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

Good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM