前言:
隨着用戶的需求越來越多,對App的用戶體驗也變的要求越來越高。為了更好的應對各種需求:
①App架構:開發人員從軟件工程的角度,將App架構由原來簡單的MVC變成MVVM,VIPER等復雜架構。更換適合業務的架構,是為了后期能更好的維護項目。
②版本快速迭代:但是用戶依舊不滿意,繼續對開發人員提出了更多更高的要求,不僅需要高質量的用戶體驗,還要求快速迭代,最好一天出一個新功能,而且用戶還要求不更新就能體驗到新功能。為了滿足用戶需求,於是開發人員就用H5,ReactNative,Weex等技術對已有的項目進行改造。
③組件化:項目架構也變得更加的復雜,縱向的會進行分層,網絡層,UI層,數據持久層。每一層橫向的也會根據業務進行組件化。
盡管這樣做了以后會讓開發更加有效率,更加好維護,但是如何解耦各層,解耦各個界面和各個組件,降低各個組件之間的耦合度,如何能讓整個系統不管多么復雜的情況下都能保持“高內聚,低耦合”的特點?
1.引子
大前端React和Vue.路由的作用主要是保證視圖和URL的同步。當用戶在頁面進行操作的時候,應用會在若干個交互狀態中切換,路由則可以記錄下某些重要的狀態,比如用戶查看一個網站,用戶是否登錄、在訪問網站的哪一個頁面。用戶可以通過手動輸入或者與頁面進行交互改變URL,然后通過同步或者異步的方式向服務端發送請求獲取資源,成功后重新繪制UI。
2.App路由能解決那些問題:
1->點擊推送消息,要求外部跳轉到App內部一個很深層次的一個界面。比如微信的3D-Touch可以直接跳轉到“我的二維碼”。“我的二維碼”界面在我的里面的第三級界面。或者再極端一點,產品需求給了更加變態的需求,要求跳轉到App內部第十層的界面,怎么處理?
2->如何解除App組件之間和App頁面之間的耦合性?
3->如何能統一iOS和Android兩端的頁面跳轉邏輯?甚至如何能統一三端的請求資源的方式?
4->如果App出現bug了,如何不用JSPatch,就能做到簡單的熱修復功能?
5->如何在每個組件間調用和頁面跳轉時都進行埋點統計?每個跳轉的地方都手寫代碼埋點?利用Runtime AOP ?
6->如何在App任何界面都可以調用同一個界面或者同一個組件?只能在AppDelegate里面注冊單例來實現?
7->比如App出現問題了,用戶可能在任何界面,如何隨時隨地的讓用戶強制登出?或者強制都跳轉到同一個本地的error界面?或者跳轉到相應的H5,ReactNative,Weex界面?如何讓用戶在任何界面,隨時隨地的彈出一個View ?
3.APP跳轉實現
1->URL Scheme方式
比如說,在iPhone的Safari瀏覽器上面輸入如下的命令,會自動打開一些App:
//打開郵箱: mailto://
關於系統功能跳轉的URL匯總列表:https://www.jianshu.com/p/32ca4bcda3d1
2.Universal Links 方式
iOS 9.0新增加了一項功能是Universal Links,使用這個功能可以使我們的App通過HTTP鏈接來啟動App。
1.如果安裝過App,不管在微信里面http鏈接還是在Safari瀏覽器,還是其他第三方瀏覽器,都可以打開App。
2.如果沒有安裝過App,就會打開網頁。
具體設置:
1.App需要開啟Associated Domains服務,並設置Domains,注意必須要applinks:開頭。
2.域名必須要支持HTTPS。
上傳內容是Json格式的文件,文件名為apple-app-site-association到自己域名的根目錄下,或者.well-known目錄下。iOS自動會去讀取這個文件。具體的文件內容請查看官方文檔。
如果App支持了Universal Links方式,那么可以在其他App里面直接跳轉到我們自己的App里面。如下圖,點擊鏈接,由於該鏈接會Matcher到我們設置的鏈接,所以菜單里面會顯示用我們的App打開。
在瀏覽器里面也是一樣的效果,如果是支持了Universal Links方式,訪問相應的URL,會有不同的效果。如下圖:
4.App內組件路由設計
主要解決:
①各個頁面和組件之間的跳轉問題:
②各個組件之間相互調用
代碼高復用、方便測試
如何設計一個路由
Route實現
主工程與首頁模塊、分類、登錄模塊不直接建立關聯,而是先通過router(路由)與你要調用的模塊建立關系 ,從而實現各個模塊的解耦和復用、
①.OCTarget_index類(解耦+交互)
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface OCTarget_Index : NSObject - (id)action_home:(NSDictionary*)params; @end NS_ASSUME_NONNULL_END #import "OCTarget_Index.h" #import <UIKit/UIKit.h> @implementation OCTarget_Index - (id)action_home:(NSDictionary*)params { UIViewController *homeVC = [UIViewController new]; homeVC.title = @"首頁"; return homeVC; } @end
2.代碼實現:
HKOCRouter.h
#import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface HKOCRouter : UIView +(instancetype)shareInstance; - (id)openUrl:(NSString *)urlStr; //返回值id,外部調用,通過target 和 action 來唯一確認一個類里面的方法 - (id)performTarget:(NSString*)targetName action:(NSString*)actionName param:(NSDictionary*)params; @end NS_ASSUME_NONNULL_END
HKOCRouter.m
#import "HKOCRouter.h" @implementation HKOCRouter +(instancetype)shareInstance { static HKOCRouter * mediator; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ mediator = [[HKOCRouter alloc] init]; }); return mediator; } - (id)openUrl:(NSString *)urlStr { NSURL *url = [NSURL URLWithString:urlStr]; NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; //查詢 NSString *urlString = [url query]; //切割字符串 for (NSString *param in [urlString componentsSeparatedByString:@"&"]) { NSArray *elts = [param componentsSeparatedByString:@"="]; if (elts.count<2) continue; id firstEle = [elts firstObject]; id lastEle = [elts lastObject]; if (firstEle && lastEle) { [params setObject:lastEle forKey:firstEle]; } } NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""]; if ([actionName hasPrefix:@"native"]) { return @(NO); } id result = [self performTarget:url.host action:actionName param:params]; return result; } -(id)performTarget:(NSString *)targetName action:(NSString *)actionName param:(NSDictionary *)params { //這個目標的類名字符串 NSString * targetClassString = [NSString stringWithFormat:@"OCTarget_%@",targetName]; NSString *actionMethodString = [NSString stringWithFormat:@"action_%@",actionName]; Class targetClass = NSClassFromString(targetClassString); NSObject *target = [[targetClass alloc] init]; SEL action = NSSelectorFromString(actionMethodString); //判斷 if ([target respondsToSelector:action]) { return [self safePerformAction:action target:target param:params]; }else { SEL action = NSSelectorFromString(@"notFound:"); if ([target respondsToSelector:action]) { return [self safePerformAction:action target:target param:params]; }else { return nil; } } } //1.通過對象調用指定的方法 //2.傳參 - (id)safePerformAction:(SEL)action target:(NSObject*)target param:(NSDictionary*)params { NSMethodSignature *methodSig = [target methodSignatureForSelector:action]; if (methodSig == nil) { return nil; } //獲取這個方法返回值的地址 const char *retType = [methodSig methodReturnType]; //id 是可以返回任意對象,所以我們單獨處理基本變量。 NSInteger Bool Void if (strcmp(retType, @encode(NSInteger)) == 0) { NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig]; //為什么傳2?前面0個1這兩個位置已經被target和action給占了 [invocation setArgument:¶ms atIndex:2]; [invocation setTarget:target]; [invocation setSelector:action]; [invocation invoke]; NSInteger result = 0; [invocation getReturnValue:&result]; return @(result); } if (strcmp(retType, @encode(BOOL)) == 0) { NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig]; //為什么傳2?前面0個1這兩個位置已經被target和action給占了 [invocation setArgument:¶ms atIndex:2]; [invocation setTarget:target]; [invocation setSelector:action]; [invocation invoke]; NSInteger result = 0; [invocation getReturnValue:&result]; return @(result); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" return [target performSelector:action withObject:target withObject:params]; #pragma clang diagnostic pop } @end
使用:
UIViewController * vc = [[HKOCRouter shareInstance] openUrl:@"http://Index/home:"];
Index為組件索引;home為actionName;“:”,冒號表示帶參數.