iOS動(dòng)態(tài)庫開發(fā)中遇到的問題小結(jié)

關(guān)鍵詞:1. XIB在framework中加載失敶乇;2. imageNamed在framework中加載失斅袜汀;3. 第三方庫沖突屋摔;4. 然后手工添加Pods庫烁设;5. 一些意想不到的事情(感覺這里是本文最干的干貨,前面沒意思的钓试,可以直接拉到最后装黑,(__) 嘻嘻……)

如何創(chuàng)建一個(gè)iOS動(dòng)態(tài)framework的事情就不在本文贅述了,網(wǎng)上有很多的相關(guān)文章介紹弓熏。需要注意一點(diǎn)的是恋谭,網(wǎng)上有些文章會(huì)比較舊(iOS8以前的時(shí)代),會(huì)講一些過時(shí)了的方法硝烂,注意選擇正確的文章即可箕别。


為了更加方便讀者了解framework,放一個(gè)我認(rèn)為寫得用心的連接iOS 開發(fā)中的『庫』


本人主要講述實(shí)際做一個(gè)動(dòng)態(tài)framework作為SDK提供給第三方使用時(shí)候遇到的實(shí)際問題以及我的一些解決辦法滞谢。
情況是這樣的:項(xiàng)目原先已經(jīng)開發(fā)了一個(gè)APP了串稀,現(xiàn)在需要把其中的一些功能部件包裝成SDK給第三方使用。
為了方便同步開發(fā)SDK和APP狮杨,我選擇在原來的APP工程中添加一個(gè)target的方式來產(chǎn)生SDK母截。

XIB在framework中加載失敗

UIViewController的init方法默認(rèn)使用mainBundle加載與類同名的XIB文件。而動(dòng)態(tài)庫在APP里面是一個(gè)獨(dú)立的bundle橄教。這樣子的話喘漏,在動(dòng)態(tài)庫中用到這種方式加載的視圖都會(huì)失敗了。我解決的辦法是使用runtime修改了一下init的實(shí)現(xiàn):

@implementation UIViewController(InitFromFramewok)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod(self, @selector(init));
        Method swizzleMethod = class_getInstanceMethod(self, @selector(dwsdk_init));
        method_exchangeImplementations(originalMethod, swizzleMethod);
    });
}

- (instancetype)dwsdk_init{
    NSBundle *bundle = [NSBundle bundleForClass:[self class]];

    return [self initWithNibName:nil bundle:bundle];
}

@end

imageNamed方法失敗了

失敗的的原因和1的問題差不多华烟,因?yàn)閕mageNamed默認(rèn)也是從mainBundle加載翩迈,因此動(dòng)態(tài)庫里的圖片都加載不到了。

一開始盔夜,我使用了和1類似的方法负饲,想通過runtime修改imageNamed的默認(rèn)實(shí)現(xiàn),改成從[self class]的bundle中加載喂链,結(jié)果卻是不成功返十。

不成功的原因是,imageNamed是類方法椭微,它的[self class]是UIImage洞坑,而UIImage類所在的bundle是UIKit這個(gè)系統(tǒng)動(dòng)態(tài)庫中。
于是蝇率,更換另外一個(gè)實(shí)現(xiàn)方式如下:

+ (nullable UIImage *)dwsdk_imageNamed:(NSString *)name {
    NSBundle *bundle = [NSBundle bundleForClass:[FrameworkNibSwizzle class]];
    UIImage *image = [UIImage imageNamed:name
                                inBundle:bundle
           compatibleWithTraitCollection:nil];
    NSLog(@"<DEBUG>load image:%@ from bundle:%@, result:%@", name, bundle, (image?@"YES":@"NO"));
    return image;
}

其中的FrameworkNibSwizzle是一個(gè)確定在動(dòng)態(tài)庫中的類迟杂。
這下子雖然解決了SDK里加載圖片的問題了,然而瓢剿,這個(gè)方案會(huì)使得SDK外面的imageNamed也被一起替換了逢慌,結(jié)果就是變成SDK外面的imageNamed找不到圖片了悠轩。
于是间狂,引入了一套更加復(fù)雜一些的配套操作:

@implementation FrameworkNibSwizzle

static IMP orgImageNamedImp = nil;

+ (void)initialize {
    orgImageNamedImp = [self currentImageNamedIMP];
}

+ (IMP)orgImageNamedIMP {
    return orgImageNamedImp;
}

+ (IMP)currentImageNamedIMP {
    Method currentImageNamed = class_getClassMethod([UIImage class], @selector(imageNamed:));
    return method_getImplementation(currentImageNamed);
}

+ (IMP)sdkImageNamedIMP {
    Method sdkImageNamed = class_getClassMethod(self, @selector(jcsdk_imageNamed:));
    return method_getImplementation(sdkImageNamed);
}

+ (void)changeImageNamed{
    IMP currIMP = [self currentImageNamedIMP];
    IMP sdkIMP = [self sdkImageNamedIMP];
    if (currIMP != sdkIMP) {
        Method currentImageNamed = class_getClassMethod([UIImage class], @selector(imageNamed:));
        method_setImplementation(currentImageNamed, sdkIMP);
    }
}

