微信 SDK 的接入是最简单的,本 Handler 基于微信 SDK 1.8.7.1 实现。
General
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class WechatSDKHandler {
public let endpoints: [Endpoint] = [ Endpoints.Wechat.friend, Endpoints.Wechat.timeline, Endpoints.Wechat.favorite, ]
public let platform: Platform = Platforms.wechat
public var isInstalled: Bool { WXApi.isWXAppInstalled() }
private var shareCompletionHandler: Bus.ShareCompletionHandler? private var oauthCompletionHandler: Bus.OauthCompletionHandler?
public let appID: String public let universalLink: URL
public var logHandler: Bus.LogHandler = { message, _, _, _ in #if DEBUG print(message) #endif }
private var helper: Helper!
public init(appID: String, universalLink: URL) { self.appID = appID self.universalLink = universalLink
helper = Helper(master: self)
#if DEBUG WXApi.startLog(by: .detail) { [weak self] message in self?.log(message) } #endif
WXApi.registerApp( appID, universalLink: universalLink.absoluteString ) } }
|
调用 WXApi.registerApp(_:universalLink:) 注册微信 SDK,调用 WXApi.startLog(by:logBlock:) 记录日志。Helper 处理微信的回调,相关内容下文会进行说明。
LogHandlerProxyType
1
| extension WechatSDKHandler: LogHandlerProxyType {}
|
声明 WechatSDKHandler 遵循 LogHandlerProxyType 协议。
ShareHandlerType
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
| extension WechatSDKHandler: ShareHandlerType {
public func share( message: MessageType, to endpoint: Endpoint, options: [Bus.ShareOptionKey: Any] = [:], completionHandler: @escaping Bus.ShareCompletionHandler ) { guard isInstalled else { completionHandler(.failure(.missingApplication)) return }
guard canShare(message: message.identifier, to: endpoint) else { completionHandler(.failure(.unsupportedMessage)) return }
shareCompletionHandler = completionHandler
let request = SendMessageToWXReq() request.scene = Int32(scene(endpoint).rawValue)
let mediaMessage = WXMediaMessage()
if let message = message as? MediaMessageType { mediaMessage.title = message.title ?? "" mediaMessage.description = message.description ?? "" mediaMessage.thumbData = message.thumbnail }
switch message { case let message as TextMessage: request.text = message.text request.bText = true
case let message as ImageMessage: let imageObject = WXImageObject() imageObject.imageData = message.data
mediaMessage.mediaObject = imageObject
case let message as AudioMessage: let audioObject = WXMusicObject() audioObject.musicUrl = message.link.absoluteString audioObject.musicDataUrl = message.dataLink?.absoluteString ?? ""
mediaMessage.mediaObject = audioObject
case let message as VideoMessage: let videoObject = WXVideoObject() videoObject.videoUrl = message.link.absoluteString
mediaMessage.mediaObject = videoObject
case let message as WebPageMessage: let webPageObject = WXWebpageObject() webPageObject.webpageUrl = message.link.absoluteString
mediaMessage.mediaObject = webPageObject
case let message as FileMessage: let fileObject = WXFileObject() fileObject.fileData = message.data fileObject.fileExtension = message.fileExtension
mediaMessage.mediaObject = fileObject
case let message as MiniProgramMessage: let miniProgramObject = WXMiniProgramObject() miniProgramObject.webpageUrl = message.link.absoluteString miniProgramObject.userName = message.miniProgramID miniProgramObject.path = message.path miniProgramObject.miniProgramType = miniProgramType(message.miniProgramType) miniProgramObject.hdImageData = message.thumbnail
mediaMessage.mediaObject = miniProgramObject
default: assertionFailure() completionHandler(.failure(.unsupportedMessage)) return }
request.message = mediaMessage
WXApi.send(request) { result in if !result { completionHandler(.failure(.invalidMessage)) } } }
private func canShare(message: Message, to endpoint: Endpoint) -> Bool { switch endpoint { case Endpoints.Wechat.friend: return true case Endpoints.Wechat.timeline: return ![Messages.file, Messages.miniProgram].contains(message) case Endpoints.Wechat.favorite: return ![Messages.miniProgram].contains(message) default: assertionFailure() return false } }
private func scene(_ endpoint: Endpoint) -> WXScene { switch endpoint { case Endpoints.Wechat.friend: return WXSceneSession case Endpoints.Wechat.timeline: return WXSceneTimeline case Endpoints.Wechat.favorite: return WXSceneFavorite default: assertionFailure() return WXSceneSession } }
private func miniProgramType(_ miniProgramType: MiniProgramMessage.MiniProgramType) -> WXMiniProgramType { switch miniProgramType { case .release: return .release case .test: return .test case .preview: return .preview } } }
|
在分享流程中:
- 调用
isInstalled 判断是否安装微信,没有则提前退出。
- 调用
canShare(message:to:) 判断 Endpoint 是否支持此类型 Message。例如 Endpoints.Wechat.timeline 朋友圈不支持 Messages.file 文件和 Messages.miniProgram 小程序。
- 创建
SendMessageToWXReq 请求,调用 scene(_:) 根据 Endpoint 创建 WXScene。
- 创建
WXMediaMessage 多媒体消息,如果 message 遵循 MediaMessageType 协议,设置 title / description / thumbData。
- 判断
message 的具体类型,设置 request 和 mediaMessage 的相关属性。例如 TextMessage 需要设置 request.bText = true,MiniProgramMessage 需要调用 miniProgramType(_:) 根据 MiniProgramMessage.MiniProgramType 创建 WXMiniProgramType。
- 调用
WXApi.send(_:completion:) 拉起微信分享。
OauthHandlerType
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| extension WechatSDKHandler: OauthHandlerType {
public func oauth( options: [Bus.OauthOptionKey: Any] = [:], completionHandler: @escaping Bus.OauthCompletionHandler ) { guard isInstalled else { completionHandler(.failure(.missingApplication)) return }
oauthCompletionHandler = completionHandler
let request = SendAuthReq() request.scope = "snsapi_userinfo"
let viewController = (options[OauthOptionKeys.viewController] as? UIViewController) ?? UIViewController()
WXApi.sendAuthReq( request, viewController: viewController, delegate: helper ) { result in if !result { completionHandler(.failure(.unknown)) } } } }
extension WechatSDKHandler {
public enum OauthOptionKeys {
public static let viewController = Bus.OauthOptionKey(rawValue: "com.nuomi1.bus.wechatSDKHandler.viewController") } }
|
在登录流程中:
- 调用
isInstalled 判断是否安装微信,没有则提前退出。
- 创建
SendAuthReq 请求。scope 在微信 SDK 中没有对应的常量,使用 snsapi_userinfo 硬编码。
- 从
options 中获取 viewController,没有则创建一个空白的。
- 调用
WXApi.sendAuthReq(_:viewController:delegate:completion:) 拉起微信登录。
OpenURLHandlerType
1 2 3 4 5 6
| extension WechatSDKHandler: OpenURLHandlerType {
public func openURL(_ url: URL) { WXApi.handleOpen(url, delegate: helper) } }
|
调用 WXApi.handleOpen(_:delegate:) 处理 URL Scheme 回调。
OpenUserActivityHandlerType
1 2 3 4 5 6
| extension WechatSDKHandler: OpenUserActivityHandlerType {
public func openUserActivity(_ userActivity: NSUserActivity) { WXApi.handleOpenUniversalLink(userActivity, delegate: helper) } }
|
调用 WXApi.handleOpenUniversalLink(_:delegate:) 处理 NSUserActivity 回调。
Helper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| extension WechatSDKHandler {
fileprivate class Helper: NSObject, WXApiDelegate {
weak var master: WechatSDKHandler?
required init(master: WechatSDKHandler) { self.master = master }
func onReq(_ req: BaseReq) { assertionFailure("\(req)") }
func onResp(_ resp: BaseResp) { switch resp { case let response as SendMessageToWXResp: switch response.errCode { case WXSuccess.rawValue: master?.shareCompletionHandler?(.success(())) default: master?.shareCompletionHandler?(.failure(.unknown)) } case let response as SendAuthResp: switch (response.errCode, response.code) { case let (WXSuccess.rawValue, code): let parameters = [ OauthInfoKeys.code: code, ] .compactMapContent()
if !parameters.isEmpty { master?.oauthCompletionHandler?(.success(parameters)) } else { master?.oauthCompletionHandler?(.failure(.unknown)) } default: master?.oauthCompletionHandler?(.failure(.unknown)) } default: assertionFailure("\(resp)") } } } }
extension WechatSDKHandler {
public enum OauthInfoKeys {
public static let code = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.wechatSDKHandler.code") } }
|
创建 Helper 处理微信的回调:
WechatSDKHandler 强持有 Helper,所以 Helper 的 master 声明为 weak 避免循环引用。
- 处理分享回调时,返回
SendMessageToWXResp,调用 response.errCode 判断是否为 WXSuccess.rawValue,调用 master?.shareCompletionHandler?(.success(())) 完成分享回调。
- 处理登录回调时,返回
SendAuthResp,调用 response.errCode 和 response.code 判断成功且 code 不为空时,调用 master?.oauthCompletionHandler?(.success(parameters)) 完成登录回调。
总结
本文通过封装微信 SDK 做出 WechatSDKHandler,实现微信的登录和分享功能。同时使用了独立的内部类 Helper 来处理微信的回调,保持 WechatSDKHandler 的整洁。