前言
關(guān)于架構(gòu)優(yōu)化每個人都有不同的理解,這一點(diǎn)我深有體會,身邊的例子就是我跟一個同樣做iOS開發(fā)的小伙伴的分歧,大家都是你說服不了我,我也說服不了你的狀態(tài),去年我瘋狂癡迷于Casa Toloyum架構(gòu)師的理論(現(xiàn)在應(yīng)該是在天貓架構(gòu)師組),我記得有一段時間他是跟另一個架構(gòu)師在iOS路由器的設(shè)計(jì)上有一些不同的意見,兩人分別在網(wǎng)上寫文章來闡述自己的觀點(diǎn),你來我往,好不熱鬧。
我借這兩個例子就是來說明,對于架構(gòu)設(shè)計(jì),一千個人心中有一千種架構(gòu),KVRouter也只是基于我目前不太成熟的架構(gòu)理解開發(fā)出來的,我覺得這種架構(gòu)就是適合我的,以我目前的能力來說,我都能看到KVRouter某些不足的地方(好 > 壞),所以我希望能夠和你們一起分享,一起學(xué)習(xí),一起進(jìn)步。
盡管在架構(gòu)優(yōu)化之路上肯定會需要拋棄一些東西,也許會犧牲少量運(yùn)行性能來優(yōu)化項(xiàng)目的架構(gòu),也許會在開發(fā)上讓開發(fā)者多付出一些勞動來迎合這個新的架構(gòu),但我覺得這都是值得的,畢竟當(dāng)一個成熟的框架搭建起來之后,不管是在維護(hù)還是開發(fā)都能夠給予不小的幫助,就像當(dāng)初我在開發(fā)KVRouter的時候,公司的項(xiàng)目已經(jīng)算是成熟了,集成KVRouter的成本非常大,但是我還是決定更換上去,事實(shí)證明,KVRouter在后續(xù)的開發(fā)中讓我節(jié)省了很多力氣,包括后來的頁面動態(tài)定向方案也是基于KVRouter設(shè)計(jì)的。
這篇文章目的在于幫大家提供一種App架構(gòu)優(yōu)化的思路,不一定是最好的方案,但卻是我在架構(gòu)設(shè)計(jì)方向上前進(jìn)的一個小jio步。
1 KVRouter介紹
Git地址
KVRouter是一個可以解決傳統(tǒng)界面跳轉(zhuǎn)耦合的一個開源項(xiàng)目,在2017年初已經(jīng)在GitHub上開源,在我的公司iOS項(xiàng)目上已經(jīng)運(yùn)行了一年,可用性和穩(wěn)定性都得到了驗(yàn)證,期間解決了一些問題,現(xiàn)在我重新對該項(xiàng)目進(jìn)行重構(gòu),代碼和邏輯已經(jīng)優(yōu)化了不少,API設(shè)計(jì)也更加友好(由于API改了,我花了不少時間將原項(xiàng)目調(diào)整過來(╥╯^╰╥))。
備注:花了一個早上的時間熟悉了Swift,然后將KVRouter用Swift重寫了一遍,對于Swift還是深有體會的,Swift版本的KVRouter將會在文章最后簡單介紹一下。
1.1 傳統(tǒng)界面跳轉(zhuǎn)
我們先來看一段代碼。
#import "OneViewController.h"
#import "PushOneController.h" //需要引入目標(biāo)界面文件
@interface OneViewController ()
@end
@implementation OneViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor redColor];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
PushOneController * vc = [[PushOneController alloc] init];
vc.user = @"kevin"; //傳統(tǒng)賦值方式,如果該界面所需參數(shù)比較多,參數(shù)的增刪都會比較難維護(hù)
[self.navigationController pushViewController:vc animated:YES];
}
可以看到,傳統(tǒng)方式的界面跳轉(zhuǎn)對于下個界面基本上是保持著耦合關(guān)系,并且也不靈活,如果遇到可以靈活跳轉(zhuǎn)界面的需求基本上需要做很多邏輯去解決這個耦合關(guān)系,而且傳統(tǒng)的界面?zhèn)鲄⒎绞揭脖容^死板,無法做到靈活傳參。
1.2 使用了KVRouter的界面跳轉(zhuǎn)
還是先看代碼吧。
#import "OneViewController.h"
//不需要引入目標(biāo)界面文件
@interface OneViewController ()
@end
@implementation OneViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor redColor];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//當(dāng)目標(biāo)界面注冊了pushone這個url后,可以直接使用該API進(jìn)行push跳轉(zhuǎn),參數(shù)通過url攜帶或者后接參數(shù)字典進(jìn)行傳遞
[KVRouter openUrl:@"kv://pushone?id=1" parameter:@{@"user" : @"kevin"}];
}
可以看到,引入KVRouter后,整個跳轉(zhuǎn)邏輯變得簡潔,傳值也變得靈活。
1.3 KVRouter實(shí)現(xiàn)原理
KVRouter沒有使用一些高大上的技術(shù)去實(shí)現(xiàn),只是一種頁面解耦方案,而且我覺得有時候不需要刻意地使用比較生僻的技術(shù)去實(shí)現(xiàn)某些功能,能夠使用基礎(chǔ)庫的功能去實(shí)現(xiàn)的盡量不依靠一些黑科技。
1.3.1 注冊過程
無圖無真相

