一種簡單的iOS 的組件化設(shè)計

前言:如今iOS的開發(fā)中琉挖,組件化設(shè)計都成為一種標(biāo)配了鳖孤,要是不用上好像就趕不上潮流了。網(wǎng)上相關(guān)的方案分析也多如牛毛图柏。分析比較多的是URLRoute / 蘑菇街的 url-block 和 Protocol-Class / 和 casa 的target-action冠息。

一吼驶、 方案比較

URLRoute 與蘑菇街 url-block 和Protocol-Class 都不得不維護一套 url 或 Protocol 與組件間的映射炼鞠,通過 url 或 Protocol 去尋找組件戈擒。先不說效率問題捂敌,單是編寫和維護這個映射的代碼艾扮,使用者使用必須先注冊,然后才能使用占婉,模板代碼會充滿整個項目泡嘴。其實小的覺得這份映射是完全沒必要的。

casa 的target-action 不用維護映射表逆济,這個方案使用 “Target_ + 組件名字” 作 為key 通過runtime 尋找服務(wù)酌予。但是 hardcode 的地方太多磺箕,宏定義或者const 變量充滿了頭文件。這樣使用起來必須記憶很多const變量抛虫,編碼效率也稍微下降松靡。在調(diào)用組件的時候,cache 了 targetClass建椰。不曉得是否覺得NSClassFromString 方法不夠高效雕欺。

二、小的試把兩套方案結(jié)合起來棉姐,弄一個叫protocol-module模式的組件化

  1. 約定

oc 語言中充滿了約定屠列。比如 類的初始化方法 initXXX,系統(tǒng)合成的setter getter方法伞矩。更具體的是KVO觀察了一個類的屬性笛洛,被觀察的類變更為一個帶約定前綴的子類

- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
image.png

小的這套方案最主要的是編寫代碼的約定,思路如下圖


image.png

2.說明
小的打個比方乃坤。我們把主app稱為用戶苛让,用戶委托商家生產(chǎn)一個組件。用戶把需求和想怎么使用這個組件告訴商家湿诊∮埽“需求”的實現(xiàn)就是組件本身,“怎么使用”對應(yīng)的就是協(xié)議了枫吧。因為協(xié)議是雙方都知曉的浦旱,所以是公開的。這樣的好處是九杂,用戶不管組件是怎么實現(xiàn)的颁湖,我只要按協(xié)議使用就行了。

圖中public protocol 是定義好的組件調(diào)用協(xié)議例隆,基本協(xié)議規(guī)定了組件回調(diào)的 callback甥捺,和服務(wù)體 serverBody,也就是組件提供服務(wù)的 controller镀层。組件moduleA 镰禾、moduleB等為了使用者方便,都會實現(xiàn)一個interface的類唱逢。這個類遵守對應(yīng)的協(xié)議吴侦,類名必須是 “協(xié)議名 + SI”。比如協(xié)議名是ModuleA坞古,那么interface類的類名就是ModuleASI备韧。

1)編寫協(xié)議和組件
基本協(xié)議定義如下

#ifndef MoudleProtocol_ServerInterface
#define MoudleProtocol_ServerInterface @"SI"
#endif

@protocol BaseModule <NSObject>
@required
// server body
@property(nonatomic, weak) __kindof UIViewController *serverBody;

@optional
// callback
@property(nonatomic, copy) void (^callback) (id params);

@end

通過將 BaseModule 協(xié)議子協(xié)議化,定制特定組件調(diào)用協(xié)議痪枫。例如我們要調(diào)用組件moduleA织堂,我們可以定義一個叫ModuleA的協(xié)議叠艳,這個協(xié)議遵守BaseModule協(xié)議

@protocol ModuleA <BaseModule>

@required
// input
@property(nonatomic, strong) NSString *name;

@end

這個協(xié)議很簡單,只有一個屬性name易阳,指定了我們調(diào)用組件時要傳入的參數(shù)是name附较。

定好了協(xié)議,商家生產(chǎn)出組件后潦俺,他還要做一件事就是實現(xiàn)interface接口類ModuleASI
這個類實現(xiàn)如下

@interface MouduleASI : NSObject <ModuleA>

@end

@implementation MouduleASI

@synthesize callback;

@synthesize name;

@synthesize serverBody;

- (UIViewController *)serverBody {
  if (!serverBody) {
    UIStoryboard *sb  = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    serverBody = [sb instantiateViewControllerWithIdentifier: @"OhgRacViewController"];
    ((OhgRacViewController *)serverBody).interface = self;
  }
  return serverBody;
}
@end

好了拒课,代碼很簡單不是嗎,商家不會抱怨讓他多干活的黑竞。在MouduleASI的.m文件里捕发,商家簡單的返回一個serverBody疏旨。

2)大功告成了嗎很魂?還差一點

組件化最重要的路由忘了。不過放心檐涝,小的這個路由很簡單的遏匆,老規(guī)矩,先看代碼谁榜,路由定義

@interface OHGRouter : NSObject

+ (instancetype) router;
- (id) interfaceForProtocol:(Protocol *) p;
- (id) interfaceForURL:(NSURL *) url;

// for unit test
- (void) assertForMoudleWithProtocol:(Protocol *) p;
- (void) assertForMoudleWithURL:(NSURL *) url;

// navi type for vc
// push present
- (UIViewController *) findVcOfView:(UIView *) view;

@end

同樣簡單的要死幅聘,小的不用說各位看官都基本明白了,不過窃植,還是啰嗦一下...別打臉

  • (instancetype) router;這個方法略
  • (id) interfaceForProtocol:(Protocol *) p;這個方法是通過協(xié)議找到對應(yīng)的組件接口類帝蒿,實現(xiàn)如下
