Swift 提供当前 CLLocation 的函数(闭包不是委托)

Swift func to provide current CLLocation (with closures NOT delegates)

我想创建一个这样的 Swift 函数:

static func queryLocation(desiredAccuracy: CLLocationAccuracy, completion: @escaping (Result<CLLocation, Error>)->())

这个函数会尽可能简单。作为调用者,您不需要保留位置管理器,也不需要实现任何委托方法。只需询问位置并实现完成处理程序即可。

(删除原始源代码并添加为答案,因为它确实有效 - 我只是在正确调用它时遇到问题)

对不起大家。调用方法有问题。 OneTimeLocation class 实际上按预期工作。 (也可作为 Swift 包装:https://github.com/hoereth/CoreLocationUtils/blob/main/Sources/CoreLocationUtils/OneTimeLocation.swift

import Foundation
import CoreLocation

public class OneTimeLocation : NSObject, CLLocationManagerDelegate {
    public enum LocationError : Error {
        case denied
        case restricted
        case timeout
        case unknown
    }
    
    let manager: CLLocationManager
    let completion: (Result<CLLocation, Error>)->()
    let timeout: TimeInterval

    fileprivate static let instancesQueue = DispatchQueue(label: "OneTimeLocation.instances")
    fileprivate static var instances = Set<OneTimeLocation>()
    
    /// Will either find you the current location or produce an error.
    /// - Parameters:
    ///   - desiredAccuracy: see CLLocationManager.desiredAccuracy
    ///   - timeout: Applies to actual finding a location. Dialogs are presented without timeout.
    public static func queryLocation(desiredAccuracy: CLLocationAccuracy, timeout: TimeInterval, completion: @escaping (Result<CLLocation, Error>)->()) {
        let oneTimeLocation = OneTimeLocation(desiredAccuracy: desiredAccuracy, completion: completion, timeout: timeout)
        oneTimeLocation.manager.delegate = oneTimeLocation
        
        switch CLLocationManager.authorizationStatus() {
        case .authorizedAlways, .authorizedWhenInUse:
            instancesQueue.sync {
                _ = instances.insert(oneTimeLocation)
                oneTimeLocation.manager.startUpdatingLocation()
                DispatchQueue.main.asyncAfter(deadline: .now() + timeout) {
                    oneTimeLocation.manager.stopUpdatingLocation()
                    completion(Result.failure(LocationError.timeout))
                    oneTimeLocation.removeInstance()
                }
            }
        case .notDetermined:
            instancesQueue.sync {
                _ = instances.insert(oneTimeLocation)
                oneTimeLocation.manager.requestWhenInUseAuthorization()
            }
        case .denied:
            completion(Result.failure(LocationError.denied))
        case .restricted:
            completion(Result.failure(LocationError.restricted))
        @unknown default:
            completion(Result.failure(LocationError.unknown))
        }
    }
    
    fileprivate init(desiredAccuracy: CLLocationAccuracy, completion: @escaping (Result<CLLocation, Error>)->(), timeout: TimeInterval) {
        self.manager = CLLocationManager()
        self.manager.desiredAccuracy = desiredAccuracy
        self.completion = completion
        self.timeout = timeout
    }
    
    public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if (locations.count > 0) {
            self.completion(Result.success(locations[0]))
        } else {
            self.completion(Result.failure(LocationError.unknown))
        }
        self.manager.stopUpdatingLocation()
        self.removeInstance()
    }
    
    public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        self.completion(Result.failure(error))
        self.manager.stopUpdatingLocation()
        self.removeInstance()
    }
    
    public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {

        switch CLLocationManager.authorizationStatus() {
        case .authorizedAlways, .authorizedWhenInUse:
            self.manager.startUpdatingLocation()
            DispatchQueue.main.asyncAfter(deadline: .now() + timeout) {
                self.manager.stopUpdatingLocation()
                self.completion(Result.failure(LocationError.timeout))
                self.removeInstance()
            }
        case .notDetermined:
            break;
        case .denied:
            completion(Result.failure(LocationError.denied))
            self.removeInstance()
        case .restricted:
            completion(Result.failure(LocationError.restricted))
            self.removeInstance()
        @unknown default:
            completion(Result.failure(LocationError.unknown))
            self.removeInstance()
        }
    }
    
    fileprivate func removeInstance() {
        Self.instancesQueue.sync {
            _ = OneTimeLocation.instances.remove(self)
        }
    }
}