美文网首页
iOS SDK 动态引入第三方SDK

iOS SDK 动态引入第三方SDK

作者: Hamiltion | 来源:发表于2020-03-25 15:48 被阅读0次

引言

最近在做SDK,我们的SDK中需要集成很多第三方库,比如Twitter、Facebook等,我们SDK中需要用到这些第三方SDK中的登录、分享等功能,这些第三方库我们不会打包到我们自己的SDK中,而是接入方通过Pod或者其他方式导入到他们的工程。接我们SDK的人需求是不一样的,有的只用到Facebook、有的只用到Twitter。如果有一个接入我们SDK的人只用到Facebook,但是我们要求接入者不仅要导入Facebook的SDK,还要导入Twitter的SDK(因为SDK中集成了Twitter的东西,不导入会报错),这样是不是不太好,而且接入者可能会说,我们都没有用到这个第三方,为什么要导入他们的SDK。

那么问题来了,我们要怎么样才能做到我们SDK中既集成所有的第三方SDK,又能满足接入者用到哪个第三方才导入对应的第三方SDK?

解决方案

经过一段时间的琢磨、想出了几个方案:
方案一:在SDK内部判断是否导入了某个第三方库,然后通过预编译的方式,是否要编译某段代码(这个方案行不通)
方案二:SDK内部通过runtime的方式调用第三方SDK的东西,通过动态的方式使用类,方法及属性(这个方法可以)
方案三:SDK 分成两个部分,一个部分是Framework部分,一个部分是源码部分,结合方案一和方案二(最终的方案)

下面详细的说说这三种方案,包括这三种方案使用时遇到的困难以及优缺点

注意:本文中的代码只是为了简单的测试,在正常开发的时候,最好进行一些相应的判断,以免引起不必要的麻烦

方案一

直接上代码,这是.m文件所有示例代码

#import "Test.h"

// 判断是否存在TwitterKit/TWTRKit.h,如果存在说明导入了Twitter的SDK
#define TwitterSDKModule __has_include(<TwitterKit/TWTRKit.h>)

// 在导入了Twitter的SDK情况下,才引入
#if TwitterSDKModule
@import TwitterKit;
#endif

// 遵循Twitter的某个协议,以及使用Twitter的某个类的对象最为属性
#if TwitterSDKModule
@interface Test()<这儿是Twitter的某个协议>
@property (nonatomic, strong) TWTRTwitter *twitter;
@end
#endif

// 跟Twitter没关系的属性
@interface Test()
@property (nonatomic, copy) NSString *message;
@end

@implementation Test

#if TwitterSDKModule

/// 调用Twitter的方法
- (void)loginByTwitter {
    [[Twitter sharedInstance] logInWithCompletion:^(TWTRSession * _Nullable session, NSError * _Nullable error) {
        
    }];
}

#endif

@end

原本信心满满,但是惨惨招打脸,为什么这种方式不行呢?原因就在于我们打包Framework时,我们SDK内部的代码就编译好了,是否存在<TwitterKit/TWTRKit.h>在打包Framework时就决定了,而不是取决于接入者的工程中是否包含<TwitterKit/TWTRKit.h>。所以这种方式宣告失败

方案二

使用runtime方式。我们可以通过runtime的方式,在不引入头文件的情况下进行某个类的调用
具体参考代码如下:

    // 获取Person类
    Class personClass = NSClassFromString(@"Person");
    // 获取方法编号
    SEL shareSEL = NSSelectorFromString(@"shared");
    // 调用Person类的类方法
    [personClass performSelector:shareSEL withObject:nil];
    // 创建实例化对象
    id person = [[personClass alloc] init];
    // 获取对象的属性
    NSString *name = [person valueForKey:@"name"];
    // 给属性赋值
    [person setValue:@"xiao hai" forKey:@"name"];
    // 获取方法编号
    SEL runSEL = NSSelectorFromString(@"run");
    // 实例方法的调用
    [person performSelector:runSEL withObject:nil];

这里获取类、调用类方法、获取属性值、给属性赋值、调用实例方法都有了,那么问题来了,我们要怎么去遵循协议、实现协议的方法,常规写法如下:

@interface Test ()<PersonDelegate>
@end

@implementation ViewController
- (void)eatWithPerson:(Person *)person {
    
}
@end

那么问题来了,如果不引入头文件的情况下,PersonDelegate、Person都是未知的,代理方法里还有很多未知的类,我们应该如何解决,首先“@interface Test ()<PersonDelegate>”中, PersonDelegate代理完全不用写,之所以要写,我个人理解是为了写代码方便,减少写代码带来的错误
遵循代理解决了,那么代理方法的实现怎么解决,Person是未知的,直接写上去肯定会报错,我们只需要将Person用id代替就行了,之所以可以这么写,是因为这两种写法,方法名是一样的,都是“eatWithPerson:”,这样就解决了代理相关问题,简直perfect,实例代码如下:

@interface Test ()
@end

@implementation Test
- (void)eatWithPerson:(id)person {
    
}
@end

本以为问题都解决了,但是又遇到问题了,performSelector方法不支持多参数,于是苦苦寻找解决方法,终于在GitHub上找到了解决办法,MXRuntimeUtils帮我解决了这个问题,非常感谢MXRuntimeUtils的作者。本应该再次高兴时,问题又又又出现了,MXRuntimeUtils不支持block类型的参数,于是我对MXRuntimeUtils做了改进,增加了对block类型参数的支持,如果有需要的小伙伴可以联系我
这种方式终于结束了,下面我说说这种方式很大的一个弊端,每个使用第三方SDK的地方都要用这种方式来写,我只能说太累,太麻烦了

方案三(最终方案)

把方案一和方案二结合,并进行改进。我以SDK集成Facebook、Twitter为例子,下面我直接给出我们SDK在这块的架构示意图


7E80D089C5A36B2FAA30D77A4FF12579.jpg

我针对这个图解释一下,整个SDK分成两个部分,Framework部分和源文件部分,Framework部分是我们常规把包达成Framework的形式,源文件部分,主要是对第三方SDK的接口进行二次封装,直接向SDK接入者暴露源代码(完全不牵扯到我们SDK内部业务,代码暴露给接入者没什么关系)。源文件中的Adapter(下面简称A)是对需要的第三方接口进行整合,比如我们SDK需要集成Twitter和Facebook的登录功能,A中就提供一个登录接口(通过一个参数来判断要调用Twitter的登录还是Facebook的登录)源文件中的对Twitter、Facebook接口的二次封装,通过方案一中使用的方法封装。Framework中的Adapter(下面简称B)是对A进行翻译,供Framework部分中其他地方直接调用。B是framework内部文件,A是Framework外部文件,B如何调用A呢,可以通过方案二中的runtime形式调用。
分析:这种方案也用到了跟方案一中几乎一样的方案,为什么方案一达不到效果,而这种方案可以,原因在于在这个方案中,把方案一中用到的东西放到了源代码中。这两种方案,区别在于我们写的有关预编译的部分的编译时机,方案一,预编译部分的代码在打包Framework的时候就编译好了,而这种方案,预编译部分的代码在接入方编译时才编译,达到了我们想要的效果。

结束语

如果有什么写的不对的地方,欢迎指正,如果有什么不明白的地方也很欢迎一起探讨
作者联系方式:QQ 782304472 (要加的小伙伴请写明在此文章中看到的,谢谢)

相关文章

网友评论

      本文标题:iOS SDK 动态引入第三方SDK

      本文链接:https://www.haomeiwen.com/subject/iidlyhtx.html