You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中MapSearch类无法触发DeInit(析构)的问题求助

解决MapSearch类无法触发deinit的问题

我一眼就看出问题所在了——你的代码里存在循环引用,导致MapSearch实例无法被ARC(自动引用计数)自动释放,所以deinit方法永远不会被调用。下面我来拆解问题原因,并给出具体的修复方案。

问题根源

你的MapSearch类里有两处强引用循环:

  1. Combine订阅的循环引用:$searchTerm的sink闭包直接强引用了self,而这个订阅被存储在self.cancellables集合中。形成了这样的循环链:MapSearch → cancellables → AnyCancellable → 闭包 → MapSearch。
  2. 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))
    }
}

修复说明

  1. 使用[weak self]打破循环:在flatMap和sink的闭包开头添加[weak self],然后用guard let self = self else确保实例仍然存在后再操作。这样闭包不再强持有MapSearch实例,ARC可以正常回收。
  2. 主动清理资源:在deinit里调用searchCompleter.cancel()取消未完成的搜索任务,同时清空cancellables集合,确保所有订阅都被取消,彻底释放资源。

现在你再运行测试,点击返回按钮后,控制台应该会输出MapSearch Deinitialized,和Test View Model Deinitialized一起出现,说明问题已经解决了。

内容的提问来源于stack exchange,提问作者kittonian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 22:12:37