I’m making an app with SwiftUI and UIkit, I use UIkit for the main app controller and navigation, and I use SwiftUI for app design.
The app works very well, but I’m worried about the memory leaks. This is because the ViewModels I use to pass data between views don’t call desinit whene the view disappears. I know that in SwiftUI views are not disposed immediately, but since I’m using UIKit to navigate I don’t know what the problem is.
//The ViewModel for each user fetched
internal class UserViewModel: ObservableObject, Identifiable {
//MARK: - Propeties var currentListener: ListenerRegistration?
@Published var request: Request?
@Published var user: User
init(user: User) {
self.user = user
getRequest()
fetchAdmins()
}
deinit {
//Dosnt get called removeListener()
}
func getRequest() {
guard let uid = Auth.auth().currentUser?.uid else {return}
guard let id = id else {return}
self.currentListener = Collections.requests(id).document(uid).addSnapshotListener { snapshot, error in
if let error = error {
print(error.localizedDescription)
return
}
if ((snapshot?.exists) != nil) {
if let request = try? snapshot!.data(as: Request.self) {
DispatchQueue.main.async {
self.request = request
}
}
}
}
}
func removeListener() {
self.currentListener?.remove()
}
}
}
//The ViewModel to fetch all the users ViewModels
class UsersViewModel: ObservableObject {
@Published var users = [UserViewModel]()
func fetch() {
DispatchQueue.global(qos: .background).async {
Collections.users.getDocuments(completion: { snapshot, err in
guard let documents = snapshot?.documents else { return } let users = documents.compactMap({ try? $0.data(as: User.self) })
users.forEach { user in
let vm = UserViewModel(user: user)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.users.append(vm)
}
}
})
}
} }
//Show the users cells with the ViewModel
struct HomeView: View {
@ObservedObject var usersViewModels: UsersViewModel
//MARK: - Init
init() {
self.usersViewModels = UsersViewModel()
}
var body: some View {
ListView(content: {
ForEach(usersViewModels) { usersViewModel in
UserCell(viewModel: usersViewModel).id(user.id)
}
})
}
}
This is how I navigate between controllers and views of my app. I don’t use NavigationLinks:
public static func push<Content: View>(view: Content) {
DispatchQueue.main.async {
guard let tabBarController = UIApplication.rootViewController as? UITabBarController, let navigationController = tabBarController.selectedViewController as? UINavigationController else { return nil }
if let navigationController = UIApplication.getCurrentNavigationController() {
navigationController.pushViewController(HostingController(content: view), animated: true)
}
}
}
Does anyone know if this method that I am using to navigate can cause me memory problems? And you know why my app doesn’t reduce its memory every time I close a window, it just increases more and more.
2
Answers
The disappearing does not mean it is no longer in memory.
It looks like you keep pushing them onto the navigation stack which increases their retain count.
You’ve got a memory leak here:
View structs must not init objects because the View struct is recreated every state change thus the object is being constantly init.
SwiftUI is all about taking advantage of value semantics, try to use
@State
with value types (or group them in a struct) in theView
struct for your view data.Model data structs go in a singleton
ObservableObject
supplied to the Views using.environmentObject
.