+ (void)restoreImageNamed{
    Method currentImageNamed = class_getClassMethod([UIImage class], @selector(imageNamed:));
    method_setImplementation(currentImageNamed, orgImageNamedImp);
}

+ (nullable UIImage *)dwsdk_imageNamed:(NSString *)name {
    NSBundle *bundle = [NSBundle bundleForClass:[FrameworkNibSwizzle class]];
    UIImage *image = [UIImage imageNamed:name
                                inBundle:bundle
           compatibleWithTraitCollection:nil];
    NSLog(@"<DEBUG>load image:%@ from bundle:%@, result:%@", name, bundle, (image?@"YES":@"NO"));
    return image;
}
@end

這套處理方法的基本過程是:SDK加載的時(shí)候先記錄一下原始UIImage的imageNamed方法的實(shí)現(xiàn)函數(shù),然后在需要使用SDK的時(shí)候火架,替換實(shí)現(xiàn)方法鉴象,使用完SDK之后,還原實(shí)現(xiàn)方法何鸡。

這個(gè)方案對于使用SDK的過程有明確界限的情況還勉強(qiáng)可以纺弊,如果是使用SDK的同時(shí)還要使用其他代碼的情況,還是不能解決問題骡男。

這里請教讀者淆游,基于runtime有沒有可能完美解決此問題?

最后隔盛,為了完全解決imageNamed的問題犹菱,把項(xiàng)目的全部UIImage imageNamed方法統(tǒng)一改了一遍,改成調(diào)用[DWImageLoader imageNamed:]:

@implementation DWImageLoader

+ (UIImage *)imageNamed:(NSString *)name {
    NSBundle *bundle = [NSBundle bundleForClass:[self class]];
    return [UIImage imageNamed:name
                      inBundle:bundle compatibleWithTraitCollection:nil];
}
@end

哈哈吮炕,也就是說在工程里面明確的定義一個(gè)類和imageNamed方法腊脱,使用這個(gè)類的bundle來加載,完全解決了龙亲。

第三方庫沖突

這個(gè)問題比較普遍陕凹,比如悍抑,我們自己的SDK使用的AFN這個(gè)開源庫。使用我們SDK的人同時(shí)也使用AFN這個(gè)開源庫杜耙,這樣搜骡,在運(yùn)行的時(shí)候會(huì)有告警,大致的意思是:現(xiàn)在runtime找到兩個(gè)AFN的類佑女,我警告你浆兰,只有其中一個(gè)類會(huì)被加載,至于是加載SDK里的AFN還是別的地方的AFN珊豹,我也保證不了:(

這個(gè)問題沒有解決簸呈,目前我的情況是,確保AFN這些第三方庫是相同的版本的話店茶,就可以忽略這個(gè)告警

補(bǔ)充說明一下蜕便,動(dòng)態(tài)庫方式的framework和靜態(tài)庫方式framework的庫沖突不同:靜態(tài)庫如果有庫沖突,編譯的時(shí)候就會(huì)編譯錯(cuò)誤贩幻,錯(cuò)誤是說符號重復(fù)了轿腺,必須要重新調(diào)整framework把其中重復(fù)的代碼去掉才行。動(dòng)態(tài)庫在編譯的時(shí)候不會(huì)報(bào)錯(cuò)丛楚,執(zhí)行的時(shí)候告警族壳。

手工添加Pods

因?yàn)閒ramework是自己手動(dòng)添加的target,其中使用到一些原APP已經(jīng)引入的Pods庫趣些。需要手工添加需要使用的Pods庫

有讀者知道這種情況下還可以用Pod install給我新加的target關(guān)聯(lián)使用Pods嗎仿荆?

具體需要做如下一些事情(中間過程磕磕碰碰,不一定能把完整的步驟復(fù)原出來坏平,如果有遺漏拢操,還請?zhí)釂枺?br> a. 在Setting里首先添加一個(gè)自定義變量PODS_ROOT,后面的挺多配置都需要這個(gè)環(huán)境變量的


b. 設(shè)置Pods頭文件引用路徑

c. 添加Pods的庫引用(由于歷史原因舶替,我的Pods還是靜態(tài)庫令境,沒改成動(dòng)態(tài)framework)

最后,還有一些意想不到的事情顾瞪。

a. 有個(gè)客戶說使用我們的SDK之后舔庶,它的tabbar的樣式總覺得不正常了。查看了代碼才知道陈醒,原來我們APP里是通過這樣的方式修改了全局的tarbar:)

+ (void)load {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        Class class = [self class];
        
        SwizzleInstanceMethod(class,
                            @selector(viewDidLoad),
                            @selector(xx_viewDidLoad));
        SwizzleInstanceMethod(class,
                            @selector(setSelectedViewController:),
                            @selector(xx_setSelectedViewController:));
        SwizzleInstanceMethod(class,
                            @selector(setSelectedIndex:),
                            @selector(xx_setSelectedIndex:));
    });
}

