引言
最近在做SDK拗军,我們的SDK中需要集成很多第三方庫殊轴,比如Twitter钦奋、Facebook等座云,我們SDK中需要用到這些第三方SDK中的登錄、分享等功能付材,這些第三方庫我們不會打包到我們自己的SDK中朦拖,而是接入方通過Pod或者其他方式導(dǎo)入到他們的工程。接我們SDK的人需求是不一樣的厌衔,有的只用到Facebook璧帝、有的只用到Twitter。如果有一個接入我們SDK的人只用到Facebook富寿,但是我們要求接入者不僅要導(dǎo)入Facebook的SDK睬隶,還要導(dǎo)入Twitter的SDK(因為SDK中集成了Twitter的東西,不導(dǎo)入會報錯)页徐,這樣是不是不太好苏潜,而且接入者可能會說,我們都沒有用到這個第三方变勇,為什么要導(dǎo)入他們的SDK恤左。
那么問題來了,我們要怎么樣才能做到我們SDK中既集成所有的第三方SDK搀绣,又能滿足接入者用到哪個第三方才導(dǎo)入對應(yīng)的第三方SDK飞袋?
解決方案
經(jīng)過一段時間的琢磨、想出了幾個方案:
方案一:在SDK內(nèi)部判斷是否導(dǎo)入了某個第三方庫豌熄,然后通過預(yù)編譯的方式授嘀,是否要編譯某段代碼(這個方案行不通)
方案二:SDK內(nèi)部通過runtime的方式調(diào)用第三方SDK的東西,通過動態(tài)的方式使用類锣险,方法及屬性(這個方法可以)
方案三:SDK 分成兩個部分蹄皱,一個部分是Framework部分览闰,一個部分是源碼部分,結(jié)合方案一和方案二(最終的方案)
下面詳細(xì)的說說這三種方案巷折,包括這三種方案使用時遇到的困難以及優(yōu)缺點
注意:本文中的代碼只是為了簡單的測試压鉴,在正常開發(fā)的時候,最好進(jìn)行一些相應(yīng)的判斷油吭,以免引起不必要的麻煩
方案一
直接上代碼,這是.m文件所有示例代碼
#import "Test.h"
// 判斷是否存在TwitterKit/TWTRKit.h署拟,如果存在說明導(dǎo)入了Twitter的SDK
#define TwitterSDKModule __has_include(<TwitterKit/TWTRKit.h>)
// 在導(dǎo)入了Twitter的SDK情況下婉宰,才引入
#if TwitterSDKModule
@import TwitterKit;
#endif
// 遵循Twitter的某個協(xié)議,以及使用Twitter的某個類的對象最為屬性
#if TwitterSDKModule
@interface Test()<這兒是Twitter的某個協(xié)議>
@property (nonatomic, strong) TWTRTwitter *twitter;
@end
#endif
// 跟Twitter沒關(guān)系的屬性
@interface Test()
@property (nonatomic, copy) NSString *message;
@end
@implementation Test
#if TwitterSDKModule
/// 調(diào)用Twitter的方法
- (void)loginByTwitter {
[[Twitter sharedInstance] logInWithCompletion:^(TWTRSession * _Nullable session, NSError * _Nullable error) {
}];
}
#endif
@end
原本信心滿滿推穷,但是慘慘招打臉心包,為什么這種方式不行呢?原因就在于我們打包Framework時馒铃,我們SDK內(nèi)部的代碼就編譯好了蟹腾,是否存在<TwitterKit/TWTRKit.h>在打包Framework時就決定了,而不是取決于接入者的工程中是否包含<TwitterKit/TWTRKit.h>区宇。所以這種方式宣告失敗
方案二
使用runtime方式娃殖。我們可以通過runtime的方式,在不引入頭文件的情況下進(jìn)行某個類的調(diào)用
具體參考代碼如下:
// 獲取Person類
Class personClass = NSClassFromString(@"Person");
// 獲取方法編號
SEL shareSEL = NSSelectorFromString(@"shared");
// 調(diào)用Person類的類方法
[personClass performSelector:shareSEL withObject:nil];
// 創(chuàng)建實例化對象
id person = [[personClass alloc] init];
// 獲取對象的屬性
NSString *name = [person valueForKey:@"name"];
// 給屬性賦值
[person setValue:@"xiao hai" forKey:@"name"];
// 獲取方法編號
SEL runSEL = NSSelectorFromString(@"run");
// 實例方法的調(diào)用
[person performSelector:runSEL withObject:nil];
這里獲取類议谷、調(diào)用類方法炉爆、獲取屬性值、給屬性賦值柿隙、調(diào)用實例方法都有了叶洞,那么問題來了鲫凶,我們要怎么去遵循協(xié)議禀崖、實現(xiàn)協(xié)議的方法,常規(guī)寫法如下:
@interface Test ()<PersonDelegate>
@end
@implementation ViewController
- (void)eatWithPerson:(Person *)person {
}
@end
那么問題來了螟炫,如果不引入頭文件的情況下波附,PersonDelegate、Person都是未知的昼钻,代理方法里還有很多未知的類掸屡,我們應(yīng)該如何解決,首先“@interface Test ()<PersonDelegate>”中然评, PersonDelegate代理完全不用寫,之所以要寫,我個人理解是為了寫代碼方便亮钦,減少寫代碼帶來的錯誤
遵循代理解決了,那么代理方法的實現(xiàn)怎么解決抖锥,Person是未知的,直接寫上去肯定會報錯碎罚,我們只需要將Person用id代替就行了磅废,之所以可以這么寫,是因為這兩種寫法荆烈,方法名是一樣的拯勉,都是“eatWithPerson:”,這樣就解決了代理相關(guān)問題憔购,簡直perfect宫峦,實例代碼如下:
@interface Test ()
@end
@implementation Test
- (void)eatWithPerson:(id)person {
}
@end
本以為問題都解決了,但是又遇到問題了玫鸟,performSelector方法不支持多參數(shù)斗遏,于是苦苦尋找解決方法,終于在GitHub上找到了解決辦法鞋邑,MXRuntimeUtils幫我解決了這個問題诵次,非常感謝MXRuntimeUtils的作者。本應(yīng)該再次高興時枚碗,問題又又又出現(xiàn)了逾一,MXRuntimeUtils不支持block類型的參數(shù),于是我對MXRuntimeUtils做了改進(jìn)肮雨,增加了對block類型參數(shù)的支持遵堵,如果有需要的小伙伴可以聯(lián)系我
這種方式終于結(jié)束了,下面我說說這種方式很大的一個弊端怨规,每個使用第三方SDK的地方都要用這種方式來寫陌宿,我只能說太累,太麻煩了
方案三(最終方案)
把方案一和方案二結(jié)合波丰,并進(jìn)行改進(jìn)壳坪。我以SDK集成Facebook、Twitter為例子掰烟,下面我直接給出我們SDK在這塊的架構(gòu)示意圖
我針對這個圖解釋一下爽蝴,整個SDK分成兩個部分,F(xiàn)ramework部分和源文件部分纫骑,F(xiàn)ramework部分是我們常規(guī)把包達(dá)成Framework的形式蝎亚,源文件部分,主要是對第三方SDK的接口進(jìn)行二次封裝先馆,直接向SDK接入者暴露源代碼(完全不牽扯到我們SDK內(nèi)部業(yè)務(wù)发框,代碼暴露給接入者沒什么關(guān)系)。源文件中的Adapter(下面簡稱A)是對需要的第三方接口進(jìn)行整合煤墙,比如我們SDK需要集成Twitter和Facebook的登錄功能梅惯,A中就提供一個登錄接口(通過一個參數(shù)來判斷要調(diào)用Twitter的登錄還是Facebook的登錄)源文件中的對Twitter顾患、Facebook接口的二次封裝,通過方案一中使用的方法封裝个唧。Framework中的Adapter(下面簡稱B)是對A進(jìn)行翻譯江解,供Framework部分中其他地方直接調(diào)用。B是framework內(nèi)部文件徙歼,A是Framework外部文件犁河,B如何調(diào)用A呢,可以通過方案二中的runtime形式調(diào)用魄梯。
分析:這種方案也用到了跟方案一中幾乎一樣的方案桨螺,為什么方案一達(dá)不到效果,而這種方案可以酿秸,原因在于在這個方案中灭翔,把方案一中用到的東西放到了源代碼中。這兩種方案辣苏,區(qū)別在于我們寫的有關(guān)預(yù)編譯的部分的編譯時機(jī)肝箱,方案一,預(yù)編譯部分的代碼在打包Framework的時候就編譯好了稀蟋,而這種方案煌张,預(yù)編譯部分的代碼在接入方編譯時才編譯,達(dá)到了我們想要的效果退客。
結(jié)束語
如果有什么寫的不對的地方骏融,歡迎指正,如果有什么不明白的地方也很歡迎一起探討
作者聯(lián)系方式:QQ 782304472 (要加的小伙伴請寫明在此文章中看到的萌狂,謝謝)