Memory leak in refreshable view

I'm trying to call the refresh function in the view model from the .refreshable in a view.

I have tested it with an empty function in the viewModel and it causes a memory leak in this view

struct TopicListView: View {
  @StateObject private var viewModel: TopicListViewModel
  @State private var boardId: String
  @State private var boardName: String
   
  init(dataFetchable: DataFetchable, boardId: String, boardName: String) {
    self._viewModel = StateObject(wrappedValue: TopicListViewModel(dataFetchable: dataFetchable))
    self.boardId = boardId
    self.boardName = boardName
  }
   
  var body: some View {
    List(viewModel.topics.indices, id: \.self) { index in
      HStack {
        TopicView(title: viewModel.topics[index].title, lastPostTime: viewModel.topics[index].lastPostTime, formatter: viewModel.dateFormatter)
         
        NavigationLink(destination: PostView(dataFetchable: viewModel.dataFetchable, title: viewModel.topics[index].title, topicId: viewModel.topics[index].id)) {
           
          EmptyView()
        }
        .frame(width: 0)
        .opacity(0)
      }
      .onAppear {
        if index == viewModel.topics.count-1 {
          viewModel.page+=1
          viewModel.fetchTopics(boardId)
        }
      }
    }
    .listStyle(.plain)
    .navigationBarTitle(boardName)
    .refreshable {
      await viewModel.doNotThing()
    }
    .onAppear {
      viewModel.boardId = boardId
    }
  }
}

However, the PostView which I have linked with a NavigationLink have no issue

struct PostView: View {
  @State private var title: String
  @State private var topicId: String
  @StateObject private var viewModel: PostViewModel
   
  init(dataFetchable: DataFetchable, title: String, topicId: String) {
    self._viewModel = StateObject(wrappedValue: PostViewModel(dataFetchable: dataFetchable))
    self.topicId = topicId
    self.title = title
  }
   
  var body: some View {
    List(viewModel.posts, id: \.id) { item in
      TextblockView(textBlock: item.textBlock)
    }
    .listStyle(.plain)
    .navigationBarTitle(title)
    .navigationBarTitleDisplayMode(.inline)
    .refreshable {
      await viewModel.doNotThing()
    }
    .onAppear(perform: {
      viewModel.topicId = topicId
    })
     
  }
}

Here is the memory graph

Any ideas how to fix it?

Perhaps I don't understand your problem, but where do you see a memory leak?

The SwiftUI „runtime“ is keeping the StateObject alive. That's an intended function.

I add .navigationBarTitleDisplayMode(.inline) to the List of TopicListView and the issue magically fixed...

I was running into the same issue, and your work around of adding .navigationBarTitleDisplayMode(.inline) also worked for me :)

Seems like there's a bug somewhere on .refreshable with List that causes this retain cycle. Filed a Feedback Assistant (Radar) ticket - FB10640905

Has there been any update to this issue? I've just encountered this bug and I can't find much about it online other than this thread.

I'm trying to create a state object wrapper for Apollo iOS watch queries and was hoping to rely on deinit on ObservedObject to clean up the watcher. This bug prevents deinit from ever getting called (and of course, memory leak is bad in general).

I also encountered this bug, and it was fixed as well by adding .navigationBarTitleDisplayMode()

I've been loosing some hairs from this one, hopefully it can get fixed.

I realized now that it only works using .inline. That's a problem.

Did any of you find a way to get around it?

Here is what I would say is a much more appropriate fix for this issue—create the weak reference yourself:

.refreshable { [weak viewModel] in
      await viewModel?.doNotThing()
}

you can‘t use .searchable and .refreshable together, Otherwise, a memory leak may occur,use weak can fix this

import SwiftUI
import Observation

struct ContentView: View {
    @State var path: String = ""
    var body: some View {
        NavigationStack {
            VStack {
                NavigationLink(value: "new") {
                    Text("Hello, world!")
                }
            }
            .navigationDestination(for: String.self) { _ in
                SubContentView()
            }
        }
    }
}

struct SubContentView: View {
    @State private var viewModel: TestViewModel = TestViewModel(someString: "some string")
    @State private var searchText: String = ""
    
    var body: some View {
        ScrollView {
            LazyVStack {
                Text("\(viewModel.someString)")
            }
        }
        .searchable(text: $searchText) // 删除searchable 后 viewModel 可以正常执行
        .refreshable {
            viewModel.someString = "sadsadsa"
        }
//        .refreshable { [weak viewModel] in ✅
//            // 同时使用refreshable 和 searchable 的情况下,viewModel必须weak 处理,否则会没法deinit
//            viewModel?.someString = "sadsadsa"
//        }
        .padding()
    }
}

@Observable
class TestViewModel {
    var someString: String = "some string"
    init(someString: String) {
        self.someString = someString
        print("init")
    }
    deinit {
        print("deinit")
    }
}
Memory leak in refreshable view
 
 
Q