iOS組件化之路由設計(Router)


前言

隨着用戶的需求越來越多,對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方式

比如說,在iPhoneSafari瀏覽器上面輸入如下的命令,會自動打開一些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:&params 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:&params 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;“:”,冒號表示帶參數.

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM