iOS第三发平台组件化解耦实践

iOS第三发平台组件化解耦实践

背景

之前写过一篇类似的,以下是旧的背景介绍,因为这部分没有变动,依旧还是使用旧的背景介绍,引用如下。这次把这个组件做了一个比较大的改动,所以重新写了一篇文章总结,固有此文。

项目使用到了一些第三方平台的登录、分享、支付功能,包括了微信、微博、QQ平台登录分享和支付宝、微信平台的支付,使用的是原生的接入配置集成的,功能上基本上对照着SDK的开发文档就能够成功的集成了。但是问题也后面也渐渐的暴露出来了,第三方平台的登录、分享、支付功能不同平台的的SDK实现方式还是有很大的差别的,包括了输入的参数以及回调方式都有差别很大,如果只是简单的按照文档集成,那么一定会遇到代码调用结构很乱,回调杂乱不统一的问题,更为严重的是,后面如果添加删除一个第三发平台,那么修改变得很困难和难以维护,这违反了软件开发中的开闭原则,所以考虑到了把这部分代码做一个重构。

本文主要介绍的内容集中在实现步骤这部分,其他部分可以视为引子,实现步骤这部分的内容包含如下:

  • 旧的方式局限性
  • 实现思路
  • 架构设计(抽象接口分析设计)
    • 平台处理的接口设计
    • 平台请求的接口设计
    • 平台回调的接口设计
  • 典型案例分析(QQ登录分享以及QQ钱包支付为例)
    • 平台处理的类实现
    • 平台请求的类实现
    • 平台回调的类实现
    • 平台管理类
      • 平台管理类管理平台处理类的实现
      • 平台的配置参数处理
      • 平台管理类提供的扩展点

结果

项目地址:YTThirdPlatformManager

以一个使用案例和一个插件扩展自定义平台的例子来简单说下结果

使用案例

第三方平台注册使用如下方法,提供参数可配置的接口,以在不同的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+PayPTThirdPlatformManager+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];

总结

以上就是第三发平台组件化解耦实践的一些总结,如有不妥之处,还请不吝赐教。

    原文作者:移动开发
    原文地址: https://my.oschina.net/FEEDFACF/blog/1635545
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