iOS組件化:2.多target模式下的組件配合使用(OC語言)

業(yè)務場景:比如有一系列功能類似的app,只是包名不同藏鹊,個別頁面不同皮假,但其他大部分功能和接口數(shù)據(jù)都相同時,又不想為每個項目單獨創(chuàng)建工程衩椒,則可以在基于組件化的workspace中添加多target模式進行開發(fā)

針對這種需求蚌父,我的做法是先針對一個完整的app做模塊化拆分并做成framework的形式,在pods中引用毛萌;轉(zhuǎn)變?yōu)閒ramework的方法在iOS組件化第一篇內(nèi)容中有所講解,這里建議做本地組件化工程苟弛,不建議將各個組件放在github或其他服務器上,因為組件修改需要上傳再pod下來阁将,比較繁瑣也不利于后期的維護膏秫。

1.添加target后build settings中展現(xiàn)的樣式如下

建議在創(chuàng)建target前,先配置好project下所有環(huán)境變量做盅,這樣在原有target基礎上復制時缤削,會將主工程配置項一起自動復制到新target中

添加target

2.每個target下的資源配置方法如下圖

主工程目錄下添加的每個target配置信息

所添加的資源可以在xcode右邊控制面板中的target membership中關聯(lián)target(只有關聯(lián)了target的文件在編譯時才會被包含進安裝包中)

注意:除了.m文件不能與組件中的重復之外窘哈,其他格式的文件均可重名;主要是因為項目中的組件都是使用了framework(稱之為動態(tài)庫)的形式加載亭敢,每個組件中自有的文件都只包含在其framework文件中(類似c++的命名空間的概率)滚婉;不同于以往靜態(tài)庫.a文件(在主目錄下與所有文件同級,沒有文本域的概念帅刀,不可重名)让腹;

3.加載xib,圖片文件等方式方法的探索

場景一:組件中已封裝了制定好的viewcontroller.xib,但在某個target下該界面的布局會有所不同劝篷,但又無法在組件中做兼容性處理(因為未來項目變化是未知的)哨鸭,這時候需要在有變動的target下新建一個對應的viewcontroller.xib,這樣以來娇妓,在主目錄下和組件中各存在一個xib;這時就需要選擇性加載xib活鹰;

在選擇性加載xib前我們先看看程序在編譯后生成的文件存儲關系哈恰,按下圖步驟去找


通過此處跳轉(zhuǎn)進入安裝目錄


這里可看到target(即主工程目錄中的內(nèi)容)和pod中的framework文件是同級的


隨便進入一個framework看到由xib加密后生成而來的nib


以及我們隨便通過右鍵查看一個target包內(nèi)容后看到的此圖中的同樣的一個nib,這個nib即是主工程目錄中重寫的xib

針對以上文件存儲方式的初步了解后志群,我們來通過代碼實現(xiàn)xib的選擇性加載:

大家都知道uiviewcontroller初始化時可以通過方法initWithNibName:bundle:來選擇性加載xib着绷,而且即使只通過init方法初始化,其內(nèi)部實現(xiàn)也會去尋找是否有匹配的xib锌云,如果有則調(diào)用initWithNibName:bundle:方法荠医;為了使項目中統(tǒng)一使用init方法自動優(yōu)先選擇指定的xib,我們需要通過runtime中的swizlling技術來hook掉initWithNibName:bundle:方法來指定加載xib的優(yōu)先級桑涎;該hook方法中我們選擇先尋找主工程目錄中(上圖中的target即為主工程目錄文件)匹配的xib彬向,如果沒有再匹配各自framework中的xib。

所以我們創(chuàng)建了一個uiviewcontroller的分類攻冷,并添加如下代碼

//注:tc_swizzleSelector方法為自行封裝的swizzling方法娃胆,不懂的可以在網(wǎng)上搜索

@implementation UIViewController (runtime)

+ (void)load {

? ? static dispatch_once_t onceToken;

? ? dispatch_once(&onceToken, ^{

? ? ? ? Class class = [self class];

? ? ? ? //hook nib初始化方法,并選擇性加載bundle中的nib ? ? ? ?tc_swizzleSelector(class,@selector(initWithNibName:bundle:),@selector(initWithRTNibName:bundle:));

? ?});

}

