SwiftUI中MapSearch类无法触发DeInit(析构)的问题求助
解决MapSearch类无法触发deinit的问题
我一眼就看出问题所在了——你的代码里存在循环引用,导致MapSearch实例无法被ARC(自动引用计数)自动释放,所以deinit方法永远不会被调用。下面我来拆解问题原因,并给出具体的修复方案。
问题根源
你的MapSearch类里有两处强引用循环:
- Combine订阅的循环引用:
$searchTerm的sink闭包直接强引用了self,而这个订阅被存储在self.cancellables集合中。形成了这样的循环链:MapSearch→cancellables→AnyCancellable→ 闭包 →MapSearch。 - Future闭包的强引用:
searchTermToResults方法里的Future闭包也强引用了self,进一步加固了循环,让ARC无法回收实例。
另外,MKLocalSearchCompleter如果在后台还有未完成的任务,也可能间接持有对MapSearch的引用,但核心原因还是Combine闭包的循环引用。
修复方案
我们需要在所有捕获self的Combine闭包里使用[weak self](优先用weak而非unowned,避免野指针风险),打破循环引用。同时在deinit里主动清理资源,确保万无一失。
修复后的MapSearch.swift代码
import Combine import CoreLocation import Foundation import MapKit /// Uses MapKit and CoreLocation to auto-complete an address class MapSearch: NSObject, ObservableObject { @Published var countryName: String = "United States" @Published var locationResults: [MKLocalSearchCompletion] = [] @Published var searchTerm = "" private var cancellables: Set<AnyCancellable> = [] private var searchCompleter = MKLocalSearchCompleter() private var currentPromise: ((Result<[MKLocalSearchCompletion], Error>) -> Void)? override init() { super.init() searchCompleter.delegate = self searchCompleter.resultTypes = MKLocalSearchCompleter.ResultType([.address]) $searchTerm .debounce(for: .seconds(0.2), scheduler: RunLoop.main) .removeDuplicates() .flatMap({ [weak self] (currentSearchTerm) in guard let self = self else { return Future { $0(.success([])) } } return self.searchTermToResults(searchTerm: currentSearchTerm, countryName: self.countryName) }) .sink(receiveCompletion: { (_) in }, receiveValue: { [weak self] (results) in guard let self = self else { return } // Show country specific results self.locationResults = results.filter { $0.subtitle.contains(self.countryName) } }) .store(in: &cancellables) print("MapSearch Initialized") } deinit { // 主动清理资源 searchCompleter.cancel() cancellables.removeAll() print("MapSearch Deinitialized") } func searchTermToResults(searchTerm: String, countryName: String) -> Future<[MKLocalSearchCompletion], Error> { Future { [weak self] promise in guard let self = self else { promise(.success([])) return } self.searchCompleter.queryFragment = searchTerm self.currentPromise = promise } } } extension MapSearch: MKLocalSearchCompleterDelegate { func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { currentPromise?(.success(completer.results)) } func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) { // deal with the error here, it will finish the Combine publisher stream currentPromise?(.failure(error)) } }
修复说明
- 使用
[weak self]打破循环:在flatMap和sink的闭包开头添加[weak self],然后用guard let self = self else确保实例仍然存在后再操作。这样闭包不再强持有MapSearch实例,ARC可以正常回收。 - 主动清理资源:在
deinit里调用searchCompleter.cancel()取消未完成的搜索任务,同时清空cancellables集合,确保所有订阅都被取消,彻底释放资源。
现在你再运行测试,点击返回按钮后,控制台应该会输出MapSearch Deinitialized,和Test View Model Deinitialized一起出现,说明问题已经解决了。
内容的提问来源于stack exchange,提问作者kittonian
相关产品推荐
相关产品推荐

