修复定位的回调问题

This commit is contained in:
joywayer
2026-08-07 12:18:44 +08:00
parent 3f8b382ad3
commit 1203957924
12 changed files with 637 additions and 180 deletions
+224 -41
View File
@@ -32,93 +32,276 @@ public struct LocationPayload: Sendable {
public let cityCode: String
public let country: String
public let district: String
public let latitude: String
public let longitude: String
/// **JSON ** `%f` 6
/// `Decimal` `Double` init §3.2 B
public let latitude: Decimal
public let longitude: Decimal
public let province: String
public let street: String
}
public enum LocationError: Error, Sendable {
case sdkNotLinked
case authorizationDenied
case timeout
case underlying(any Error)
#if canImport(AMapLocationKit)
extension LocationPayload {
/// msext `@{...}`
///
/// msext
/// ```objc
/// @try{ [_bridge callHandler:@"getlocationinfo" data:@{
/// @"address":regeocode.formattedAddress, @"city":regeocode.city, ... }]; }
/// @catch (NSException * e) { NSLog(...); }
/// ```
/// `@{}` ** value nil NSException**
/// `@try/@catch` 9 nil
/// ** getlocationinfo H5**
///
/// `?? ""`
/// H5 msext
/// nil nil
nonisolated init?(location: CLLocation, reGeocode: AMapLocationReGeocode) {
guard let address = reGeocode.formattedAddress,
let city = reGeocode.city,
let cityCode = reGeocode.citycode,
let country = reGeocode.country,
let district = reGeocode.district,
let province = reGeocode.province,
let street = reGeocode.street
else { return nil }
self.address = address
self.city = city
self.cityCode = cityCode
self.country = country
self.district = district
// **** `%f` 6
//
// docs/H5-Native-Contract.md §3.2 Bmsext WVJB
// `gameController.m:2528` / `NewRootVC.m:2132`
// `[NSString stringWithFormat:@"%f", ...]` ****
// UIWebView / JSContext `RootVC.m:1998` / `fourviewVC.m:1541`
// `\"latitude\":%f` ** JSON ** H5
// jinxianmahjong 05_Func.js:2029-2031
// `"latitude":28.623546` `errorCode` WVJB
//
//
// `%f` `Decimal` 6
// JSONSerialization `28.636486` Double 17
// `28.636486000000001` JS parse IEEE754 doubleH5
// JSON
let latText = String(format: "%f", location.coordinate.latitude)
let lonText = String(format: "%f", location.coordinate.longitude)
self.latitude = Decimal(string: latText) ?? Decimal(location.coordinate.latitude)
self.longitude = Decimal(string: lonText) ?? Decimal(location.coordinate.longitude)
self.province = province
self.street = street
}
}
#endif
/// **** failure success
/// msext `completionBlock` requestOnce
public enum LocationOutcome: Sendable {
/// H5 `getlocationinfo({errorCode:12, errorMsg:""})`
case failure
/// H5 `getlocationinfo(<9 >)`
case success(LocationPayload)
}
@MainActor
public final class LocationService {
public static let shared = LocationService()
/// `lobby` / `subGame`
private let owner: String
public init() {}
/// ****
/// msext `NewRootVC.m:1568` / `gameController.m:1230`
/// `[[AMapLocationManager alloc] init]` manager + completionBlock +
/// delegate manager
/// 1. `requestLocationWithReGeocode:completionBlock:`
///
/// completionBlock continuation H5
/// 2. `stopUpdatingLocation` cancel
/// `backgameData`
/// 3. `delegate = nil` manager ****
public init(owner: String = "?") {
self.owner = owner
#if canImport(AMapLocationKit)
// msext `configLocationManager` `setDelegate:self` ****
// `amapLocationManager:doRequireLocationAuth:` delegate
// init shim lazy
_ = delegateShim
#endif
}
#if canImport(AMapLocationKit)
/// msext `configLocationManager`
private let manager: AMapLocationManager = {
let m = AMapLocationManager()
m.desiredAccuracy = kCLLocationAccuracyHundredMeters
m.pausesLocationUpdatesAutomatically = false
m.locationTimeout = 6
m.reGeocodeTimeout = 3
m.locatingWithReGeocode = true
return m
}()
/// `startlocation` type == 1 delegate
/// msext `configLocationManager` `setDelegate:self` VC
/// LocationService NSObject
private lazy var delegateShim: LocationDelegateShim = {
let shim = LocationDelegateShim(owner: owner)
manager.delegate = shim
return shim
}()
#endif
/// requestOnce
private var requestSeq: UInt64 = 0
/// + msext locAction + completionBlock
public func requestOnce(caller: String = "?") async throws -> LocationPayload {
/// + ** msext `initCompleteBlock` completionBlock**
/// `gameController.m:2493-2545` / `NewRootVC.m:2099-2150`
///
/// ```objc
/// if (error) {
/// callHandler getlocationinfo {errorCode:12,...} //
/// if (error.code == AMapLocationErrorLocateFailed) return;
/// }
/// if (location) { if (regeocode) {
/// if (regeocode.formattedAddress != nil)
/// callHandler getlocationinfo <9 > //
/// }}
/// ```
///
/// ** return** locateFailed CLLocation
/// / error **** location msext
/// errorCode 12H5
///
/// `async throws -> LocationPayload`
/// error return H5 errorCode 12
/// `C_Player.SetLocationInfo({errorCode:12})` addr
/// /CPU
/// msext outcome
///
/// - Parameter emit: MainActor 0 / 1 / 2
public func requestOnce(caller: String = "?",
emit: @escaping @MainActor (LocationOutcome) -> Void) {
#if canImport(AMapLocationKit)
requestSeq += 1
let seq = requestSeq
locLog.debug("[\(caller, privacy: .public)] requestOnce #\(seq, privacy: .public) 发起(shared manager id=\(String(describing: ObjectIdentifier(self.manager)), privacy: .public)")
return try await withCheckedThrowingContinuation { (cont: CheckedContinuation<LocationPayload, Error>) in
let accepted = manager.requestLocation(withReGeocode: true) { loc, regeo, err in
locLog.debug("[\(caller, privacy: .public)] requestOnce #\(seq, privacy: .public) completionBlock 回来:loc=\(loc != nil, privacy: .public) regeo=\(regeo != nil, privacy: .public) err=\(err?.localizedDescription ?? "nil", privacy: .public)")
diagLog("[\(caller)] requestOnce #\(seq) 发起(owner=\(owner) manager=\(UInt(bitPattern: ObjectIdentifier(self.manager).hashValue))")
let t0 = Date()
// completionBlock 线 msext
let accepted = manager.requestLocation(withReGeocode: true) { loc, regeo, err in
let ms = Int(Date().timeIntervalSince(t0) * 1000)
diagLog("[\(caller)] requestOnce #\(seq) completionBlock 回来(耗时 \(ms)ms):loc=\(loc != nil) regeo=\(regeo != nil) err=\(err?.localizedDescription ?? "nil")")
MainActor.assumeIsolated {
if let err = err as NSError? {
// msext 12 errorCodehandler
emit(.failure) //
if err.code == AMapLocationErrorCode.locateFailed.rawValue {
cont.resume(throwing: LocationError.authorizationDenied)
} else {
cont.resume(throwing: LocationError.underlying(err))
diagLog("[\(caller)] requestOnce #\(seq) locateFailed,按 msext 就此结束")
return
}
diagLog("[\(caller)] requestOnce #\(seq) 有 error 但非 locateFailed,按 msext 继续尝试补发真实定位")
}
// msext location / regeocode / formattedAddress
guard let loc, let regeo, regeo.formattedAddress != nil else {
diagLog("[\(caller)] requestOnce #\(seq) 无可用逆地理(loc=\(loc != nil) regeo=\(regeo != nil)),按 msext 不发 9 字段")
return
}
guard let loc, let regeo else {
cont.resume(throwing: LocationError.timeout)
guard let payload = LocationPayload(location: loc, reGeocode: regeo) else {
diagLog("[\(caller)] requestOnce #\(seq) 逆地理有 nil 字段,按 msext@{} 抛 NSException)整条不发")
return
}
cont.resume(returning: LocationPayload(
address: regeo.formattedAddress ?? "",
city: regeo.city ?? "",
cityCode: regeo.citycode ?? "",
country: regeo.country ?? "",
district: regeo.district ?? "",
latitude: String(format: "%f", loc.coordinate.latitude),
longitude: String(format: "%f", loc.coordinate.longitude),
province: regeo.province ?? "",
street: regeo.street ?? ""
))
emit(.success(payload)) //
}
// AMapLocationManager.hRequest NO
// completionBlock continuation H5
// getlocationinfo errorCode 12
locLog.debug("[\(caller, privacy: .public)] requestOnce #\(seq, privacy: .public) requestLocation 返回 accepted=\(accepted, privacy: .public)")
}
// AMapLocationManager.hRequestmsext
// H5 continuation
// 便
if !accepted {
diagLog("[\(caller)] requestOnce #\(seq) requestLocation 返回 NO(未挂上请求),按 msext 静默")
}
#else
locLog.error("[\(caller, privacy: .public)] requestOnce: AMapLocationKit 未链接,抛 sdkNotLinked")
throw LocationError.sdkNotLinked
diagLog("[\(caller)] requestOnce: AMapLocationKit 未链接,按 msext 无 SDK 场景不回 H5")
#endif
}
/// `startlocation` type == 1
/// msext `NewRootVC.m:414` / `gameController.m:537`
/// `if (tempinfo == 1) [self.locationManager startUpdatingLocation];`
/// delegate `amapLocationManager:didUpdateLocation:reGeocode:`
/// `getlocationinfo`msext `gameController.m:2551-2560` `reGeocode`
/// `formattedAddress` `didFailWithError` msext NSLog H5
///
/// - Parameter emit:
public func startContinuous(caller: String = "?",
emit: @escaping @MainActor (LocationOutcome) -> Void) {
#if canImport(AMapLocationKit)
diagLog("[\(caller)] startContinuous 发起(owner=\(owner)")
delegateShim.onUpdate = emit
delegateShim.caller = caller
manager.startUpdatingLocation()
#else
diagLog("[\(caller)] startContinuous: AMapLocationKit 未链接,按 msext 无 SDK 场景不回 H5")
#endif
}
/// msext gameController.m:2473-2478 cleanUpAction
public func stop(caller: String = "?") {
#if canImport(AMapLocationKit)
// AMapLocationManager.hstopUpdatingLocation cancel
// shared manager ****
locLog.debug("[\(caller, privacy: .public)] stop()stopUpdatingLocation + delegate=nil(会取消所有在飞的单次定位)")
// `stopUpdatingLocation` cancel manager
// manager msext
// cleanUpAction manager
diagLog("[\(caller)] stop()stopUpdatingLocation + delegate=nilowner=\(owner)")
manager.stopUpdatingLocation()
manager.delegate = nil
#endif
}
}
#if canImport(AMapLocationKit)
/// `AMapLocationManagerDelegate`
/// msext NewRootVC / gameController
/// LocationService 宿
@MainActor
private final class LocationDelegateShim: NSObject, AMapLocationManagerDelegate {
var onUpdate: (@MainActor (LocationOutcome) -> Void)?
var caller: String = "?"
private let owner: String
init(owner: String) {
self.owner = owner
super.init()
}
/// msext `gameController.m:2551-2560` reGeocode formattedAddress
/// 9
nonisolated func amapLocationManager(_ manager: AMapLocationManager!,
didUpdate location: CLLocation!,
reGeocode: AMapLocationReGeocode!) {
// AMap Sendable Sendable
// LocationPayload MainActor Swift 6 reGeocode
guard let location, let reGeocode, reGeocode.formattedAddress != nil else {
diagLog("[持续定位] 更新但无可用逆地理,按 msext 不发")
return
}
guard let payload = LocationPayload(location: location, reGeocode: reGeocode) else {
diagLog("[持续定位] 逆地理有 nil 字段,按 msext@{} 抛 NSException)整条不发")
return
}
MainActor.assumeIsolated {
diagLog("[\(caller)] 持续定位 → getlocationinfo city=\(payload.city)")
onUpdate?(.success(payload))
}
}
/// msext `gameController.m:2547` ** H5**
nonisolated func amapLocationManager(_ manager: AMapLocationManager!,
didFailWithError error: (any Error)!) {
diagLog("[持续定位 owner=\(owner)] didFailWithError: \(error?.localizedDescription ?? "nil")msext 同款:不通知 H5")
}
}
#endif