注冊比較簡單,主要是將URL跟頁面進(jìn)行匹配,然后將這個關(guān)系保存起來,其實(shí)內(nèi)部僅僅使用了一個字典去保存這個映射關(guān)系。
1.3.2 跳轉(zhuǎn)過程
同樣上圖

同樣沒有用到很復(fù)雜的技術(shù),只是使用基礎(chǔ)庫,更多的是對內(nèi)部的邏輯處理過程進(jìn)行優(yōu)化,保證不出錯,以及方便使用。
2 集成KVRouter
KVRouter已經(jīng)在GitHub和Cocoapod上發(fā)布。
2.1 Git集成
Git地址
開源項(xiàng)目包含一個Demo,以及Swift版本的KVRouter,都已經(jīng)在Demo里面描述清楚用法。
2.2 pod集成
使用以下命令集成。PS:僅對Object-C版本進(jìn)行了pod上傳,Swift版本僅限通過GitHub下載。
pod 'KVRouter', ' 1.0.0'
3 使用KVRouter
3.1 注冊
KVRouter的運(yùn)行前提就是頁面必須提前注冊進(jìn)KVRouter,KVRouter提供兩種方式進(jìn)行注冊。
1 本地配置文件注冊
該方式能夠批量注冊頁面,方便開發(fā)者本身項(xiàng)目已成熟的情況下集成KVRouter降低集成成本(不需要每個頁面都去執(zhí)行一遍注冊API),缺點(diǎn)是該種方式注冊的頁面僅能通過默認(rèn)方式初始化(init),并且無法添加自己的處理邏輯。
注冊信息保存在以下文件,請嚴(yán)格按照該格式輸入,也可以查看KVRouter初始化那部分文件解析代碼,修改解析規(guī)則。