- (id)interfaceForProtocol:(Protocol *)p {
  Class cls = [self _clsForProtocol:p];
  return [[cls alloc] init];
}

- (Class) _clsForProtocol:(Protocol *) p {
  NSString *clsString = [NSStringFromProtocol(p) stringByAppendingString: MoudleProtocol_ServerInterface];
  return NSClassFromString(clsString);
}

好吧,就這么點代碼巷怜,獲取協(xié)議名葛超,拼接上前面public protocol的宏定MoudleProtocol_ServerInterface @"SI",就是我們的接口類名延塑。這就是我們?yōu)樯短崆白隽思s定的原因绣张。(類似蘋果實現(xiàn)的KVO),這個約定好處在于关带,我們完全不用維護 url-block / protocol-class / target-action等在內(nèi)存的映射侥涵,也就不需要注冊組件了。

  • (id) interfaceForURL:(NSURL *) url;這個方法可以處理遠端調(diào)用組件宋雏,我們稍后再說

// for unit test

  • (void) assertForMoudleWithProtocol:(Protocol *) p;
  • (void) assertForMoudleWithURL:(NSURL *) url;
    這兩個方法很有意思芜飘,在單元測試的時候,我們?nèi)菀淄ㄟ^協(xié)議和url檢查磨总,組件是否實現(xiàn)了 interface 接口類嗦明,看代碼
- (void)assertForMoudleWithProtocol:(Protocol *)p {
  if (![self _clsForProtocol:p]) {
    NSString *protocolName = NSStringFromProtocol(p);
    NSString *clsName = [protocolName stringByAppendingString: MoudleProtocol_ServerInterface];
    NSString *reason = [NSString stringWithFormat: @"找不到協(xié)議 %@ 對應(yīng)的接口類 %@ 的實現(xiàn)", protocolName, clsName];
    [self _throwException: reason];
  }
}

如果我們找不到接口類,說明組件還沒寫好舍败,拋出異常招狸。
以上敬拓,路由也就寫好了。

3)很快就完成了裙戏,看看怎么使用
在ViewController.m下

#import "ViewController.h"
#import "OHGRouter.h"

@implementation ViewController
- (IBAction)showModlue:(id)sender { 
  id <ModuleA> obj = [[OHGRouter router] interfaceForProtocol: @protocol(ModuleA)];
//  id<ModuleA> obj = [[OHGRouter router] interfaceForURL:[NSURL URLWithString:@"ModuleA://?name=xiaobaitu"]];
  obj.name = @"小白兔";
  obj.callback = ^(id params) {
    NSLog(@"%@", params);
  };
  [self.navigationController pushViewController: obj.serverBody animated: YES];
}
@end

在showModlue方法里乘凸,小的簡單的傳入?yún)f(xié)議ModuleA,就能調(diào)用組件了累榜,傳入?yún)?shù)name营勤, 實現(xiàn)回調(diào)callback。并且咱基本上沒有hardcode壹罚,所以還有編碼過程編譯器還能給溫馨提示葛作。在整個ViewController.m,咱并沒有導(dǎo)入任何組件相關(guān)的信息猖凛,路由里也沒有赂蠢,很簡單的咱就實現(xiàn)了組件的解耦。這一切就是因為我們一開始就明確了協(xié)議和組件接口的約定辨泳。

4)還沒完
前面說到路由- (id) interfaceForURL:(NSURL ) url;方法虱岂,從使用就可以看出來這事怎么回事了id<ModuleA> obj = [[OHGRouter router] interfaceForURL:[NSURL URLWithString:@"ModuleA://?name=xiaobaitu"]];
只要我們按格式|
協(xié)議名://參數(shù) *| 調(diào)用就大功告成了,例如: ModuleA://?name=xiaobaitu菠红。


好了第岖,完了

附上demo地址:https://github.com/ovcv8/a_simple_router

ps: 針對@inxx 同學(xué)提出的建議,又有一些新的想法试溯,俺打算開本篇的第二篇蔑滓,這里先打個樁??。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末遇绞,一起剝皮案震驚了整個濱河市键袱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌试读,老刑警劉巖杠纵,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異钩骇,居然都是意外死亡比藻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門倘屹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來银亲,“玉大人,你說我怎么就攤上這事纽匙∥耱穑” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵烛缔,是天一觀的道長馏段。 經(jīng)常有香客問我轩拨,道長,這世上最難降的妖魔是什么院喜? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任亡蓉,我火速辦了婚禮,結(jié)果婚禮上喷舀,老公的妹妹穿的比我還像新娘砍濒。我一直安慰自己,他們只是感情好硫麻,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布爸邢。 她就那樣靜靜地躺著,像睡著了一般拿愧。 火紅的嫁衣襯著肌膚如雪杠河。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天赶掖,我揣著相機與錄音感猛,去河邊找鬼七扰。 笑死奢赂,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颈走。 我是一名探鬼主播膳灶,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼立由!你這毒婦竟也來了轧钓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤锐膜,失蹤者是張志新(化名)和其女友劉穎毕箍,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體道盏,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡而柑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了荷逞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片媒咳。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖种远,靈堂內(nèi)的尸體忽然破棺而出涩澡,到底是詐尸還是另有隱情,我是刑警寧澤坠敷,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布妙同,位于F島的核電站射富,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏粥帚。R本人自食惡果不足惜辉浦,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茎辐。 院中可真熱鬧宪郊,春花似錦、人聲如沸拖陆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽依啰。三九已至乎串,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間速警,已是汗流浹背叹誉。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留闷旧,地道東北人长豁。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像忙灼,于是被迫代替她去往敵國和親匠襟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

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