微信 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
的整洁。