iOS第三发平台组件化解耦实践
背景
之前写过一篇类似的,以下是旧的背景介绍,因为这部分没有变动,依旧还是使用旧的背景介绍,引用如下。这次把这个组件做了一个比较大的改动,所以重新写了一篇文章总结,固有此文。
项目使用到了一些第三方平台的登录、分享、支付功能,包括了微信、微博、QQ平台登录分享和支付宝、微信平台的支付,使用的是原生的接入配置集成的,功能上基本上对照着SDK的开发文档就能够成功的集成了。但是问题也后面也渐渐的暴露出来了,第三方平台的登录、分享、支付功能不同平台的的SDK实现方式还是有很大的差别的,包括了输入的参数以及回调方式都有差别很大,如果只是简单的按照文档集成,那么一定会遇到代码调用结构很乱,回调杂乱不统一的问题,更为严重的是,后面如果添加删除一个第三发平台,那么修改变得很困难和难以维护,这违反了软件开发中的开闭原则,所以考虑到了把这部分代码做一个重构。
本文主要介绍的内容集中在实现步骤这部分,其他部分可以视为引子,实现步骤这部分的内容包含如下:
- 旧的方式局限性
- 实现思路
- 架构设计(抽象接口分析设计)
- 平台处理的接口设计
- 平台请求的接口设计
- 平台回调的接口设计
- 典型案例分析(QQ登录分享以及QQ钱包支付为例)
- 平台处理的类实现
- 平台请求的类实现
- 平台回调的类实现
- 平台管理类
- 平台管理类管理平台处理类的实现
- 平台的配置参数处理
- 平台管理类提供的扩展点
结果
以一个使用案例和一个插件扩展自定义平台的例子来简单说下结果
使用案例
第三方平台注册使用如下方法,提供参数可配置的接口,以在不同的app中只要修改配置参数即可,不用修改实现类类的代码
/**** 第三方平台注册 *****/
// 微信
[configInstance setPlaform:PTThirdPlatformTypeWechat
appID:kWXAppID
appKey:nil
appSecret:kWXAppSecret
redirectURL:nil
URLSchemes:nil];
// QQ授权分享
[configInstance setPlaform:PTThirdPlatformTypeTencentQQ
subType:PTThirdPlatformSubTypeAuthShare
appID:kTencentAppID
appKey:kTencentAppKey
appSecret:kTencentAppSecret
redirectURL:nil
URLSchemes:nil];
分享和支付的例子如下:
// 分享模型
ThirdPlatformShareModel* shareModel = [[ThirdPlatformShareModel alloc] init];
shareModel.image = nil;
shareModel.imageUrlString = @"";
shareModel.title = @"title";
shareModel.text = @"text";
shareModel.weiboText = @"weibo text";
shareModel.urlString = @"http://www.baidu.com";
shareModel.fromViewController = self;
shareModel.shareResultBlock = ^(PTShareType pplatform, PTShareResult result, NSError * error) {
};
[self addActionWithName:@"钉钉分享Demo" callback:^{
shareModel.platform = PTCustumShareTypeDingTalk;
[[PTThirdPlatformManager sharedInstance] shareWithModel:shareModel];
}];
// 支付信息模型
OrderModel* order = [[OrderModel alloc] init];
[self addActionWithName:@"支付宝支付" callback:^{
[[PTThirdPlatformManager sharedInstance] payWithPlateform:PTThirdPlatformTypeAlipay order:order paymentBlock:^(PTPayResult result) {
}];
}];
插件扩展自定义平台的例子
该库提供了扩展点提供用户的自定义功能扩展,以下是钉钉分享的一个自定义扩展接入的示例代码
// 自定义的第三方平台以插件的方式添加
PTThirdPlatformManager* configInstance = [PTThirdPlatformManager sharedInstance];
[configInstance addCustomSharePlatform:PTCustumShareTypeDingTalk
managerClass:PTDingTalkManager.class];
[configInstance setPlaform:PTCustumShareTypeDingTalk
appID:kDingTalkAppID
appKey:nil
appSecret:nil
redirectURL:nil
URLSchemes:nil];
调用扩展的功能代码如下:
// 分享模型
ThirdPlatformShareModel* shareModel = [[ThirdPlatformShareModel alloc] init];
shareModel.image = nil;
shareModel.imageUrlString = @"";
shareModel.title = @"title";
shareModel.text = @"text";
shareModel.weiboText = @"weibo text";
shareModel.urlString = @"http://www.baidu.com";
shareModel.fromViewController = self;
shareModel.shareResultBlock = ^(PTShareType pplatform, PTShareResult result, NSError * error) {
};
[self addActionWithName:@"钉钉分享Demo" callback:^{
shareModel.platform = PTCustumShareTypeDingTalk;
[[PTThirdPlatformManager sharedInstance] shareWithModel:shareModel];
}];
实现步骤
实现步骤从以下四个方面展开来说
- 旧的方式局限性
- 实现思路
- 架构设计(抽象接口分析设计)
- 典型案例分析(QQ登录分享以及QQ钱包支付为例)
旧的方式局限性
在我把这个功能实现拆分解耦的上一个版本,我的实现方式是使用OC中的Category,我定义了一个PTThirdPlatformManager
第三方平台的管理类,这个类的职责是第三方授权登录,然后使用两个Category:PTThirdPlatformManager+Pay
和PTThirdPlatformManager+Share
分别处理第三方平台的支付和分享,下面是PTThirdPlatformManager+Pay
中的接口设计和部分的实现设计
PTThirdPlatformManager (Share) 头文件
typedef void(^PTShareResultBlock)(PTShareType platform, PTShareResult shareResult, NSError* error);
@interface PTThirdPlatformManager (Share) <WXApiManagerDelegate, PTTencentApiManagerDelegate>
@property (nonatomic, copy) PTShareResultBlock shareResultBlock;
/**
获取分享的URL
*/
- (NSString*)shareURLStringWithParams:(NSDictionary*)params;
/**
* 第三方分享,支持分享到Facebook、Twitter、etc..
* @param platform 第三方分享平台
* @param text 分享的文字
* @param url 分享的URL
* @param fromViewController 从哪个页面调用的分享
* @param shareResultBlock 分享结果回调Block
*/
- (void)shareToPlateform:(PTShareType)platform
image:(UIImage*)image
imageUrlString:(NSString*)imageUrlString
title:(NSString*)title
text:(NSString*)text
urlString:(NSString*)urlString
fromViewController:(UIViewController*)fromViewController
shareResultBlock:(PTShareResultBlock)shareResultBlock;
@end
PTThirdPlatformManager (Share) 部分实现文件
@implementation PTThirdPlatformManager (Share)
/**
获取分享的URL
*/
- (NSString*)shareURLStringWithParams:(NSDictionary*)params {
NSString* globalShareLink = [MMGlobalConfigManager sharedInstance].globalConfig.shareLink;
if (globalShareLink) {
NSString* randStr = [NSString stringWithFormat:@"%d", arc4random_uniform(100000)];
NSMutableDictionary* tmpParams =
[@{@"name": ValueOrEmpty([AccountManager sharedInstance].account.nickname),
@"country": ValueOrEmpty([ProductParameters currentCountry]),
@"av": ValueOrEmpty([ProductParameters appVersion]),
@"pid": ValueOrEmpty([ProductParameters productID]),
@"rand": ValueOrEmpty(randStr)} mutableCopy];
if (params) {
[tmpParams addEntriesFromDictionary:params];
}
for (NSString* key in tmpParams.allKeys) {
tmpParams[key] = [GlobalURL URLEncode:tmpParams[key]];
}
NSString* composedUrlString = [GlobalURL URLStringWithBaseURLString:globalShareLink params:tmpParams];
return composedUrlString;
}
return nil;
}
/**
* 第三方分享,支持分享到Facebook、Twitter、etc..
* @param platform 第三方分享平台
* @param text 分享的文字
* @param url 分享的URL
* @param fromViewController 从哪个页面调用的分享
* @param shareResultBlock 分享结果回调Block
*/
- (void)shareToPlateform:(PTShareType)platform
image:(UIImage*)image
imageUrlString:(NSString*)imageUrlString
title:(NSString*)title
text:(NSString*)text
urlString:(NSString*)urlString
fromViewController:(UIViewController*)fromViewController
shareResultBlock:(PTShareResultBlock)shareResultBlock {
self.shareResultBlock = shareResultBlock;
__block UIImage* sharedImage = nil;
if (image) {
sharedImage = image;
[self doSharePlateform:platform image:sharedImage imageUrlString:imageUrlString title:title text:text urlString:urlString fromViewController:fromViewController shareResultBlock:shareResultBlock];
} else if (imageUrlString != nil) {
[[SDWebImageManager sharedManager] loadImageWithURL:[NSURL URLWithString:imageUrlString] options:0 progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
if (image) {
sharedImage = image;
} else {
sharedImage = [UIImage imageNamed:@"app_icon"];
}
[self doSharePlateform:platform image:sharedImage imageUrlString:imageUrlString title:title text:text urlString:urlString fromViewController:fromViewController shareResultBlock:shareResultBlock];
}];
} else {
sharedImage = [UIImage imageNamed:@"signin_logo"];
[self doSharePlateform:platform image:sharedImage imageUrlString:imageUrlString title:title text:text urlString:urlString fromViewController:fromViewController shareResultBlock:shareResultBlock];
}
}
- (void)doSharePlateform:(PTShareType)platform
image:(UIImage*)image
imageUrlString:(NSString*)imageUrlString
title:(NSString*)title
text:(NSString*)text
urlString:(NSString*)urlString
fromViewController:(UIViewController*)fromViewController
shareResultBlock:(PTShareResultBlock)shareResultBlock {
switch (platform) {
case PTShareTypeWechat:
{
[self doWechatShareWithImage:image urlString:urlString title:title text:text fromViewController:fromViewController];
}
break;
case PTShareTypeWechatLine:
{
[self doWechatLineShareWithImage:image
urlString:urlString
title:title
text:text
fromViewController:fromViewController];
}
case PTShareTypeQQ:
case PTShareTypeQQZone:
{
[self doQQShareWithImage:image
imageUrlString:imageUrlString
urlString:urlString
title:title
text:text
shareType:platform
fromViewController:fromViewController];
}
default:
break;
}
}
- (void)doWechatShareWithImage:(UIImage*)image
urlString:(NSString*)urlString
title:(NSString*)title
text:(NSString*)text
fromViewController:(UIViewController*)fromViewController {
[PTWXApiManager sharedManager].delegate = self;
BOOL shareResult = [PTWXRequestHandler sendMessageWithImage:image urlString:urlString title:title text:text scene:WXSceneSession];
if (shareResult == NO) {
dispatch_async(dispatch_get_main_queue(), ^{
[Dialog toast:_(@"Please install Wechat")];
});
!self.shareResultBlock ?: self.shareResultBlock(PTShareTypeWechat, PTShareResultFailed, nil);
}
}
- (void)doWechatLineShareWithImage:(UIImage*)image
urlString:(NSString*)urlString
title:(NSString*)title
text:(NSString*)text
fromViewController:(UIViewController*)fromViewController {
[PTWXApiManager sharedManager].delegate = self;
BOOL shareResult = [PTWXRequestHandler sendMessageWithImage:image urlString:urlString title:(NSString*)title text:text scene:WXSceneTimeline];
if (shareResult == NO) {
!self.shareResultBlock ?: self.shareResultBlock(PTShareTypeWechat, PTShareResultFailed, nil);
}
}
// 分享到QQ
- (void)doQQShareWithImage:(UIImage*)image
imageUrlString:(NSString*)imageUrlString
urlString:(NSString*)urlString
title:(NSString*)title
text:(NSString*)text
shareType:(PTShareType)shareType
fromViewController:(UIViewController*)fromViewController {
[PTTencentApiManager sharedManager].delegate = self;
BOOL shareResult = [PTTencentApiRequestHandler sendMessageWithImage:image imageUrlString:imageUrlString urlString:urlString title:title text:text shareType:shareType];
if (shareResult == NO) {
dispatch_async(dispatch_get_main_queue(), ^{
// [Dialog toast:_(@"Please install Wechat")];
});
!self.shareResultBlock ?: self.shareResultBlock(PTShareTypeWechat, PTShareResultFailed, nil);
}
}
#pragma mark - WXApiManagerDelegate
- (void)managerDidRecvMessageResponse:(SendMessageToWXResp *)response; {
if (response.errCode == WXSuccess) {
!self.shareResultBlock ?: self.shareResultBlock(PTShareTypeWechat, PTShareResultSuccess, nil);
} else if (response.errCode == WXErrCodeUserCancel) {
!self.shareResultBlock ?: self.shareResultBlock(PTShareTypeWechat, PTShareResultCancel, nil);
} else {
!self.shareResultBlock ?: self.shareResultBlock(PTShareTypeWechat, PTShareResultFailed, nil);
}
}
#pragma mark - ......::::::: PTTencentApiManagerDelegate :::::::......
- (void)qq_managerDidRecvMessageResponse:(BOOL)result {
if (result) {
!self.shareResultBlock ?: self.shareResultBlock(PTShareTypeQQ, PTShareResultSuccess, nil);
} else {
!self.shareResultBlock ?: self.shareResultBlock(PTShareTypeQQ, PTShareResultFailed, nil);
}
}
#pragma mark - ......::::::: getter & setter :::::::......
- (void)setShareResultBlock:(PTShareResultBlock)shareResultBlock {
objc_setAssociatedObject(self, @selector(shareResultBlock), shareResultBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (PTShareResultBlock)shareResultBlock {
return objc_getAssociatedObject(self, _cmd);
}
@end
这个类承担了职责目前包括了:分享信息的预处理、分享到微信、分享到微信的回调结果处理、分享到QQ、分享到QQ的回调结果处理、分享到微博、分享到微博的回调结果处理,这个类负责的职责太多了,这违反了软件设计中的单一职责原则。后续如果有其他的分享平台,我们必须去修改这个类的代码,这个类会添加更多的if/else、switch/case条件分支以及对应平台的结果回调处理,会导致这个类的内容变得更加的膨胀和难以维护,这违反了软甲设计中的开闭原则。此外因为是Category,需要使用runtime处理属性的getter和setter,显得笨重、麻烦、不够优雅并且可能会因为内存管理产生不必要的问题。
由上面的分析,使用这种方式有以下的缺点:
- 违反了单一职责原则
- 违反了开闭原则
- Category中定义属性保存数据不优雅和易出错
实现思路
根据上一节「旧的方式局限性」中的分析,针对缺点,具体问题具体分析,找到对应的解决方案
- 违反了单一职责原则
分享分类处理了微信、QQ、微博三个平台的业务逻辑,导致代码逻辑上的复杂,所以如果从平台角度切入,每个平台独立处理自己的业务那么代码将会得到简化,职责也会变得更加的清晰。
针对单个平台分析,单个平台处理步骤的指针包括发送分享的请求、接收异步回调、返回信息给客户端,单个平台的职责还是可以进行拆分为:发送请求、处理异步回调
以上从平台和平台的发送和处理回调两个角度去拆分职责,让每个类的职责更加的单一和清晰。
- 违反了开闭原则
如果从平台和平台的发送和处理回调两个角度去拆分职责、那么新增一个第三方分享只要新增对应的平台类、请求处理类、异步回调处理类,虽然增加了类的数量,但是这个代价相比不遵循设计原则导致了类膨胀和难以维护来说还是值得的。
- Category中定义属性保存数据不优雅和易出错
从以上的单一职责原则和开闭原则两方面的分析,大概可以从平台、平台的请求、平台的回调三个维度进行抽象,设计对应的子类去实现抽象的接口,所以这是一个继承的体系,不存在Category了,可以使用在普通类中那样的去定义属性即可。
架构设计(抽象接口分析设计)
平台处理的接口设计
平台处理的接口包含了以下功能:
- 第三方平台处理URL
- 第三方登录
- 第三方分享
- 第三方支付
- 检测平台APP是否安装
@protocol PTAbsThirdPlatformManager <NSObject>
@optional
- (void)thirdPlatConfigWithApplication:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
/**
第三方平台处理URL
*/
- (BOOL)thirdPlatCanOpenUrlWithApplication:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation;
/**
第三方登录
@param thirdPlatformType 第三方平台
@param viewController 从哪个页面调用的分享
@param callback 登录回调
*/
- (void)signInWithType:(PTThirdPlatformType)thirdPlatformType
fromViewController:(UIViewController *)viewController
callback:(void (^)(ThirdPlatformUserInfo* userInfo, NSError* err))callback;
/**
第三方分享
*/
- (void)shareWithModel:(ThirdPlatformShareModel*)model;
/**
第三方支付
@param payMethodType 支付平台
@param order 支付订单模型
@param paymentBlock 支付结果回调
*/
- (void)payWithPlateform:(PTThirdPlatformType)payMethodType order:(OrderModel*)order paymentBlock:(void (^)(BOOL result))paymentBlock;
// APP是否安装
- (BOOL)isThirdPlatformInstalled:(PTShareType)thirdPlatform;
@end
平台请求的接口设计
平台请求接口设计包含了以下功能:
- 第三方授权登录
- 第三方支付
- 第三方分享
@protocol PTAbsThirdPlatformRequestHandler <NSObject>
@optional
// 第三方授权
+ (BOOL)sendAuthInViewController:(UIViewController *)viewController;
// 支付
+ (BOOL)payWithOrder:(OrderModel*)order;
// 分享
+ (BOOL)sendMessageWithModel:(ThirdPlatformShareModel*)model;
@end
平台回调的接口设计
平台回调的接口设计处理单个平台的SDK回调,一般的是第三方APP通过URL的跨进程通信,跳转到宿主APP,传递结果参数。这个回调不同的第三方平台设计的接口不一样,所以在我们的平台回调的接口设计种就把这种差异化统一,使用自定义的回调,把数据通过统一的方法回调到上层去。
- PTAbsThirdPlatformRespManagerDelegate 是统一回调接口的抽象设计
- PTAbsThirdPlatformRespManager 实现不同平台的回调接口,通过统一的PTAbsThirdPlatformRespManagerDelegate对象delegate回调到上层
// RespManagerDelegate
@protocol PTAbsThirdPlatformRespManagerDelegate <NSObject>
@optional
- (void)respManagerDidRecvPayResponse:(BOOL)result platform:(PTThirdPlatformType)platform;
- (void)respManagerDidRecvAuthResponse:(ThirdPlatformUserInfo *)response platform:(PTThirdPlatformType)platform;
- (void)respManagerDidRecvMessageResponse:(BOOL)result platform:(PTShareType)platform;
@end
@protocol PTAbsThirdPlatformRespManager <NSObject>
@optional
// 代理,子类需要设置getter/setter
@property (nonatomic, weak) id<PTAbsThirdPlatformRespManagerDelegate> delegate;
@end
典型案例分析(QQ登录分享以及QQ钱包支付为例)
QQ登录分享和QQ钱包支付虽然是属于同一个平台,但是却需要集成不同的SDK以及不同的配置,相比微信这种一个SDK集成了授权、分享、支付功能的第三方平台来说,QQ平台具体典型性,所以选择QQ平台进行案例分析。此外,因为功能根据职责进行了拆分,生成多个平台处理类,需要一个管理类来管理这些平台处理类,并且为了实现更好的扩展,比如说需要扩展一个不再该库中内置的平台处理类,管理类应当提供一个扩展点进行扩展,这样库才具有更好的灵活性和扩展性。
从以下四个方面来实现QQ平台的授权登录、分享、支付的功能
- 平台处理的类实现
- 平台请求的类实现
- 平台回调的类实现
- 平台管理类
- 平台管理类管理平台处理类的实现
- 平台的配置参数处理
- 平台管理类提供的扩展点
平台处理的类实现
平台处理类包含了以下功能:
- 第三方平台处理URL
- 第三方登录
- 第三方分享
- 第三方支付
- 检测平台APP是否安装
平台请求处理类
依赖平台请求的类
和平台回调的类
,可以认为是一个中间者,负责协调平台请求的类
和平台回调的类
之间的关系。
@interface PTTencentManager () <PTAbsThirdPlatformRespManagerDelegate>
@end
@implementation PTTencentManager
DEF_SINGLETON
- (void)thirdPlatConfigWithApplication:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 子类实现
// 初始化QQ模块
[PTTencentRespManager sharedInstance];
}
/**
第三方平台处理URL
*/
- (BOOL)thirdPlatCanOpenUrlWithApplication:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// QQ 授权
if ([TencentOAuth CanHandleOpenURL:url] && [TencentOAuth HandleOpenURL:url]) {
return YES;
}
// QQ 业务
if ([QQApiInterface handleOpenURL:url delegate:[PTTencentRespManager sharedInstance]]) {
return YES;
}
// QQ钱包,在此函数中注册回调监听
if ([[QQWalletSDK sharedInstance] hanldeOpenURL:url]) {
return YES;
}
return NO;
}
/**
第三方登录
*/
- (void)signInWithType:(PTThirdPlatformType)thirdPlatformType fromViewController:(UIViewController *)viewController callback:(void (^)(ThirdPlatformUserInfo* userInfo, NSError* err))callback {
self.callback = callback;
[PTTencentRespManager sharedInstance].delegate = self;
[PTTencentRequestHandler sendAuthInViewController:viewController];
}
// 分享
- (void)doShareWithModel:(ThirdPlatformShareModel *)model {
self.shareResultBlock = model.shareResultBlock;
[PTTencentRespManager sharedInstance].delegate = self;
BOOL shareResult = [PTTencentRequestHandler sendMessageWithModel:model];
if (shareResult == NO) {
!self.shareResultBlock ?: self.shareResultBlock(PTShareTypeQQ, PTShareResultFailed, nil);
}
}
/**
第三方支付
*/
- (void)payWithPlateform:(PTThirdPlatformType)payMethodType order:(OrderModel*)order paymentBlock:(void (^)(PTPayResult result))paymentBlock {
self.paymentBlock = paymentBlock;
// 使用QQ支付
[PTTencentRespManager sharedInstance].delegate = self;
[PTTencentRequestHandler payWithOrder:order];
}
- (BOOL)isThirdPlatformInstalled:(PTShareType)thirdPlatform {
return [TencentOAuth iphoneQQInstalled] || [TencentOAuth iphoneTIMInstalled];
}
@end
平台请求的类实现
平台请求的类是对SDK提供的api的一层封装,把这层单独出来也是为了实现更好的职责分离,主要功能包含如下,这些功能是可选的,可以选择其中的任意个实现:
- 第三方授权登录
- 第三方支付
- 第三方分享
@implementation PTTencentRequestHandler
// 第三方授权
+ (BOOL)sendAuthInViewController:(UIViewController *)viewController {
NSArray* permissions = [NSArray arrayWithObjects:@"get_user_info",@"get_simple_userinfo", @"add_t", nil];
BOOL result = [[PTTencentRespManager sharedInstance].tencentOAuth authorize:permissions inSafari:NO];
return result;
}
// 分享
+ (BOOL)sendMessageWithModel:(ThirdPlatformShareModel *)model {
QQApiObject* obj;
if (PTShareContentTypeVideo == model.mediaObject.contentType) {
// PTSharedVideoObject* mediaObj = (PTSharedVideoObject*)model.mediaObject;
obj = [QQApiVideoObject objectWithURL:[NSURL URLWithString:ValueOrEmpty(model.urlString)] title:model.title description:model.text previewImageURL:[NSURL URLWithString:ValueOrEmpty(model.imageUrlString)]];
} else {
obj = [QQApiNewsObject
objectWithURL:[NSURL URLWithString:ValueOrEmpty(model.urlString)]
title:model.title
description:model.text
previewImageURL:[NSURL URLWithString:ValueOrEmpty(model.imageUrlString)]];
}
SendMessageToQQReq *req = [SendMessageToQQReq reqWithContent:obj];
QQApiSendResultCode sent = 0;
if (PTShareTypeQQ == model.platform) {
//将内容分享到qq
sent = [QQApiInterface sendReq:req];
} else {
//将内容分享到qzone
sent = [QQApiInterface SendReqToQZone:req];
}
return EQQAPISENDSUCESS == sent;
}
// 支付
+ (BOOL)payWithOrder:(OrderModel*)order {
// 发起支付
NSString* appID = [[PTThirdPlatformManager sharedInstance] appIDWithPlaform:PTThirdPlatformTypeTencentQQ subType:PTThirdPlatformSubTypePay];
NSString* scheme = [[PTThirdPlatformManager sharedInstance] URLSchemesWithPlaform:PTThirdPlatformTypeTencentQQ subType:PTThirdPlatformSubTypePay];
[[QQWalletSDK sharedInstance] startPayWithAppId:appID
bargainorId:order.prepayid
tokenId:order.package
signature:order.sign
nonce:order.noncestr
scheme:scheme
completion:^(QQWalletErrCode errCode, NSString *errStr){
// 支付完成的回调处理
if (errCode == QQWalletErrCodeSuccess) {
// 对支付成功的处理
[[PTTencentRespManager sharedInstance] setPayResult:PTPayResultSuccess];
} else if (errCode == QQWalletErrCodeUserCancel) {
// 对支付取消的处理
[[PTTencentRespManager sharedInstance] setPayResult:PTPayResultCancel];
} else {
// 对支付失败的处理
[[PTTencentRespManager sharedInstance] setPayResult:PTPayResultFailed];
}
}];
return YES;
}
@end
平台回调的类实现
平台回调的接口设计处理单个平台的SDK回调,一般的是第三方APP通过URL的跨进程通信,跳转到宿主APP,传递结果参数。这个回调不同的第三方平台设计的接口不一样,所以在我们的平台回调的接口设计种就把这种差异化统一,使用自定义的回调,把数据通过统一的方法回调到上层去。
平台回调类处理的是第三方APP通过- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
方法进行跨进程数据通信的一些结果,一般的是重写SDK对应的Delegate协议的方法,还有可能对数据进行二次处理,最终返回给上层的是统一可直接使用的数据对象。比如在这个例子中,重写的是TencentSessionDelegate
协议的- (void)getUserInfoResponse:(APIResponse*) response
方法,再把获取到的数据进行二次整理,最后封装为上层可用的ThirdPlatformUserInfo
类的对象。另外有的SDK采用的不是这种Delegate协议回调方法的方式,而是采用block回调的方式,我这里采用的方式是在平台回调的类
中添加一个公有方法,把数据传到这层,然后再数据进行二次处理回调给上层,这样虽然增加了步骤,但是数据流父更加的统一。比如QQ钱包支付,回调使用的是block的方式,我会把结果通过PTTencentRespManager
定义的公有方法- (void)setPayResult:(PTPayResult)payResult
,把结果数据传递到这层来处理,然后统一回调
@implementation PTTencentRespManager
DEF_SINGLETON
- (instancetype)init
{
self = [super init];
if (self) {
NSString* appID = [[PTThirdPlatformManager sharedInstance] appIDWithPlaform:PTThirdPlatformTypeTencentQQ subType:PTThirdPlatformSubTypeAuthShare];
_tencentOAuth = [[TencentOAuth alloc] initWithAppId:appID andDelegate:self];
}
return self;
}
- (TencentOAuth *)tencentOAuth {
if (!_tencentOAuth) {
NSString* appID = [[PTThirdPlatformManager sharedInstance] appIDWithPlaform:PTThirdPlatformTypeTencentQQ subType:PTThirdPlatformSubTypeAuthShare];
_tencentOAuth = [[TencentOAuth alloc] initWithAppId:appID andDelegate:self];
}
return _tencentOAuth;
}
#pragma mark - ......::::::: TencentLoginDelegate :::::::......
/**
* 登录成功后的回调
*/
- (void)tencentDidLogin {
NSLog(@"===");
[self.tencentOAuth getUserInfo];
}
/**
* 登录失败后的回调
* \param cancelled 代表用户是否主动退出登录
*/
- (void)tencentDidNotLogin:(BOOL)cancelled {
NSLog(@"===");
if ([self.delegate respondsToSelector:@selector(respManagerDidRecvAuthResponse:platform:)]) {
[self.delegate respManagerDidRecvAuthResponse:nil platform:PTThirdPlatformTypeTencentQQ];
}
}
/**
* 登录时网络有问题的回调
*/
- (void)tencentDidNotNetWork {
NSLog(@"===");
if ([self.delegate respondsToSelector:@selector(respManagerDidRecvAuthResponse:platform:)]) {
[self.delegate respManagerDidRecvAuthResponse:nil platform:PTThirdPlatformTypeTencentQQ];
}
}
#pragma mark - ......::::::: TencentSessionDelegate :::::::......
/**
* 获取用户个人信息回调
* \param response API返回结果,具体定义参见sdkdef.h文件中\ref APIResponse
* \remarks 正确返回示例: \snippet example/getUserInfoResponse.exp success
* 错误返回示例: \snippet example/getUserInfoResponse.exp fail
*/
- (void)getUserInfoResponse:(APIResponse*) response {
NSLog(@"===");
if (URLREQUEST_SUCCEED == response.retCode
&& kOpenSDKErrorSuccess == response.detailRetCode) {
ThirdPlatformUserInfo *user = [self.class userbyTranslateTencentResult:response.jsonResponse];
user.userId = self.tencentOAuth.openId;
user.tokenString = self.tencentOAuth.accessToken;
if ([self.delegate respondsToSelector:@selector(respManagerDidRecvAuthResponse:platform:)]) {
[self.delegate respManagerDidRecvAuthResponse:user platform:PTThirdPlatformTypeTencentQQ];
}
} else {
if ([self.delegate respondsToSelector:@selector(respManagerDidRecvAuthResponse:platform:)]) {
[self.delegate respManagerDidRecvAuthResponse:nil platform:PTThirdPlatformTypeTencentQQ];
}
}
}
+ (ThirdPlatformUserInfo *)userbyTranslateTencentResult:(id)result {
ThirdPlatformUserInfo *user = [[ThirdPlatformUserInfo alloc] init];
user.thirdPlatformType = PTThirdPlatformTypeTencentQQ;
if ([result isKindOfClass:[NSDictionary class]]) {
user.gender = [result objectForKey:@"gender"];
user.username = [result objectForKey:@"nickname"];
user.head = [result objectForKey:@"figureurl_qq_2"];
NSString *year = [result objectForKeyedSubscript:@"year"];
NSDateFormatter *dateFoematter = [[NSDateFormatter alloc] init];
[dateFoematter setDateFormat:@"yyyy"];
NSString *currDate = [dateFoematter stringFromDate:[NSDate date]];
int ageNum = [currDate intValue] - [year intValue];
user.age = [NSString stringWithFormat:@"%d",ageNum];
}
return user;
}
/**
* 社交API统一回调接口
* \param response API返回结果,具体定义参见sdkdef.h文件中\ref APIResponse
* \param message 响应的消息,目前支持‘SendStory’,‘AppInvitation’,‘AppChallenge’,‘AppGiftRequest’
*/
- (void)responseDidReceived:(APIResponse*)response forMessage:(NSString *)message {
NSLog(@"===");
}
/**
处理来至QQ的请求
*/
- (void)onReq:(QQBaseReq *)req {
NSLog(@"===");
}
/**
处理来至QQ的响应
*/
- (void)onResp:(QQBaseResp *)resp {
NSLog(@"===");
if ([resp isKindOfClass:[SendMessageToQQResp class]]) {
if ([self.delegate respondsToSelector:@selector(respManagerDidRecvMessageResponse:platform:)]) {
if ([resp.result isEqualToString:@"0"]) {
[self.delegate respManagerDidRecvMessageResponse:YES platform:PTShareTypeQQ];
} else {
[self.delegate respManagerDidRecvMessageResponse:NO platform:PTShareTypeQQ];
}
}
}
}
/**
处理QQ在线状态的回调
*/
- (void)isOnlineResponse:(NSDictionary *)response {
NSLog(@"===");
}
#pragma mark - ......::::::: Public :::::::......
- (void)setPayResult:(PTPayResult)payResult {
if ([self.delegate respondsToSelector:@selector(respManagerDidRecvPayResponse:platform:)]) {
[self.delegate respManagerDidRecvPayResponse:payResult platform:PTThirdPlatformTypeTencentQQ];
}
}
@end
平台管理类
平台管理类本身也是一个平台处理类,在这基础上添加了配置参数处理功能以及平台扩展的功能
从以下三个方面分析和实现平台管理类的功能:
- 平台管理类管理平台处理类的实现
- 平台的配置参数处理
- 平台管理类提供的扩展点
平台管理类管理平台处理类的实现
平台管理类管理平台处理类简单来说就是把平台类型和对应的处理类做成成一个配置,在平台管理类中定义的如下的数据来保存这些配置和关系。在配置的时候会查找不同类型平台对应的实现类,然后通过运行时生成类对象,最终把请求转发给这个生成类对象处理,下面是配置的代码段
// 配置管理类的类名
- (NSMutableSet*)thirdPlatformManagerClasses {
if (nil == _thirdPlatformManagerClasses) {
_thirdPlatformManagerClasses = [[NSMutableSet alloc] init];
[_thirdPlatformManagerClasses
addObjectsFromArray:@[@"PTAlipayManager",
@"PTTencentManager",
@"PTWeiboManager",
@"PTWXManager",
]];
}
return _thirdPlatformManagerClasses;
}
// 配置第三方登录支付对应的管理类
- (NSMutableDictionary*)thirdPlatformManagerConfig {
if (nil == _thirdPlatformManagerConfig) {
_thirdPlatformManagerConfig = [[NSMutableDictionary alloc] init];
[_thirdPlatformManagerConfig addEntriesFromDictionary:
@{
@(PTThirdPlatformTypeWechat): @"PTWXManager",
@(PTThirdPlatformTypeTencentQQ): @"PTTencentManager",
@(PTThirdPlatformTypeWeibo): @"PTWeiboManager",
@(PTThirdPlatformTypeAlipay): @"PTAlipayManager",
}];
}
return _thirdPlatformManagerConfig;
}
// 配置第三方分享对应的管理类
- (NSMutableDictionary*)thirdPlatformShareManagerConfig {
if (nil == _thirdPlatformShareManagerConfig) {
_thirdPlatformShareManagerConfig = [[NSMutableDictionary alloc] init];
[_thirdPlatformShareManagerConfig addEntriesFromDictionary:
@{
@(PTShareTypeWechat): @"PTWXManager",
@(PTShareTypeWechatLine): @"PTWXManager",
@(PTShareTypeQQ): @"PTTencentManager",
@(PTShareTypeQQZone): @"PTTencentManager",
@(PTShareTypeWeibo): @"PTWeiboManager",
}];
}
return _thirdPlatformShareManagerConfig;
}
在配置第三方平台时使用到上面的配置
/**
第三方平台配置
*/
- (void)thirdPlatConfigWithApplication:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
for (NSString* classString in [self thirdPlatformManagerClasses]) {
id<PTAbsThirdPlatformManager> manager = [self managerFromClassString:classString];
if (manager && [manager conformsToProtocol:@protocol(PTAbsThirdPlatformManager)]) {
[manager thirdPlatConfigWithApplication:application didFinishLaunchingWithOptions:launchOptions];
}
}
}
在第三方平台分享时使用到上面的配置
/**
第三方分享
@param model 分享数据
*/
- (void)shareWithModel:(ThirdPlatformShareModel *)model {
NSString* classString = [[self thirdPlatformShareManagerConfig] objectForKey:@(model.platform)];
id<PTAbsThirdPlatformManager> manager = [self managerFromClassString:classString];
[manager shareWithModel:model];
}
此外,第三方平台处理授权登录、支付、处理跨进程通信的回调URL时候也会使用到上面的配置,不在一一例举了
平台的配置参数处理
因为QQ登录分享和QQ钱包支付虽然是属于同一个平台,但是却需要集成不同的SDK以及不同的配置,所以需要两个配置,我采用的方式是定义一个主类型枚举和定义一个子类型枚举来处理,比如主类型枚举如下:
// 第三方平台类型
typedef NS_ENUM(NSInteger, PTThirdPlatformType) {
PTThirdPlatformTypeWechat = 100,//微信
PTThirdPlatformTypeTencentQQ,//QQ
PTThirdPlatformTypeWeibo,//微博
PTThirdPlatformTypeAlipay,//支付宝
};
子类型枚举如下:
// 第三方平台类型对应的子类型
typedef NS_ENUM(NSInteger, PTThirdPlatformSubType) {
PTThirdPlatformSubTypeTotal = 1,//所有的子类型,不区分
PTThirdPlatformSubTypeAuthShare,//分享授权子类型
PTThirdPlatformSubTypePay,//支付子类型
};
设置的数据保存在一个NSDictionary里面,以及可以使用一系列的方法来获取配置信息,相关部分的代码如下,更详细的可打开我的Demo功能来查看
- (BOOL)setPlaform:(PTThirdPlatformType)platformType
subType:(PTThirdPlatformSubType)subType
appID:(NSString *)appID
appKey:(NSString *)appKey
appSecret:(NSString *)appSecret
redirectURL:(NSString *)redirectURL
URLSchemes:(NSString*)URLSchemes {
NSDictionary* subTypeConfig = @{@(PTThirdPlatformAppID): ValueOrEmpty(appID),
@(PTThirdPlatformAppKey): ValueOrEmpty(appKey),
@(PTThirdPlatformAppSecret): ValueOrEmpty(appSecret),
@(PTThirdPlatformRedirectURI): ValueOrEmpty(redirectURL),
@(PTThirdPlatformURLSchemes): ValueOrEmpty(URLSchemes),
};
if (![self.thirdPlatformKeysConfig objectForKey:@(platformType)]) {
[self.thirdPlatformKeysConfig setObject:[@{} mutableCopy] forKey:@(platformType)];
}
[[self.thirdPlatformKeysConfig objectForKey:@(platformType)] setObject:subTypeConfig forKey:@(subType)];
return YES;
}
//......
- (NSString*)appIDWithPlaform:(PTThirdPlatformType)platformType {
return [self appIDWithPlaform:platformType subType:PTThirdPlatformSubTypeTotal];
}
- (NSString*)appKeyWithPlaform:(PTThirdPlatformType)platformType {
return [self appKeyWithPlaform:platformType subType:PTThirdPlatformSubTypeTotal];
}
- (NSString*)appSecretWithPlaform:(PTThirdPlatformType)platformType {
return [self appSecretWithPlaform:platformType subType:PTThirdPlatformSubTypeTotal];
}
- (NSString*)appRedirectURLWithPlaform:(PTThirdPlatformType)platformType {
return [self appRedirectURLWithPlaform:platformType subType:PTThirdPlatformSubTypeTotal];
}
- (NSString*)URLSchemesWithPlaform:(PTThirdPlatformType)platformType {
return [self URLSchemesWithPlaform:platformType subType:PTThirdPlatformSubTypeTotal];
}
- (NSString*)appIDWithPlaform:(PTThirdPlatformType)platformType subType:(PTThirdPlatformSubType)subType {
return [[[self.thirdPlatformKeysConfig objectForKey:@(platformType)] objectForKey:@(subType)] objectForKey:@(PTThirdPlatformAppID)];
}
- (NSString*)appKeyWithPlaform:(PTThirdPlatformType)platformType subType:(PTThirdPlatformSubType)subType {
return [[[self.thirdPlatformKeysConfig objectForKey:@(platformType)] objectForKey:@(subType)] objectForKey:@(PTThirdPlatformAppKey)];
}
- (NSString*)appSecretWithPlaform:(PTThirdPlatformType)platformType subType:(PTThirdPlatformSubType)subType {
return [[[self.thirdPlatformKeysConfig objectForKey:@(platformType)] objectForKey:@(subType)] objectForKey:@(PTThirdPlatformAppSecret)];
}
- (NSString*)appRedirectURLWithPlaform:(PTThirdPlatformType)platformType subType:(PTThirdPlatformSubType)subType {
return [[[self.thirdPlatformKeysConfig objectForKey:@(platformType)] objectForKey:@(subType)] objectForKey:@(PTThirdPlatformRedirectURI)];
}
- (NSString*)URLSchemesWithPlaform:(PTThirdPlatformType)platformType subType:(PTThirdPlatformSubType)subType {
return [[[self.thirdPlatformKeysConfig objectForKey:@(platformType)] objectForKey:@(subType)] objectForKey:@(PTThirdPlatformURLSchemes)];
}
平台管理类提供的扩展点
在上面的步骤中,已经把平台类型和平台处理类形成了配置,扩展点其实就是把自定义的平台类型和对应的平台处理类添加到这些配置后面就行了,管理类中提供了的扩展点方法有以下两个
#pragma mark 插件接入点
/**
插件接入点-添加登录或者是支付的管理类
@param platformType 自定义的第三方平台类型,大于999
@param managerClass 实现了PTAbsThirdPlatformManager接口的自定义第三方平台管理类
*/
- (void)addCustomPlatform:(NSInteger)platformType managerClass:(Class)managerClass {
NSString* classString = NSStringFromClass(managerClass);
if (classString) {
[self.thirdPlatformManagerConfig setObject:NSStringFromClass(managerClass) forKey:@(platformType)];
[self.thirdPlatformManagerClasses addObject:classString];
}
}
/**
插件接入点-添加分享的管理类
@param sharePlatformType 自定义的第三方平台分享类型,大于999
@param managerClass 实现了PTAbsThirdPlatformManager接口的自定义第三方平台管理类
*/
- (void)addCustomSharePlatform:(NSInteger)sharePlatformType managerClass:(Class)managerClass {
NSString* classString = NSStringFromClass(managerClass);
if (classString) {
[self.thirdPlatformShareManagerConfig setObject:classString forKey:@(sharePlatformType)];
[self.thirdPlatformManagerClasses addObject:classString];
}
}
下面以钉钉分享为例,实现一个自定义的第三方平台,使用扩展点接口扩展该功能,钉钉平台处理类的类名为PTDingTalkManager
,使用扩展点接口添加该平台处理类的代码如下,具体的实现可以查看Demo代码
// 自定义的第三方平台以插件的方式添加
PTThirdPlatformManager* configInstance = [PTThirdPlatformManager sharedInstance];
[configInstance addCustomSharePlatform:PTCustumShareTypeDingTalk
managerClass:PTDingTalkManager.class];
[configInstance setPlaform:PTCustumShareTypeDingTalk
appID:kDingTalkAppID
appKey:nil
appSecret:nil
redirectURL:nil
URLSchemes:nil];
总结
以上就是第三发平台组件化解耦实践的一些总结,如有不妥之处,还请不吝赐教。