自從項目用了路由設計模式,每次測試告訴我有bug的時候,大哥都會微微一笑,告訴她這是服務器的bug
1. 什么是路由
在Web開發過程中,經常會遇到『路由』的概念。那么,到底什么是路由?簡單來說,路由就是URL到函數的映射。
2. router和route的區別
route 就是一條路由,它將一個URL路徑和一個函數進行映射,例如:
/users -> getAllUsers()
/users/count -> getUsersCount()
這就是兩條路由,當訪問 /users 的時候,會執行 getAllUsers() 函數;當訪問 /users/count 的時候,會執行 getUsersCount() 函數。
而 router 可以理解為一個容器,或者說一種機制,它管理了一組 route 。簡單來說, route 只是進行了URL和函數的映射,而在當接收到一個URL之后,去路由映射表中查找相應的函數,這個過程是由 router 來處理的。一句話概括就是 “ The router routes you to a route “。
上面亂七八糟一堆沒什么用,只有一句話是有用的:路由就是URL到函數的映射。例如“User/login”,User控制器下的login方法。
首先我們來比較一下我們以前的結構模式以及與 加入路由機制后的項目結構,實現路由機制,不僅需要一個映射文件,還需要一套路由管理機制,那么采用路由機制,我們的項目架構就跟原來不一樣了.
我們看一下它有哪些缺點:
(1)都要在當前頁面引入要跳轉頁面的class 類。這就造成了頁面的耦合性很高。
(2)遇到重大bug,不能夠及時的修復問題,需要等待更新發版后才能解決。
(3)推送消息或者是3D-Touch需求,要求直接跳轉到內部第10級界面,那么就需要寫一個入口跳轉到指定界面。
二、App路由能解決哪些問題
思考如下的問題,平時我們開發中是如何優雅的解決的:
1.3D-Touch功能或者點擊推送消息,要求外部跳轉到App內部一個很深層次的一個界面。
比如微信的3D-Touch可以直接跳轉到“我的二維碼”?!拔业亩S碼”界面在我的里面的第三級界面?;蛘咴贅O端一點,產品需求給了更加變態的需求,要求跳轉到App內部第十層的界面,怎么處理?
2.自家的一系列App之間如何相互跳轉?
如果自己App有幾個,相互之間還想相互跳轉,怎么處理?
3.如何解除App組件之間和App頁面之間的耦合性?
隨著項目越來越復雜,各個組件,各個頁面之間的跳轉邏輯關聯性越來越多,如何能優雅的解除各個組件和頁面之間的耦合性?
4.如何能統一iOS和Android兩端的頁面跳轉邏輯?甚至如何能統一三端的請求資源的方式?
項目里面某些模塊會混合ReactNative,Weex,H5界面,這些界面還會調用Native的界面,以及Native的組件。那么,如何能統一Web端和Native端請求資源的方式?
5.如果使用了動態下發配置文件來配置App的跳轉邏輯,那么如果做到iOS和Android兩邊只要共用一套配置文件?
6.如果App出現bug了,如何不用JSPatch,就能做到簡單的熱修復功能?
比如App上線突然遇到了緊急bug,能否把頁面動態降級成H5,ReactNative,Weex?或者是直接換成一個本地的錯誤界面?
7.如何在每個組件間調用和頁面跳轉時都進行埋點統計?每個跳轉的地方都手寫代碼埋點?利用Runtime AOP ?
8.如何在每個組件間調用的過程中,加入調用的邏輯檢查,令牌機制,配合灰度進行風控邏輯?
9.如何在App任何界面都可以調用同一個界面或者同一個組件?只能在AppDelegate里面注冊單例來實現?
比如App出現問題了,用戶可能在任何界面,如何隨時隨地的讓用戶強制登出?或者強制都跳轉到同一個本地的error界面?或者跳轉到相應的H5,ReactNative,Weex界面?如何讓用戶在任何界面,隨時隨地的彈出一個View ?
以上這些問題其實都可以通過在App端設計一個路由來解決。那么我們怎么設計一個路由呢?
+(void) processActionURL:(NSString*)actionURL andWithAction:(NSString*)action
{
NSDictionary * ACTION_MAP = @{
/*瀏覽器*/ @"browser":@"HSWebViewController",
/*資訊詳情*/ @"news_detail":@"HSNewsDetailController",
/*帖子詳情*/ @"topic_detail":@"HSTopicDatailTableViewController",
/*章節列表*/ @"material_detail":@"HSMaterialDetailTableViewController",
/*資料管理*/ @"material_manage":@"MaterialManage",
/*題目展示*/ @"question_detail":@"HSMaterialQuestionController",
/*文章詳情*/ @"article_detail":@"",
/*問卷調查*/ @"survey_detail":@"",
/*套餐詳情*/ @"meal_detail":@"",
/*課程詳情*/ @"course_detail":@"",
/*購買頁面*/ @"purchase":@"HSPayViewController",
/*列表頁面*/ @"search_list":@"HSComonSearchTBV",
/*用戶幫助頁面*/ @"guide":@"HSHelpCenterViewController",
/*二維碼掃描*/ @"qrcode":@"HSScanViewController",
/*領取課程*/ @"playcode":@"HSPlayCodeViewController",
/*我的課程*/ @"my_course":@"",
/*個人設置*/ @"profile_setting":@"HSPensonalSettingController",
/*系統設置*/ @"system_setting":@"HSSettingController",
/*用戶信息*/ @"user_info":@"HSUserInfoVC"
};
NSDictionary * dict = [HSNetUrlProcess queryDictWithUrlString:actionURL];
if ([action isEqualToString:@"browser"]) { // 0 跳轉到瀏覽器
HSWebViewController * webView = [[HSWebViewController alloc]init];
webView.urlStr = dict[HSOBJ_URL];
webView.hidesBottomBarWhenPushed = YES;
[[HSTool getCurrentVC].navigationController pushViewController:webView animated:YES];
}
else if([action isEqualToString:@"news_detail"])//1
{
HSNewsDetailController *newsVC=[[HSNewsDetailController alloc]init];
newsVC.param=dict;
[[HSTool getCurrentVC].navigationController pushViewController:newsVC animated:YES];
}
else if([action isEqualToString:@"topic_detail"])//2
{
HSTopicDatailTableViewController * topicDetail = [[HSTopicDatailTableViewController alloc]init];
topicDetail.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:topicDetail animated:YES];
}
else if([action isEqualToString:@"material_detail"])//3
{
HSMaterialDetailTableViewController *materialDeialVC=[[HSMaterialDetailTableViewController alloc]init];
materialDeialVC.param=dict;
[[HSTool getCurrentVC].navigationController pushViewController:materialDeialVC animated:YES];
}
else if([action isEqualToString:@"material_manage"])//4
{
MaterialManage * manage = [[MaterialManage alloc]init];
manage.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:manage animated:YES];
}
else if([action isEqualToString:@"system_setting"])//5
{
HVsystemSetingTableController * setting = [[HVsystemSetingTableController alloc]init];
[[HSTool getCurrentVC].navigationController pushViewController:setting animated:YES];
}
else if([action isEqualToString:@"profile_setting"])//6
{
HSPensonalSettingController * personSet = [[HSPensonalSettingController alloc]init];
[[HSTool getCurrentVC].navigationController pushViewController:personSet animated:YES];
}
else if([action isEqualToString:@"user_info"])//7
{
HSUserInfoVC * personSet = [[HSUserInfoVC alloc]init];
personSet.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:personSet animated:YES];
}
else if([action isEqualToString:@"guide"])//8
{
HSHelpCenterViewController * help = [[HSHelpCenterViewController alloc]init];
[[HSTool getCurrentVC].navigationController pushViewController:help animated:YES];
}
else if([action isEqualToString:@"qrcode"])//9
{
if (![FLLoginDataModel currentUser].isLogin) {
HSLoginController * log = [[HSLoginController alloc]init];
log.hidesBottomBarWhenPushed = YES;
[[HSTool getCurrentVC].navigationController pushViewController:log animated:YES];
return;
}
NSString *mediaType = AVMediaTypeVideo;
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied){
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"請在設置中打開APP相機權限" message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action =[UIAlertAction actionWithTitle:@"取消" style:(UIAlertActionStyleCancel) handler:nil];
[alert addAction:action];
[[HSTool getCurrentVC] presentViewController:alert animated:YES completion:nil];
return;
}
HSScanViewController * scan = [[HSScanViewController alloc]init];
HSNavigationViewController *nav =[[HSNavigationViewController alloc]initWithRootViewController:scan];
[scan.navigationController.navigationBar setHidden:YES];
[[HSTool getCurrentVC] presentViewController:nav animated:YES completion:nil];
}
else if([action isEqualToString:@"search_list"])//10
{
HSComonSearchTBV * searchtbv = [[HSComonSearchTBV alloc]init];
searchtbv.param = dict;
if (title.length>0) {
searchtbv.title = title;
}
[[HSTool getCurrentVC].navigationController pushViewController:searchtbv animated:YES];
}
else if([action isEqualToString:@"playcode"])//11
{
HSPlayCodeViewController * playcode = [[HSPlayCodeViewController alloc]init];
[[HSTool getCurrentVC].navigationController pushViewController:playcode animated:YES];
}
else if([action isEqualToString:@"question_detail"])//12
{
HSMaterialQuestionController * questionVC = [[HSMaterialQuestionController alloc]init];
questionVC.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:questionVC animated:YES];
}
else if([action isEqualToString:@"purchase"]) //購買界面 13
{
HSPayViewController * pay = [[HSPayViewController alloc]init];
pay.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:pay animated:YES];
}
else if([action isEqualToString:@"article_detail"])//14
{
HSNewsContentVC * article = [[HSNewsContentVC alloc]init];
article.paramDict = dict;
article.isArticleDetail = YES;
if (title.length>0) {
article.title = title;
}
[[HSTool getCurrentVC].navigationController pushViewController:article animated:YES];
}
else if([action isEqualToString:@"survey_detail"])//15
{
}
else if([action isEqualToString:@"meal_detail"]||[action isEqualToString:@"pack_detail"])//16
{
HSMealDetailViewController *detailVC=[[HSMealDetailViewController alloc]init];
detailVC.param=dict;
[[HSTool getCurrentVC].navigationController pushViewController:detailVC animated:YES];
}
else if([action isEqualToString:@"course_detail"])//17
{
HSCourseDetailViewController *detailVC=[[HSCourseDetailViewController alloc]init];
detailVC.param=dict;
if ([[HSTool getCurrentVC] isKindOfClass:[HSCourseDetailViewController class]]||[[HSTool getCurrentVC] isKindOfClass:[HSVideoDetailViewController class]]) {
detailVC.isPushToSelfVC = YES;
}
[[HSTool getCurrentVC].navigationController pushViewController:detailVC animated:YES];
}
else if([action isEqualToString:@"my_course"])//18
{
HSMyCourseViewController *mycourse = [[HSMyCourseViewController alloc]init];
[[HSTool getCurrentVC].navigationController pushViewController:mycourse animated:YES];
}
else if([action isEqualToString:@"teacher_detail"])
{
HVTeacherDetailVC * teacherDetail = [[HVTeacherDetailVC alloc]init];
teacherDetail.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:teacherDetail animated:YES];
}
else if([action isEqualToString:@"suggest"])
{
FeedBackVC * feedBack = [[FeedBackVC alloc]init];
feedBack.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:feedBack animated:YES];
}
else if([action isEqualToString:@"video_detail"])
{
HSVideoDetailViewController *videoDetail= [[HSVideoDetailViewController alloc]init];
videoDetail.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:videoDetail animated:YES];
}
else if([action isEqualToString:@"answer_detail"])
{//"answer/view?answer_id=d8c83aa9e8707475c545a8966ae052ea&token=357487b027fda52852792171a18ac2f4"
HSAnalysisViewController *analysisVC = [[HSAnalysisViewController alloc]init];
analysisVC.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:analysisVC animated:YES];
}
else if([action isEqualToString:@"qq"])
{
[HSTool jumpToQQWithQQnumber:dict[HSOBJ_URL]];
}
else if([action isEqualToString:@"phone"])
{
[HSTool jumpToPhoneWithPhonenumber:dict[HSOBJ_URL]];
}
else if([action isEqualToString:@"webview"])
{
HSWebViewController * webView = [[HSWebViewController alloc]init];
webView.urlStr = dict[HSOBJ_URL];
webView.hidesBottomBarWhenPushed = YES;
webView.shouldHideNavBar = YES;
[[HSTool getCurrentVC].navigationController pushViewController:webView animated:YES];
}
else if([action isEqualToString:@"login"])
{
HSLoginController * login = [[HSLoginController alloc]init];
[[HSTool getCurrentVC].navigationController pushViewController:login animated:YES];
}
else if([action isEqualToString:@"order_detail"])
{
OrderDetailController * detail = [[OrderDetailController alloc]init];
detail.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:detail animated:YES];
}
else if([action isEqualToString:@"web_login"])
{
SwipLoginVC * swip = [[SwipLoginVC alloc]init];
swip.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:swip animated:YES];
}
else if([action isEqualToString:@"aboutus"])
{
[[HSTool getCurrentVC].navigationController pushViewController:[[HvaboutVC alloc]init] animated:YES];
}
else if([action isEqualToString:@"message_detail"]){
HSMessageDetailViewController * detailVC = [[HSMessageDetailViewController alloc]init];
detailVC.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:detailVC animated:YES];
}
else if([action isEqualToString:@"score"]){
IntegralDetailVC *detailVC = [[IntegralDetailVC alloc]init];
[[HSTool getCurrentVC].navigationController pushViewController:detailVC animated:YES];
}
else if([action isEqualToString:@"close"]){
[[HSTool getCurrentVC].navigationController popViewControllerAnimated:YES];
}
else if([action isEqualToString:@"goods_detail"]){
HSGoodsDetailViewController * detailVC = [[HSGoodsDetailViewController alloc]init];
detailVC.param = dict;
[[HSTool getCurrentVC].navigationController pushViewController:detailVC animated:YES];
}
else if([action isEqualToString:@"self"]){
[AFNetworkTool HVDataCache:dict NetBlock:^(NSDictionary *json) {
} ErrorCode:^(int errorCode) {
} Fail:^{
} Setting:nil];
}
else if([action isEqualToString:@"update_store"])
{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:actionURL]];
// [HSCoverView showMessage:@"未指定跳轉內容" finishBlock:nil];
}
無論是路由還是中間人或者是組件化,其實感覺思想上都差不多,現在舉個例子還說明一下上面的代碼:例如界面上有一個按鈕,服務器就會給你一個action和action_url ,action表示你要跳轉的頁面,url表示下一個頁面網絡請求的地址,項目中幾乎所有的點擊事件都由服務器控制,服務器也會傳給你相應的action和url。
這樣的封裝,也為與H5交互提供了便捷,
[_bridge registerHandler:@"WebViewAction" handler:^(id data, WVJBResponseCallback responseCallback) {
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:[data dataUsingEncoding:(NSUTF8StringEncoding)] options:NSJSONReadingAllowFragments error:nil];
[HSTool processActionURL:[dic stirngWithSaftKey:@"action_url"] andWithAction:[dic stirngWithSaftKey:@"action"]];
}];
還記得以前每個H5頁面都要單獨一個controller,然后跟著后臺小哥一頓對接口,想一堆的標識符,現在只需要一個webVC,只需要兩行代碼就能實現,而且支持所有原生支持的事件,感覺寫的這么方便我好想要失業了。。。。。。
如法炮制的在UI顯示上我們同樣寫好了二十多種type的UI樣式,每種樣式對應一個cell,一個controller可以顯示二十幾種樣式,完全可以通過后端來配置一個自己想要的頁面,從二十幾種中選出你要展示的樣式,你可以用type1也可以用type2這個controller有很高的復用率。
曾經想過既然所有的事件跳轉都交給Tool去處理,那可不可以同樣實現一個view制作這樣的一個工具類,其實也可以,不過高度和布局實現比較麻煩。
但是需求來了也沒辦法,后來我直接把支持type解析的controller,用child controller,addsubview的方式直接把這個controller當成了一個萬能的view,但是由于controller是個tableview,而你卻非要把它當成一個view,所以高度是一個很麻煩的問題,后來用KVO解決了,感覺很尷尬,雖然能滿足需求,但是總感覺哪里怪怪的。
總結:
相對來說這樣的設計還是很方便的,低耦合,而且支持小小的熱更新,樣式不定,事件不定,都由服務器端來控制,用起來很方便,最重要的是,我發現我幾乎沒什么bug,有bug基本上全是后臺大兄弟的,哈哈
相關:
http://www.cnblogs.com/oc-bowen/p/6489070.html
https://casatwy.com/iOS-Modulization.html
http://www.cocoachina.com/ios/20170315/18893.html
http://blog.csdn.net/tianyitianyi1/article/details/60070763