- (id)initWithRTNibName:(nullableNSString*)nibNameOrNil bundle:(nullableNSBundle*)nibBundleOrNil {

//由于主工程目錄中如果存在xib等曼,那么nibNameOrNil變量實際上是沒有值的里烦,可能是底層已經(jīng)知道需要去主工程目錄找吧,在這里我們還是強制設置一個nib的name值禁谦,方便后面查找匹配

? ? NSString*nibName = (nibNameOrNil?nibNameOrNil:NSStringFromClass(self.class));

//1.主工程目錄下尋找xib(即上圖中的target目錄中)

? ?//判斷是否在主工程中存在(非多語言環(huán)境下會存在主工程目錄下)

? ? BOOL isNibExistInMainBundle = [[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:@"%@/%@.nib",[[NSBundle mainBundle] resourcePath],nibName]];

//檢查多語言文件下是否有xib(多語言環(huán)境下xib會存在Base.lproj文件中)

? ? if(!isNibExistInMainBundle) {

? ? ? ? nibName = [NSStringstringWithFormat:@"Base.lproj/%@",nibName];

? ? ? ? isNibExistInMainBundle = [[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:@"%@/%@.nib",[[NSBundle mainBundle] resourcePath],nibName]];

? ? }

//如果在主工程目錄下則直接調(diào)用swizzling的映射方法返回

? ? if(isNibExistInMainBundle) {

? ? ? ? return[selfinitWithRTNibName:nibNamebundle:[NSBundlemainBundle]];

? ? }


//2.在framework中尋找xib(注意胁黑,[NSBundle bundleForClass:[self class]]是獲取當前類所在bundle,可以理解為該bundle是一個framework)

? ? nibName = (nibNameOrNil?nibNameOrNil:NSStringFromClass(self.class));

? ? //判斷是否在組件中存在(非多語言環(huán)境下查找)

? ? BOOL isNibExist = [[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:@"%@/%@.nib",[[NSBundle bundleForClass:[self class]] resourcePath],nibName]];

? ? if(!isNibExist) {//檢查多語言文件下是否有xib

? ? ? ? nibName = [NSStringstringWithFormat:@"Base.lproj/%@",nibName];

? ? ? ? isNibExist = [[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:@"%@/%@.nib",[[NSBundle bundleForClass:[self class]] resourcePath],nibName]];

? ? }

? ? if(isNibExist) {//framework中存在則返回

? ? ? ? return[selfinitWithRTNibName:nibNamebundle:[NSBundlebundleForClass:[selfclass]]];

? ? }

//3.如果以上都未找到xib州泊,直接調(diào)用swizzling的映射方法返回

? ? return[selfinitWithRTNibName:nibNameOrNilbundle:nibBundleOrNil];

}

@end

場景二:uiimage類的圖片讀取有兩種丧蘸,①通過imageNamed:方法初始化,這種無非是hook imageNamed方法并指定bundle路徑加載拥诡;②通過xib中UIimageview控件加載圖片触趴,這種模式如果要指定bundle圖片氮发,也需要hook,但要特殊處理冗懦;先講一下xib加載的原理:xib在被解析時會執(zhí)行initWithCoder歸檔方法即解碼爽冕,那么xib中的控件實際上也會通過歸檔方式解壓處理,但我們看到的xib中的圖片控件雖然是uiimageview披蕉,但在解析時其中的image會映射為UIImageNibPlaceholder類颈畸,大致也可判斷UIimage或許是個類簇,所以我們需要hook掉 ?UIImageNibPlaceholder的initWithCoder方法來指定需要加載的圖片資源没讲;

下面貼出uiiamge的runtime代碼:

@implementationUIImage (FXExtensions)

//注:tc_swizzleClassSelector是封裝的在同一個類中做swizzling操作的方法眯娱;tc_swizzle2InstanceSelector是封裝的對兩個類中的兩個實例方法做swizzling操作的方法

