From a3679aba7dccc3936dc996a85a6538b5e689ff9b Mon Sep 17 00:00:00 2001 From: joywayer Date: Tue, 23 Jun 2026 01:03:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20SDK=20=E7=9C=9F=E5=AE=9E?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E5=90=8E=E7=9A=84=20Swift=20=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E9=94=99=E8=AF=AF=20+=20Xcode=20=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实测三个 SDK(微信 / 高德 / 七牛)链接进 Target 后,暴露出 9 个 Swift 编译错误,逐一修正: 代码侧(5 个 Swift 文件): - AMapWrapper:updatePrivacyShow / updatePrivacyAgree 是 AMapLocationManager 类方法(since 2.8.0),不在 AMapServices 上;改为 AMapLocationManager.* 并补 import AMapLocationKit - BackGameDataHandler:删 `WXApi.delegate = nil`。新版 SDK 2.x 无 static delegate API,delegate 由调用方逐次传给 handleOpenURL/sendAuthReq 并被 SDK 弱引用;WeChatManager singleton 长生命周期不析构,无需 cleanup - QiniuConfig / QiniuTokenSigner:所有 static 成员标 nonisolated,让 actor QiniuUploader 可直接调,无需 await MainActor.run - QiniuUploader:QNUploadManager() 在七牛 SDK 8.x 已 kQNDeprecated,改用 initWithConfiguration: + defaultConfigurationV2 工程侧(pbxproj,Xcode UI 自动写入): - 新 Frameworks group 容纳 Vendor/WechatSDK / Vendor/AMap 引用 - WechatOpenSDK-NoPay.xcframework:Embed & Sign(动态库) - AMapFoundationKit / AMapLocationKit:Do Not Embed(静态 fat .framework) BuildProject 验证:Swift 全部编译通过;剩余 AMap fat framework arch 冲突 (M Mac iOS-simulator)是工程配置选择,下个 commit 处理。 Co-Authored-By: Claude Opus 4.7 --- ylgamehall.xcodeproj/project.pbxproj | 44 +++++++++++++++++++ .../Bridge/Handlers/BackGameDataHandler.swift | 15 +++---- ylgamehall/Source/Network/QiniuConfig.swift | 14 +++--- .../Source/Network/QiniuTokenSigner.swift | 5 ++- ylgamehall/Source/Network/QiniuUploader.swift | 12 ++++- ylgamehall/Source/SDK/AMap/AMapWrapper.swift | 15 ++++--- 6 files changed, 79 insertions(+), 26 deletions(-) diff --git a/ylgamehall.xcodeproj/project.pbxproj b/ylgamehall.xcodeproj/project.pbxproj index 3ef6146..d54d3f8 100644 --- a/ylgamehall.xcodeproj/project.pbxproj +++ b/ylgamehall.xcodeproj/project.pbxproj @@ -9,9 +9,30 @@ /* Begin PBXBuildFile section */ 1A2954622FE840DA009E703E /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 1A2954612FE840DA009E703E /* ZIPFoundation */; }; 1A2954DA2FE97D61009E703E /* Qiniu in Frameworks */ = {isa = PBXBuildFile; productRef = 1A2954D92FE97D61009E703E /* Qiniu */; }; + 1A2954DF2FE99C2C009E703E /* WechatOpenSDK-NoPay.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A2954DE2FE99C2C009E703E /* WechatOpenSDK-NoPay.xcframework */; }; + 1A2954E02FE99C2C009E703E /* WechatOpenSDK-NoPay.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1A2954DE2FE99C2C009E703E /* WechatOpenSDK-NoPay.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 1A2954E32FE99C53009E703E /* AMapFoundationKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A2954E22FE99C53009E703E /* AMapFoundationKit.framework */; }; + 1A2954E62FE99C63009E703E /* AMapLocationKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A2954E52FE99C63009E703E /* AMapLocationKit.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + 1A2954E12FE99C2C009E703E /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 1A2954E02FE99C2C009E703E /* WechatOpenSDK-NoPay.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 1A2954DE2FE99C2C009E703E /* WechatOpenSDK-NoPay.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = "WechatOpenSDK-NoPay.xcframework"; path = "Vendor/WechatSDK/WechatOpenSDK-NoPay.xcframework"; sourceTree = ""; }; + 1A2954E22FE99C53009E703E /* AMapFoundationKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AMapFoundationKit.framework; path = Vendor/AMap/AMapFoundationKit.framework; sourceTree = ""; }; + 1A2954E52FE99C63009E703E /* AMapLocationKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AMapLocationKit.framework; path = Vendor/AMap/AMapLocationKit.framework; sourceTree = ""; }; 1A714BD22FE8047A00913338 /* ylgamehall.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ylgamehall.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -42,17 +63,31 @@ buildActionMask = 2147483647; files = ( 1A2954622FE840DA009E703E /* ZIPFoundation in Frameworks */, + 1A2954E62FE99C63009E703E /* AMapLocationKit.framework in Frameworks */, + 1A2954E32FE99C53009E703E /* AMapFoundationKit.framework in Frameworks */, 1A2954DA2FE97D61009E703E /* Qiniu in Frameworks */, + 1A2954DF2FE99C2C009E703E /* WechatOpenSDK-NoPay.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1A2954DB2FE99B2F009E703E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1A2954E52FE99C63009E703E /* AMapLocationKit.framework */, + 1A2954E22FE99C53009E703E /* AMapFoundationKit.framework */, + 1A2954DE2FE99C2C009E703E /* WechatOpenSDK-NoPay.xcframework */, + ); + name = Frameworks; + sourceTree = ""; + }; 1A714BC92FE8047A00913338 = { isa = PBXGroup; children = ( 1A714BD42FE8047A00913338 /* ylgamehall */, + 1A2954DB2FE99B2F009E703E /* Frameworks */, 1A714BD32FE8047A00913338 /* Products */, ); sourceTree = ""; @@ -75,6 +110,7 @@ 1A714BCE2FE8047A00913338 /* Sources */, 1A714BCF2FE8047A00913338 /* Frameworks */, 1A714BD02FE8047A00913338 /* Resources */, + 1A2954E12FE99C2C009E703E /* Embed Frameworks */, ); buildRules = ( ); @@ -159,6 +195,10 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = NX5W3B4QP3; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Vendor/AMap", + ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ylgamehall/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -192,6 +232,10 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = NX5W3B4QP3; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Vendor/AMap", + ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ylgamehall/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; diff --git a/ylgamehall/Source/Bridge/Handlers/BackGameDataHandler.swift b/ylgamehall/Source/Bridge/Handlers/BackGameDataHandler.swift index 798ad6d..16b3194 100644 --- a/ylgamehall/Source/Bridge/Handlers/BackGameDataHandler.swift +++ b/ylgamehall/Source/Bridge/Handlers/BackGameDataHandler.swift @@ -19,7 +19,8 @@ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // SDK 清理钩子状态(每接入新 SDK 都必须确认或新增): // -// ✅ Phase 4.E 微信:`WXApi.delegate = nil`(已加,canImport 守卫,Xcode Embed 后激活) +// ✅ Phase 4.E 微信:新版 SDK(2.x)无 static delegate API,逐次调用 handleOpenURL/sendAuthReq +// 时传入并被 SDK 弱引用;WeChatManager singleton 长生命周期,子游戏 pop 不析构,无需清理 // ✅ Phase 5 高德定位:`LocationService.shared.stop()`(已加,canImport 守卫, // Xcode Embed AMapLocationKit 后激活) // ⏳ Phase 3.B/C/D 录音 / 七牛上传:`AudioRecorder.shared.cancel()` 待 opencore-amr @@ -31,10 +32,6 @@ import Foundation -#if canImport(WechatOpenSDK) -import WechatOpenSDK -#endif - public enum BackGameDataHandler { public static func register(on bridge: any BridgeProtocol) { @@ -52,10 +49,10 @@ public enum BackGameDataHandler { // 后台任务残留),与 msext gameController.m:691-709 + cleanUpAction 等价。 AudioPlayer.shared.stopAllBackground() - #if canImport(WechatOpenSDK) - // msext gameController.m:699 等价:清 WXApi delegate 防止子游戏回调指向已销毁 VC - WXApi.delegate = nil - #endif + // 微信 SDK 新版(2.x)delegate 由 handleOpenURL: / sendAuthReq:...:delegate: + // 逐次调用时传入并被 SDK 弱引用,无 static delegate 可清。WeChatManager + // 作为 shared singleton 长生命周期持有,子游戏 pop 不会让它析构,无悬挂回调风险。 + // 旧 msext gameController.m:699 的 [WXApi setDelegate:nil] 是 1.x SDK API,新版已删除。 #if canImport(AMapLocationKit) // msext gameController.m:2473-2478 cleanUpAction 等价 diff --git a/ylgamehall/Source/Network/QiniuConfig.swift b/ylgamehall/Source/Network/QiniuConfig.swift index 9fbe68b..27ff673 100644 --- a/ylgamehall/Source/Network/QiniuConfig.swift +++ b/ylgamehall/Source/Network/QiniuConfig.swift @@ -10,26 +10,28 @@ import Foundation +/// 七牛云配置常量。整体 nonisolated,使得 QiniuUploader(actor)和其它非 MainActor +/// 上下文可直接读取这些 immutable static 常量,无需 await MainActor.run。 public enum QiniuConfig { /// 七牛 AccessKey(沿用 msext QiniuConfig.m:12) - public static let accessKey = "dQbQLUm1jIuL9PEq4jd6VKB-6pPxPEdg7le9KeBm" + public nonisolated static let accessKey = "dQbQLUm1jIuL9PEq4jd6VKB-6pPxPEdg7le9KeBm" /// 七牛 SecretKey(沿用 msext QiniuConfig.m:13)— HMAC-SHA1 签 putPolicy 用 - public static let secretKey = "RCZpwLhAPoQ2sQQyWXzMJc7Po2MyZWfUJeW4Jmfq" + public nonisolated static let secretKey = "RCZpwLhAPoQ2sQQyWXzMJc7Po2MyZWfUJeW4Jmfq" /// 存储空间名(沿用 msext QiniuConfig.m:16)— putPolicy scope 用 - public static let bucketName = "iosaudio" + public nonisolated static let bucketName = "iosaudio" /// CDN 默认域名(沿用 msext qiniudomain/ 渠道注入目录名,已生产生效)。 /// msext fallback 是 `iosaudio.daoqi8888.cn`,但实际渠道注入覆盖为 `daoqi88`; /// 新外壳直接沿用渠道注入值。 - public static let cdnDomain = "iosaudio.daoqi88.cn" + public nonisolated static let cdnDomain = "iosaudio.daoqi88.cn" /// 录音 key 模板前缀(msext QiniuConfig.m:22 为空字符串) - public static let recordingDirectory = "" + public nonisolated static let recordingDirectory = "" /// 拼访问 URL:`http://{cdnDomain}/{key}`(msext QiniuManager.m:197 等价) - public static func publicURL(forKey key: String) -> String { + public nonisolated static func publicURL(forKey key: String) -> String { "http://\(cdnDomain)/\(key)" } } diff --git a/ylgamehall/Source/Network/QiniuTokenSigner.swift b/ylgamehall/Source/Network/QiniuTokenSigner.swift index 0d001b4..d88726a 100644 --- a/ylgamehall/Source/Network/QiniuTokenSigner.swift +++ b/ylgamehall/Source/Network/QiniuTokenSigner.swift @@ -17,13 +17,14 @@ import Foundation import CryptoKit +/// 七牛上传 token 自签。整体 nonisolated,actor 内可直接调(不需要 await MainActor.run)。 public enum QiniuTokenSigner { /// 生成上传 token。 /// - Parameters: /// - key: 上传文件 key(可空:scope = bucket,allow override;非空:scope = bucket:key) /// - expiresIn: token 有效时长(默认 3600 秒) - public static func uploadToken(key: String? = nil, expiresIn: TimeInterval = 3600) -> String { + public nonisolated static func uploadToken(key: String? = nil, expiresIn: TimeInterval = 3600) -> String { // 1. 拼 scope let scope: String if let key, !key.isEmpty { @@ -59,7 +60,7 @@ public enum QiniuTokenSigner { /// Base64URL: 标准 Base64 把 + → -、/ → _、去掉末尾 = /// 与七牛 SDK `QNUrlSafeBase64.encodeData:` 等价 - private static func base64URLEncode(_ data: Data) -> String { + private nonisolated static func base64URLEncode(_ data: Data) -> String { data.base64EncodedString() .replacingOccurrences(of: "+", with: "-") .replacingOccurrences(of: "/", with: "_") diff --git a/ylgamehall/Source/Network/QiniuUploader.swift b/ylgamehall/Source/Network/QiniuUploader.swift index bb5c8e2..f1ec5ca 100644 --- a/ylgamehall/Source/Network/QiniuUploader.swift +++ b/ylgamehall/Source/Network/QiniuUploader.swift @@ -57,9 +57,17 @@ public actor QiniuUploader { // 客户端自签 token(参 QiniuTokenSigner / msext QiniuManager 同款算法) let token = QiniuTokenSigner.uploadToken(key: key) + // 七牛 SDK 8.x:`QNUploadManager.init()` 被标 `kQNDeprecated`,必须走 + // `initWithConfiguration:`。用 defaultConfigurationV2(v2 已替代旧 defaultConfiguration)。 + // initWithConfiguration: ObjC 端 instancetype 桥到 Swift 仍为可选; + // 实际几乎不会失败(内部仅初始化无 IO),失败按 responseInvalid 抛出。 + let cfg = QNConfiguration.defaultConfigurationV2() + guard let mgr = QNUploadManager(configuration: cfg) else { + throw QiniuUploadError.responseInvalid + } + let fileName = localFile.lastPathComponent + return try await withCheckedThrowingContinuation { (cont: CheckedContinuation) in - let mgr = QNUploadManager() - let fileName = localFile.lastPathComponent mgr.putFile( localFile.path, key: key, diff --git a/ylgamehall/Source/SDK/AMap/AMapWrapper.swift b/ylgamehall/Source/SDK/AMap/AMapWrapper.swift index cf46ce0..1de9cc6 100644 --- a/ylgamehall/Source/SDK/AMap/AMapWrapper.swift +++ b/ylgamehall/Source/SDK/AMap/AMapWrapper.swift @@ -17,6 +17,9 @@ import Foundation #if canImport(AMapFoundationKit) import AMapFoundationKit #endif +#if canImport(AMapLocationKit) +import AMapLocationKit +#endif @MainActor public enum AMapWrapper { @@ -26,17 +29,15 @@ public enum AMapWrapper { /// 启动注册:在 AppDelegate.didFinishLaunchingWithOptions 调用一次。 /// 必须按 iOS 14+ 隐私合规顺序:updatePrivacyShow → updatePrivacyAgree → apiKey + /// 注意:updatePrivacyShow / updatePrivacyAgree 是 AMapLocationManager 的类方法 + /// (since 2.8.0),不在 AMapServices 上。 public static func bootstrap() { - #if canImport(AMapFoundationKit) - AMapLocationServices.updatePrivacyShow(.didShow, privacyInfo: .didContain) - AMapLocationServices.updatePrivacyAgree(.didAgree) + #if canImport(AMapFoundationKit) && canImport(AMapLocationKit) + AMapLocationManager.updatePrivacyShow(.didShow, privacyInfo: .didContain) + AMapLocationManager.updatePrivacyAgree(.didAgree) AMapServices.shared().apiKey = apiKey #else // SDK 未链接:no-op #endif } } - -#if canImport(AMapFoundationKit) -private typealias AMapLocationServices = AMapServices -#endif