在判斷SDK用不到這個(gè)功能的情況下惕橙,加了一個(gè)編譯選項(xiàng)關(guān)閉了這個(gè)動(dòng)作

b. 又有客戶說,用了我們SDK之后孵延,他的navigation bar的樣式總覺得不正常了吕漂。查看了代碼才知道,原來我們的APP里是通過[UINavigationBar appearance]修改了全局的樣式尘应。好吧惶凝,改成使用到SDK的界面才修改就好了吼虎。

c. 又有客戶說,用了我們的SDK之后苍鲜,每次進(jìn)入某個(gè)界面再出來就crashed了思灰。我那個(gè)去,還有這么神奇的事情混滔?仔細(xì)觀察界面洒疚,發(fā)現(xiàn)界面里有一個(gè)UITextView。有了a問題的經(jīng)驗(yàn)坯屿,全局搜索“+(void)load”油湖,有所發(fā)現(xiàn)了。我們APP用到了一個(gè)開源庫:UITextView+Placeholder领跛。這是一個(gè)給UITextView添加placeholder屬性的category乏德。其中有一個(gè)這樣的實(shí)現(xiàn)

+ (void)load {
    // is this the best solution?
    method_exchangeImplementations(class_getInstanceMethod(self.class, NSSelectorFromString(@"dealloc")),
                                   class_getInstanceMethod(self.class, @selector(swizzledDealloc)));
}
- (void)swizzledDealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    UILabel *label = objc_getAssociatedObject(self, @selector(placeholderLabel));
    if (label) {
        for (NSString *key in self.class.observingKeys) {
            @try {
                [self removeObserver:self forKeyPath:key];
            }
            @catch (NSException *exception) {
                // Do nothing
            }
        }
    }
    [self swizzledDealloc];
}

它需要在dealloc里面remove一些它自己add進(jìn)去的KVO。

巧的是吠昭,我們的客戶也用到這個(gè)庫喊括。我們知道,一個(gè)類的多個(gè)category的+ (void)load都是會(huì)一一加載并執(zhí)行的矢棚,于是SDK里的這個(gè)category執(zhí)行一次load郑什,客戶的代碼執(zhí)行一次load。這樣dealloc和swizzledDealloc就被調(diào)換了兩次蒲肋,相當(dāng)于就是沒有調(diào)換了蘑拯。也就是說在UITextView的dealloc的時(shí)候,并沒有調(diào)用到期望的swizzledDealloc方法肉津,于是强胰,UITextView釋放的時(shí)候,注冊了的KVO沒有被釋放妹沙,于是crash!

解決的辦法熟吏?好吧距糖,我當(dāng)時(shí)只是簡單的把這個(gè)庫里注冊KVO的代碼注釋了(項(xiàng)目時(shí)間緊,客戶急牵寺,壓力大呀)悍引。

這個(gè)category里的KVO其實(shí)是為了實(shí)現(xiàn)在設(shè)置了placeholder之后,修改UITextView的字體帽氓,大小等屬性的時(shí)候趣斤,placeholder能夠相應(yīng)的響應(yīng)這些變化表現(xiàn)出一樣的字體和大小來。


周末終于有一些自己的時(shí)間了黎休,想了一個(gè)自己覺得能完美解決的方案浓领,暫時(shí)提交到我自己的Github分支上UITextView+Placeholder玉凯。同時(shí)PR了一份給原作者。不過联贩,作者已經(jīng)寫完這個(gè)庫有好幾個(gè)月了漫仆,不知道還會(huì)不會(huì)再看一眼這個(gè)項(xiàng)目了:)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市泪幌,隨后出現(xiàn)的幾起案子盲厌,更是在濱河造成了極大的恐慌,老刑警劉巖祸泪,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吗浩,死亡現(xiàn)場離奇詭異,居然都是意外死亡没隘,警方通過查閱死者的電腦和手機(jī)拓萌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來升略,“玉大人微王,你說我怎么就攤上這事∑废” “怎么了炕倘?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長翰撑。 經(jīng)常有香客問我罩旋,道長,這世上最難降的妖魔是什么眶诈? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任涨醋,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘镜会。我一直安慰自己式撼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布溯警。 她就那樣靜靜地躺著,像睡著了一般狡相。 火紅的嫁衣襯著肌膚如雪梯轻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天尽棕,我揣著相機(jī)與錄音喳挑,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛伊诵,可吹牛的內(nèi)容都是我干的单绑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼日戈,長吁一口氣:“原來是場噩夢啊……” “哼询张!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起浙炼,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤份氧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后弯屈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜗帜,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年资厉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厅缺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宴偿,死狀恐怖湘捎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情窄刘,我是刑警寧澤窥妇,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站娩践,受9級特大地震影響活翩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜翻伺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一材泄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吨岭,春花似錦拉宗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至络它,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間歪赢,已是汗流浹背化戳。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人点楼。 一個(gè)月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓扫尖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親掠廓。 傳聞我的和親對象是個(gè)殘疾皇子换怖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容