+ (void)load {

? ? staticdispatch_once_tonceToken;

? ? dispatch_once(&onceToken, ^{

? ? ? ? //通過imageNamed方法選擇性加載圖片資源

? ? ? ? Class clazz = [self class];

? ? ? ? tc_swizzleClassSelector(clazz,@selector(imageNamed:),@selector(rtc_imageNamed:));

? ? ? ? //通過xib中加載圖片來選擇性加載圖片資源

//注意:這里使用字符串拼接方式來組合成私有類名是為了避免蘋果代碼審核發(fā)現(xiàn)調(diào)用私有類

? ? ? ? NSString *imgNibClassName = [[@"UIImage" stringByAppendingString:@"Nib"] stringByAppendingString:@"Placeholder"];

? ? ? ? ClassimgNibClass =NSClassFromString(imgNibClassName);

//這里是借用uiimage共有類添加的自定義方法initWithCoderForNib ,用來與UIImageNibPlaceholder類的initWithCoder方法做swizzling呼喚(這里就是典型的hook私有類的方法的做法)

? ? ? ? tc_swizzle2InstanceSelector(imgNibClass ,clazz ,@selector(initWithCoder:),? @selector(initWithCoderForNib:));

? ? });

}

//優(yōu)先使用主工程資源

- (id)initWithCoderForNib:(NSCoder*)aDecoder {

? ? NSString*resourceName = [aDecoderdecodeObjectForKey:@"UIResourceName"];

? ? UIImage*image = [UIImageimageNamed:resourceName];

? ? if(image) {

? ? ? ? returnimage;

? ? }

? ? return? [self initWithCoderForNib:aDecoder];

}

//優(yōu)先使用主工程資源

+ (nullableUIImage*)rtc_imageNamed:(NSString*)name {

//1.獲取主工程資源圖片

? ? UIImage *image = [UIImage imageNamed:name inBundle:[NSBundle mainBundle] compatibleWithTraitCollection:nil];

//2.如果主工程未獲取到爬凑,再執(zhí)行本方法回到來獲取當前framework中的圖片

if(!image) {

? ? ? ? image = [self rtc_imageNamed:name];

? ? }

? ? return image;

}

@end

場景三:關于UINib類的hook用途

這里講UINib是因為所有xib解析后都會通過uinib來完成ui的加載徙缴;

除了UIViewController的xib加載比較特殊外,其他自定義的xib均可通過UInib來指定加載方式;

所以需要hook掉UINib的初始化方法?initWithNibName:directory:bundle:

(針對UIViewController的xib加載例外做一個解釋:

大家應該注意到既然我可以通過hook Uinib的方法來指定xib的加載嘁信,為什么要要hook UIViewController的初始化方法呢于样?其實UIViewController初始化執(zhí)行initWithNibName:bundle:方法后其內(nèi)部還是會調(diào)用UINib類來完成最終的初始化,通過斷點即可判斷出來潘靖;那么就有疑問了穿剖,既然兩個方法都設置了加載xib的順序,豈不是重復了嗎卦溢?首先需要解釋一下UIViewController的初始化方法默認只會尋找mainBundle中的xib糊余,初始化時默認bundle參數(shù)為空,且僅當mainbundle中存在xib時,才會去初始化UINib单寂,所以我們需要在UIViewController中做hook處理確保bundle不為空贬芥,就會初始化UINib了。但為了兼容其他方式的xib加載凄贩,我們只能也hook UINib做選擇性加載xib了)

而uitableviewcell或uiview相關的自定義xib誓军,會默認去執(zhí)行UINib的初始化方法?initWithNibName:directory:bundle:,也不會存在像UIViewController這樣的問題疲扎;

下面貼出UINib的hook方法昵时,同樣是通過在分類中進行runtime操作

@implementationUINib (FXExtensions)

