Files
youle_app_ios_v2/ylgamehall/Source/SDK/WeChat/WeChatManager.swift
T
joywayerandClaude Opus 4.7 93bbbc3d59 修复 1.8.2 WXApi 调用:用 Swift 简化后的方法名
Swift 编译器对 ObjC API 自动应用名称简化规则(Swift 3+ SE-0044),
即使是 1.x 老 SDK header 也会被简化。ObjC selector 不变,运行时
仍调 +[WXApi getApiVersion] / +[WXApi sendReq:],但 Swift 端必须
写简化后的名字:

- ObjC +(NSString *)getApiVersion → Swift WXApi.getVersion()
- ObjC +(BOOL)sendReq:(BaseReq*)req → Swift WXApi.send(_:)

错误信息里 Swift 编译器明确给出:"has been renamed to 'getVersion()'"
+ "obsoleted in Swift 3"。

注释里同步说明 selector 与 Swift 名映射关系,避免未来维护者误以为
我们调到的是 2.x SDK 的 getVersion(实际仍是 1.8.2 的 getApiVersion)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 09:13:08 +08:00

240 lines
9.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// WeChatManager.swift
// ylgamehall
//
// 微信 SDK 持久 delegate + authorize + share 异步包装。
// 把 WXApiDelegate 的回调 → async/awaitstate UUID 配对多发起方。
//
// Design §8.5msext WXApiManager.m 等价。
//
import Foundation
import UIKit
// 微信 SDK 1.8.2(与 daoqi msext 同款)通过 Bridging Header 导入,无需 import 语句
@MainActor
public final class WeChatManager: NSObject {
public static let shared = WeChatManager()
/// 授权 scope(沿用 msext SGDefineInfo.h:104
public static let authScope = "snsapi_message,snsapi_userinfo,snsapi_friend,snsapi_contact"
/// 待 resolve 的 authorize continuationstate UUID 配对)
private var authPending: [String: CheckedContinuation<WXAuthCodePayload, Error>] = [:]
/// 待 resolve 的 share continuationFIFO 串行 — 微信回包不带配对 ID)
private var sharePending: [CheckedContinuation<Void, Error>] = []
public override init() {
super.init()
}
// MARK: - Authorize
/// 拉起微信授权(msext NewRootVC.m → WXApiRequestHandler.m 同款路径)。
/// 必须用新版 SDK 2.x 的 `sendAuthReq:viewController:delegate:completion:`
/// - viewController = SDK 拉起微信回来时的 present 上下文(缺失则 SDK 无法弹起)
/// - delegate = SDK 弱引用,授权完成由 WXApiDelegate.onResp 回调(与 SceneDelegate
/// openURL 路径解耦,授权回包独立由 SDK 直接派给 delegate
/// 旧版 1.x 的 `WXApi.send(req)` 已 deprecated,不传 vc/delegate 会导致 H5 调了无反应。
public func authorize() async throws -> WXAuthCodePayload {
print("[WeChat] authorize() 开始")
print("[WeChat] - WXApi.isWXAppInstalled = \(WXApi.isWXAppInstalled())")
// ObjC 端 selector 是 +getApiVersion,但 Swift 编译器自动简化为 getVersion()
// Swift 3+ ObjC name 简化规则;运行时仍调 +[WXApi getApiVersion]
print("[WeChat] - WXApi.getVersion = \(WXApi.getVersion() ?? "nil")")
let state = UUID().uuidString
return try await withCheckedThrowingContinuation { cont in
authPending[state] = cont
let req = SendAuthReq()
req.scope = Self.authScope
req.state = state
guard let vc = Self.topmostViewController() else {
print("[WeChat] ⚠️ topmostViewController() 返回 nil,无 present 上下文")
authPending.removeValue(forKey: state)
cont.resume(throwing: WeChatError.authFailed(-3))
return
}
print("[WeChat] 调 sendAuthReq vc=\(type(of: vc)) scope=\(Self.authScope) state=\(state)")
// 1.x 的 sendAuthReq 返回 BOOL(同步知道是否成功拉起),无 completion
let success = WXApi.sendAuthReq(req, viewController: vc, delegate: self)
print("[WeChat] sendAuthReq 返回 success=\(success)")
if !success {
if let pending = authPending.removeValue(forKey: state) {
pending.resume(throwing: WeChatError.authFailed(-3))
}
}
// success=true 时等 WXApiDelegate.onResp 触发(SceneDelegate openURL 链路)
}
}
/// 取当前最顶层 ViewController(含 presented / nav.visible),供微信 SDK 作为
/// modal present 的上下文。msext 直接 selfgameController/NewRootVC),新外壳
/// AccreditLoginHandler 是 stateless enum,由本方法 resolve。
private static func topmostViewController() -> UIViewController? {
let windowScene = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first(where: { $0.activationState == .foregroundActive })
?? UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first
guard let root = windowScene?.windows.first(where: \.isKeyWindow)?.rootViewController
?? windowScene?.windows.first?.rootViewController else {
return nil
}
var top: UIViewController = root
while let presented = top.presentedViewController {
top = presented
}
if let nav = top as? UINavigationController, let visible = nav.visibleViewController {
return visible
}
return top
}
// MARK: - Share
public enum ShareScene {
case session // 微信好友
case timeline // 朋友圈
}
public struct ShareLink: Sendable {
public let url: String
public let title: String
public let desc: String
public let thumbnailURL: String?
public init(url: String, title: String, desc: String, thumbnailURL: String?) {
self.url = url; self.title = title; self.desc = desc; self.thumbnailURL = thumbnailURL
}
}
/// 图片分享载荷(type=2 截图 / type=3 远端图)。
/// imageData 是大图原始字节(JPEG 等),thumb 是缩略图源 — SDK
/// `setThumbImage:` 内部把 thumb 压到 32KB。
public struct ShareImage {
public let imageData: Data
public let thumb: UIImage
public init(imageData: Data, thumb: UIImage) {
self.imageData = imageData
self.thumb = thumb
}
}
/// 链接分享(FIFO 串行)。微信回包不携带配对 ID,连续调时按入队顺序消费。
public func shareLink(_ link: ShareLink, scene: ShareScene) async throws {
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
sharePending.append(cont)
let media = WXWebpageObject()
media.webpageUrl = link.url
let message = WXMediaMessage()
message.title = link.title
message.description = link.desc
message.mediaObject = media
let req = SendMessageToWXReq()
req.bText = false
req.message = message
switch scene {
case .session: req.scene = Int32(WXSceneSession.rawValue)
case .timeline: req.scene = Int32(WXSceneTimeline.rawValue)
}
// 1.x: sendReq BOOL 返回;分享回包通过 SceneDelegate.openURLContexts →
// WXApi.handleOpen(url:delegate:WeChatManager.shared) → onResp 路径到达。
// ObjC selector +sendReq:Swift 简化为 send(_:)(去 Req 后缀)。
// 运行时仍调 +[WXApi sendReq:]
WXApi.send(req)
}
}
/// 图片分享(FIFO 串行,复用 sharePending)。msext gameController.m:637-678
/// type=2 截图 / type=其它 远端图 共用同一条 WXImageObject + setThumbImage 路径。
public func shareImage(_ img: ShareImage, scene: ShareScene) async throws {
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
sharePending.append(cont)
let media = WXImageObject()
media.imageData = img.imageData
let message = WXMediaMessage()
// 图片分享不需要 title / descriptionmsext 字面:传 nil;这里传空串等价)
message.mediaObject = media
message.setThumbImage(img.thumb)
let req = SendMessageToWXReq()
req.bText = false
req.message = message
switch scene {
case .session: req.scene = Int32(WXSceneSession.rawValue)
case .timeline: req.scene = Int32(WXSceneTimeline.rawValue)
}
// ObjC selector +sendReq:Swift 简化为 send(_:)(去 Req 后缀)。
// 运行时仍调 +[WXApi sendReq:]
WXApi.send(req)
}
}
// MARK: - Internal: resolve callbacks
fileprivate func resolveAuth(state: String, code: String?, errCode: Int) {
guard let cont = authPending.removeValue(forKey: state) else { return }
if errCode == 0, let code {
cont.resume(returning: WXAuthCodePayload(code: code, state: state))
} else {
cont.resume(throwing: WeChatError.authFailed(errCode))
}
}
fileprivate func resolveShare(errCode: Int) {
guard !sharePending.isEmpty else { return }
let cont = sharePending.removeFirst()
if errCode == 0 {
cont.resume(returning: ())
} else {
cont.resume(throwing: WeChatError.shareFailed(errCode))
}
}
}
// MARK: - WXApiDelegate
extension WeChatManager: WXApiDelegate {
public nonisolated func onReq(_ req: BaseReq) {
// 我们当前只发起请求,不接受外部 req(微信 → 本 App 的 req 用于公众号回复等场景)
}
public nonisolated func onResp(_ resp: BaseResp) {
// 主线程化:BaseResp 由 SDK 在主线程派发,但保留 MainActor.run 兜底
let errCode = Int(resp.errCode)
if let auth = resp as? SendAuthResp {
let code = auth.code
let state = auth.state ?? ""
Task { @MainActor in
Self.shared.resolveAuth(state: state, code: code, errCode: errCode)
}
} else if resp is SendMessageToWXResp {
Task { @MainActor in
Self.shared.resolveShare(errCode: errCode)
}
}
}
}
// MARK: - Public payload / error types
public struct WXAuthCodePayload: Sendable {
public let code: String
public let state: String
}
public enum WeChatError: Error, Sendable {
case sdkNotLinked
case authFailed(Int)
case shareFailed(Int)
}