2 API注冊(比較靈活,可以添加自己的處理邏輯)
API注冊,是我推薦你使用的方式,盡管初步看每個頁面都需要寫這么一段代碼去注冊頁面,但這確實(shí)能夠做到頁面的充分解耦,例如你刪除了這個頁面,不需要再去刪除配置文件的內(nèi)容,而且API注冊允許你參與到頁面的初始化過程來,由你本身來處理頁面的初始化,并且可以添加一些業(yè)務(wù)邏輯。
說明:在Object-C里面,當(dāng)類被加載(不是初始化)的時候會觸發(fā)load方法,KVRouter運(yùn)用了該特性進(jìn)行API注冊。
- 簡單注冊
+ (void)load {
//正常注冊,內(nèi)部使用init進(jìn)行頁面的初始化
[KVRouter registerUrl:@"pushone" withClass:[self class]];
}
- 自定義注冊
+ (void)load {
//自定義注冊
[KVRouter registerUrl:@"pushone" withClass:[self class] toHandler:^UIViewController *(NSDictionary *parameter) {
//如果該頁面是需要登錄才能訪問,可以在這里加一個判定,然后返回nil,避免邏輯錯誤
// if (未登錄) {
// return nil;
// }
//實(shí)現(xiàn)自定義初始化
PushOneController * vc = [PushOneController new];
//也可以在這里進(jìn)行賦值或者其他初始化邏輯
return vc;
}];
}
3.2 跳轉(zhuǎn)
KVRouter跳轉(zhuǎn)頁面支持push和present方式(其實(shí)也就這兩種方式)。
- push
//獲取到頁面實(shí)例后再跳轉(zhuǎn)
UIViewController * vc = [KVRouter getObjectWithUrl:@"pushone?id=1"];
if (vc) {
[self.navigationController pushViewController:vc animated:YES];
}
//以下為快速push,需要提前設(shè)置全局導(dǎo)航控制器
// [KVRouter openUrl:@"pushone"];
//攜帶參數(shù)
// [KVRouter openUrl:@"pushone?id=1"];
//攜帶多參數(shù)
// [KVRouter openUrl:@"pushone?id=1" parameter:@{@"user" : @"kevin"}];
//傳入導(dǎo)航控制器進(jìn)行跳轉(zhuǎn)
// [KVRouter openUrl:@"pushone" withNavigationController:self.navigationController];
PS:iOS的push界面需要一個導(dǎo)航控制器,如果你想使用快速跳轉(zhuǎn)的話需要提前設(shè)置KVRouter的全局導(dǎo)航控制器,內(nèi)部將會使用這個導(dǎo)航控制器進(jìn)行push跳轉(zhuǎn),如果不設(shè)置,那么KVRouter是不做跳轉(zhuǎn)的,另外,有些App是通過TabbarController來管理多頁面的,并且也是通過多個導(dǎo)航控制器來管理,那么每切換一個不同的導(dǎo)航控制器都需要更新KVRouter的導(dǎo)航控制器,這一點(diǎn)在Demo里面有體現(xiàn)
- present
//這個頁面是通過配置文件進(jìn)行注冊的,僅能通過init方法初始化
//如果需要在該控制器在包裝一層導(dǎo)航欄,可以獲取到該控制器實(shí)例后再自行包裝
// UIViewController * vc = [KVRouter getObjectWithUrl:@"presenttwo"];
// if (vc) {
// [self presentViewController:vc animated:YES completion:nil];
// }
//快速present
// [KVRouter presentUrl:@"presenttwo"];
//攜帶參數(shù)
// [KVRouter presentUrl:@"presenttwo?id=1"];
//攜帶多參數(shù),設(shè)置代理,協(xié)議代理是一種強(qiáng)耦合關(guān)系,無法取消
PresentTwoController * vc = (PresentTwoController*)[KVRouter presentUrl:@"presenttwo?id=1" parameter:@{@"user" : @"kevin"}];
vc.delegate = self;
//傳入來源控制器進(jìn)行present
// [KVRouter presentUrl:@"presenttwo" sourceViewController:self];
PS:快速present操作也需要提前設(shè)置來源控制器,一般使用App的根控制器即可,不需要頻繁更換,因?yàn)閜resent本來就是一種完全沉浸式的用戶體驗(yàn)
3.3 獲取參數(shù)
一般在跳轉(zhuǎn)頁面的時候都是需要傳值的,KVRouter使用一個很巧妙的設(shè)計(jì)來實(shí)現(xiàn)頁面的傳值,同樣遵循解耦原則。
@interface NSObject (KVRouter)
/**
發(fā)送參數(shù)
@param parameter 參數(shù)
*/
- (void)routerSendParameter:(NSDictionary *)parameter;
/**
用于傳參的分類方法
使用方法:
在控制器內(nèi)部重寫這個方法,如果有傳參,那么會調(diào)用這個方法
@param router 路由器
@param parameter 傳遞的參數(shù)
*/
- (void)router:(KVRouter *)router getParameter:(NSDictionary *)parameter;
@end
KVRouter使用了類別來給每個NSObject類都增加了一個方法,當(dāng)獲取到參數(shù)的時候會調(diào)用該方法傳值,我們都知道在Object-C所有類都繼承自NSObject,所以只需要在需要接受參數(shù)的頁面重寫該方法就能接受到參數(shù)。
接受參數(shù)的示例代碼如下:
- (void)router:(KVRouter *)router getParameter:(NSDictionary *)parameter {
NSLog(@"%@\n接收參數(shù)%@", NSStringFromClass([self class]), parameter);
}
4 關(guān)于Swift版本的介紹
Swift版本的KVRouter僅僅是使用了Swift語言重寫,各個方法的命名,實(shí)現(xiàn)原理以及使用方法都與Object-C一致,但是由于Swift與Object-C還是存在一些差異,所以Swift版本的KVRouter無法跟OC一樣使用API注冊方式進(jìn)行注冊頁面。
KVRouter的API注冊依賴于OC的類load方法進(jìn)行注冊,但是Swift不讓使用load方法,那么KVRouter基本上是被斷了一臂。

但是KVRouter的另一種注冊方式還是可用的,就是通過配置文件進(jìn)行批量注冊。
其實(shí)只是因?yàn)闊o法在load里面注冊頁面而已,但是KVRouter的注冊API還是可以使用,只不過目前我沒有找到一種好的時機(jī)去注冊頁面,如果你們找到了,務(wù)必聯(lián)系我,讓我也學(xué)習(xí)一下。
另外一個注意點(diǎn)就是,Swift版本需要提前設(shè)置一下項(xiàng)目名稱,這是由于Swift命名空間的特性,如果不設(shè)置,那么無法動態(tài)獲取類,也就無法初始化頁面了。
let projectname = "KVRouter_Swift"; //項(xiàng)目名稱(命名空間),不改的話獲取不到控制器實(shí)例,
至此,KVRouter已經(jīng)介紹完畢,接下來講一下基于KVRouter的掃一掃頁面動態(tài)定向方案,該方案同樣適用于web頁面點(diǎn)擊需要動態(tài)跳轉(zhuǎn)到原生頁面的需求,我將會在另一篇文章描述清楚,請戳以下文章鏈接傳送。