+ (void)load {

? ? staticdispatch_once_tonceToken;

? ? dispatch_once(&onceToken, ^{

? ? ? ? Classclazz = [selfclass];

? ? ? ? //@"initWithNibName:directory:bundle:"

? ? ? ? NSString *selStr = [[@"initWithNibName:" stringByAppendingString:@"directory:"] stringByAppendingString:@"bundle:"];

? ? ? ? SELoriginalSelector =NSSelectorFromString(selStr);

? ? ? ? SELswizzledSelector =@selector(initWithNibNameRTC:directory:bundle:);

? ? ? ? tc_swizzleSelector(clazz, originalSelector, swizzledSelector);

? ? });

}

//優(yōu)先使用主工程中的xib

- (id)initWithNibNameRTC:(NSString*)name directory:(id)dir bundle:(NSBundle*)bundle {

//1.查找mainbundle

? ? if ([UINib isNibExistInBundle:[NSBundle mainBundle] nibName:name]) {

? ? ? ? return [self initWithNibNameRTC:name directory:dir bundle:[NSBundle mainBundle]];

? ? }

//2.查找當前傳入的bundle

? ? if(bundle&&[UINib isNibExistInBundle:bundle nibName:name]) {

? ? ? ? return [self initWithNibNameRTC:name directory:dir bundle:bundle];

? ? }

//3.通過nib名稱獲取bundle并在其中查找

? ? NSBundle*currentBundle = [UINib fetchBundleWithNibName:name];

? ? if(currentBundle&&[UINibisNibExistInBundle:currentBundlenibName:name]) {

? ? ? ? return[selfinitWithNibNameRTC:namedirectory:dirbundle:currentBundle];

? ? }

? ? return [self initWithNibNameRTC:name directory:dir bundle:bundle];

}

//先查詢bundle目錄中的nib文件,如果沒有就查詢bundle下的Base.lproj下的nib文件

+ (BOOL)isNibExistInBundle:(nonnullNSBundle*)bundle nibName:(nonnullNSString*)nibName {

? ? BOOL isNibExist = [[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:@"%@/%@.nib",[bundle resourcePath],nibName]];

? ? if(!isNibExist) {

? ? ? ? isNibExist = [[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:@"%@/Base.lproj/%@.nib",[bundle resourcePath],nibName]];

? ? }

? ? returnisNibExist;

}

//通過nibName生成獲取bundle

+ (NSBundle*)fetchBundleWithNibName:(nonnullNSString*)nibName {

? ? Classclazz =NSClassFromString(nibName);

? ? if(clazz) {

? ? ? ? return [NSBundle bundleForClass:clazz];

? ? }

? ? return nil;

}

@end

總結:本節(jié)主要針對多target模式下椒丧,如何進行資源文件的加載操作壹甥,做了具體分解操作;并對底層加載原理做了淺顯的探索壶熏,希望能給同行小伙伴們提供一些有價值的參考句柠。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子溯职,更是在濱河造成了極大的恐慌精盅,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谜酒,死亡現(xiàn)場離奇詭異叹俏,居然都是意外死亡,警方通過查閱死者的電腦和手機僻族,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門粘驰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人述么,你說我怎么就攤上這事蝌数。” “怎么了度秘?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵顶伞,是天一觀的道長。 經(jīng)常有香客問我剑梳,道長枝哄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任阻荒,我火速辦了婚禮,結果婚禮上众羡,老公的妹妹穿的比我還像新娘侨赡。我一直安慰自己,他們只是感情好粱侣,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布羊壹。 她就那樣靜靜地躺著,像睡著了一般齐婴。 火紅的嫁衣襯著肌膚如雪油猫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天柠偶,我揣著相機與錄音情妖,去河邊找鬼。 笑死诱担,一個胖子當著我的面吹牛毡证,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蔫仙,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼料睛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起恤煞,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤屎勘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后居扒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體概漱,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年苔货,在試婚紗的時候發(fā)現(xiàn)自己被綠了犀概。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡夜惭,死狀恐怖姻灶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诈茧,我是刑警寧澤产喉,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站敢会,受9級特大地震影響曾沈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鸥昏,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一塞俱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吏垮,春花似錦障涯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至遗嗽,卻和暖如春粘我,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痹换。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工征字, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晴音。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓柔纵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锤躁。 傳聞我的和親對象是個殘疾皇子搁料,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

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