iOS 路由方案演進(jìn):實現(xiàn)去中心化的自動注冊機制

在日常開發(fā)中,我們常常需要為每個功能頁面分配獨立的路由。傳統(tǒng)做法通常有兩種方案:

  1. 集中式管理路由:實現(xiàn)一個統(tǒng)一的路由類(如 URLHandler),在其中集中注冊所有路由。新功能上線時,需要手動向該類中添加路由邏輯。
  2. 模塊化管理路由:為每個功能實現(xiàn)一個對應(yīng)的路由類(如 FunNewURLHandler),再將該類名添加到 plist 或模塊靜態(tài)數(shù)組中,統(tǒng)一加載。

第一種方案隨著功能數(shù)量增長,URLHandler 會變得臃腫且耦合度高,維護(hù)難度大。第二種方式雖然更合理,但仍然存在一些問題:

  • 管理中心化,需要開發(fā)者主動注冊;
  • 會增加類的數(shù)量,影響啟動速度;
  • 注冊步驟繁瑣,若遺漏注冊路徑(如 plist 或模塊靜態(tài)數(shù)組),可能導(dǎo)致路由不生效。

那么是否有一種“去中心化”的方案,使得每個功能頁面只需定義自己的路由及處理邏輯,無需額外注冊操作,App 運行時可自動完成注冊?

這正是本文將介紹的實現(xiàn)方式。


利用 Mach-O 特性實現(xiàn)自動注冊

通過如下方式,我們可以在編譯階段將數(shù)據(jù)寫入 Mach-O 文件的指定段中:

__attribute__((used, section("__DATA,__urlhandle")))
  • used:防止 Release 環(huán)境下被鏈接器優(yōu)化掉;
  • section:指定寫入位置,這里是__DATA 段下的 __urlhandle 區(qū)域。

1. 路由結(jié)構(gòu)體定義

typedef void(*UrlHandleImp)(NSURL*, UINavigationController*, NSDictionary*);

struct URL_HANDLE {
    const char *url;
    UrlHandleImp handle;
};

url:存儲路由路徑;
handle:指向路由觸發(fā)時調(diào)用的函數(shù)指針。

通過 __attribute__ 聲明,將路由所對應(yīng)的 static struct URL_HANDLE寫入指定 section。App 運行時,使用 dyld 懶加載讀取,構(gòu)建路由表,并添加至 FBURLRouter中。

2. 路由注冊宏定義

為了方便注冊路由,定義了一套宏:

#define URL_EXPORT(...) \
    URL_EXPORT_WITH_LINE_CODE(__LINE__, __VA_ARGS__)

#define URL_EXPORT_WITH_LINE_CODE(line, ...) \
    static void URL_EXPORT_IMPL_NAME(line)(NSURL *, UINavigationController *, NSDictionary *); \
    url_macro_concat(URL_EXPORT_, url_macro_argcount(__VA_ARGS__))(line, __VA_ARGS__) \
    static void URL_EXPORT_IMPL_NAME(line)(NSURL *url, UINavigationController *navi, NSDictionary *parameters)

多參數(shù)展開示例(最多支持10個參數(shù)):

#define URL_EXPORT_10(line, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
    URL_EXPORT_9(line, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
    URL_EXPORT_EACH(line, _10, 10)

每個url被展開為:

#define URL_EXPORT_EACH(line, router, suffix) \
    __attribute__((used, section(FB_URL_EXPORT_SEGMENT "," URL_EXPORT_SECTION))) \
    static const struct URL_HANDLE URL_EXPORT_VAR_NAME(line, suffix) = { \
        (const char *)((char[sizeof(format_to_c_conststring(router))]){format_to_c_conststring(router)}), \
        URL_EXPORT_IMPL_NAME(line), \
    };

為了同時支持 NSString* 和 const char*:

#define format_to_c_conststring(a) \
(__builtin_choose_expr( \
    __builtin_types_compatible_p(__typeof__(a), NSString*), \
    #a, \
    a \
))

format_to_c_conststring("c")     //展開為 "c"
format_to_c_conststring(@"c")    //展開為 "@/"c/""

//  對于NSString轉(zhuǎn)化的c字符串,讀取時需要進(jìn)行處理
NSString *url =  [NSString stringWithUTF8String:entry.url];
if ([url hasPrefix:@"@\""] && [url hasSuffix:@"\""] && url.length >= 3) {
    url = [url substringWithRange:NSMakeRange(2, url.length-3)];
}

URL_EXPORT 最多支持10個參數(shù),宏使用__LINE__自動生成唯一函數(shù)和變量名,所以在同一源文件中可多次調(diào)用 URL_EXPORT。

3. 路由加載流程

  • 使用URL_EXPORT在編譯期間將路由注入到 __urlhandlesection中
  • 在 App 啟動時將AutoRegURLHandler注冊到 FBURLRouter;
  • 為了避免影響啟動速度,AutoRegURLHandler不立即構(gòu)建路由,延遲至調(diào)用 +patterns 時;
  • 構(gòu)建路由時,使用 dyld 讀取 __urlhandle section中所有的URL_HANDLE結(jié)構(gòu)體,構(gòu)建對應(yīng)的 _InnerURLHandler并加入 _RegisterHandlers,通知添加路由至 _RegisterURLs
  • 我們忽略了系統(tǒng)的動態(tài)庫,僅處理 .app 下的動態(tài)庫,同時支持通過修改 EXCLUDE_IMAGE_FOR_URL_HANDLE 忽略不必要模塊(目前默認(rèn)跳過 SDK 與 dylib)。

使用示例

單路由注冊
URL_EXPORT(@"/setting") {
    SettingViewController *settingVC = [[SettingViewController alloc] init];
    [navi pushViewController:settingVC animated:YES];
}
帶參數(shù)的路由注冊
URL_EXPORT("/{courseId}/paramPage") {
    NSString *courseId = parameters[@"courseId"];
    NSString *param1 = parameters[@"param1"];
    NSString *param2 = parameters[@"param2"];
    ParamViewController *vc = [[ParamViewController alloc] initWithCourseId:courseId param1:param1 param2:param2];
    vc.hidesBottomBarWhenPushed = YES;
    [navi pushViewController:vc animated:YES];
}
多路由注冊
#define FuncPageURL_1 @"/func1"
#define FuncPageURL_2 "/func2"
#define FuncPageURL_3 "/func3"

URL_EXPORT(FuncPageURL_1, FuncPageURL_2) {
    if ([url.absoluteString containsString:FuncPageURL_1]) {
        // 轉(zhuǎn)到 func 頁面 1
    } else if ([url.absoluteString containsString:FuncPageURL_2]) {
        // 轉(zhuǎn)到 func 頁面 2
    }
}
URL_EXPORT(FuncPageURL_3) {
    // 轉(zhuǎn)到 func 頁面 3
}

路由校驗機制

AutoRegURLHandler 提供校驗方法,用于檢查:

  • 路由是否注冊成功;
  • 是否存在重復(fù)路由;
Debug 環(huán)境下使用:
#ifdef DEBUG
    NSString *errorString = nil;
    BOOL urlValid = [AutoRegURLHandler checkValid:&errorString];
    NSAssert(urlValid, errorString);
#endif

?? 項目地址

完整實現(xiàn)與 Demo 示例:
?? https://github.com/linjunyi/URLHandlerDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容