iOS元件化解決方案

davidtim發表於2021-09-09

由於近期迭代週期變長,有時間想想程式碼持續改進的問題,再加上各業務模組程式碼從去年雜亂無章的狀態,到目前整體結構基本清晰,進而想到了模組之間解耦的問題,於是有了本文,關於iOS元件化的一些思路及最終的解決方案。

為什麼要元件化

技術界如今已存在很多關於元件化的解決方案,Class-Protocol、Target-Action等等,無論採用哪種方案,大家的目的都是為了解決程式碼龐大到一定規模時,依舊可以比較方便的進行管理和開發。這個時候就需要對各個業務模組進行梳理,在程式碼層面實現高內聚、低耦合,降低它們相互之間的變化帶來的影響,從而提升開發效率。

先來看看如下兩個圖,對比一下:


圖片描述

模組間跳轉現狀.jpg

圖片描述

中間層框架解耦方案.jpg

從圖中可以看出,在經過中間層框架跳轉分發之後,各業務模組之間不存在引用關係,程式碼相互隔離,呼叫層次清晰,實現了模組間的真正解耦,完美的過渡到元件化的流程。

框架內部的實現原理是什麼

這裡採用的是openURL: 和 openWithMapKey:的兩種呼叫方式,以便實現App之間跳轉及模組之間的跳轉操作,具體採用哪種方式之後會講到,下面來看一下中間層框架 簡單的呼叫過程:

圖片描述

中間層跳轉分發流程.jpg


接下來詳細介紹下流程中出現的方法及相關類:

openURL:

  • 支援App之間的跳轉
    支援設定、電話等系統Apps和info.plist白名單中的第三方Apps跳轉

  • 支援Module之間的跳轉
    傳參時僅支援NSString資料型別的賦值

  • 支援Module內部頁面的跳轉
    傳參時僅支援NSString資料型別的賦值

openWithMapKey:

  • 支援Module內部頁面的跳轉
    傳參時支援NSStringNSArrayUIImage等系統資料型別及自定義資料型別的賦值

  • 支援Module之間的跳轉
    為了模組間的解耦,傳參時建議使用系統資料型別,避免使用自定義資料型別

總結:兩種呼叫方法各有優勢,透過推送、Widget、第三方App等外部入口開啟你的App執行跳轉操作時,建議使用openURL:,其他情況的跳轉都採用openWithMapKey:的呼叫方式。

JCModuleMap

從上圖可以看出,模組間無論是透過 openURL: 還是 openWithMapKey: 方法,都是找到對應的JCModuleMap,然後實現最終的頁面跳轉。接下來看看如何透過JCModuleMap實現這一操作:

  • 子類化


    圖片描述

    JCModuleMap子類化.jpg

//  JCTestModuleMap.mNSString *const JCFirstLevelMapKey = @"JC_firstLevel";NSString *const JCSecondLevelMapKey = @"JC_secondLevel";NSString *const JCThirdLevelMapKey = @"JC_thirdLevel";NSString *const JCContentDetailMapKey = @"JC_contentDetail";@implementation JCTestModuleMap- (NSString *)mapKeyPrefix
{    return @"JC";
}

- (NSDictionary *)classesForMapKeys
{    return @{JCFirstLevelMapKey: NSClassFromString(@"JCFirstLevelViewController"),
             JCSecondLevelMapKey: NSClassFromString(@"JCSecondLevelViewController"),
             JCThirdLevelMapKey: NSClassFromString(@"JCThirdLevelViewController"),
             JCContentDetailMapKey: NSClassFromString(@"JCContentDetailViewController"),
             };
}@end
  • NSURL/mapKey對映原理


    圖片描述

    NSURL:mapKey對映原理.jpg

總結:openURL: 透過NSURL及子類化JCModuleMap中實現的mapKeyPrefix拼接對應的mapKey,然後和 openWithMapKey: 一樣,都是透過classesForMapKeys方法,獲取class-mapKey的對映關係,進而跳轉到module中對應class的檢視控制器頁面。

引數傳遞

  • 申明介面協議,以屬性的方式傳遞:

@protocol JC_contentDetail @property (nonatomic, strong) NSString *currentIndex;@property (nonatomic, strong) NSString *testId;@property (nonatomic, strong) NSArray *testArray;@end@interface JCContentDetailViewController : UIViewController@end
  • 透過屬性名-屬性值生成字典傳參:

+ (void)openContentDetailViewControllerWithCurrentIndex:(NSString *)currentIndex testId:(NSString *)testId testArray:(NSArray *)testArray
{    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:3];    if ([currentIndex isKindOfClass:[NSString class]]) {
        params[@"currentIndex"] = currentIndex;
    }    if ([testId isKindOfClass:[NSString class]]) {
        params[@"testId"] = testId;
    }    if ([testArray isKindOfClass:[NSArray class]]) {
        params[@"testArray"] = testArray;
    }
    [[JCNavigator sharedNavigator] openWithMapKey:JCContentDetailMapKey propertiesBlock:^NSDictionary *{        return params;
    } presented:YES animated:YES];
}

頁面展示效果設定

  • openURL: 時,透過JCModuleMap子類化實現的方法設定:

// 是否模態彈出(預設NO)- (BOOL)presentedForClass:(Class)viewControllerClass;// 是否有動畫(預設YES)- (BOOL)animatedForClass:(Class)viewControllerClass;
  • openWithMapKey:方法呼叫時直接設定:

// 是否模態及動畫- (void)openWithMapKey:(NSString *)mapKey propertiesBlock:(JCNavigatorPropertiesBlock)block presented:(BOOL)presented animated:(BOOL)animated;

Modules之間的解耦是怎麼實現的

好了,在實現JCModuleMap子類化及相關跳轉配置之後,現在最關鍵的操作來了,如何為各個modules之間提供通訊及呼叫的介面,才能最大限度的解決解耦的問題?廢話不多說,先來看看這段程式碼:

//  JCNavigator+JCTestModuleInterface.h@interface JCNavigator (JCTestModuleInterface)+ (void)openFirstLevelViewController;

+ (void)openSecondLevelViewController;@end//  JCNavigator+JCTestModuleInterface.m@implementation JCNavigator (JCTestModuleInterface)+ (void)load
{
    [[JCNavigator sharedNavigator] addModuleMap:[JCTestModuleMap new]];
}

+ (void)openFirstLevelViewController
{
    [[JCNavigator sharedNavigator] openWithMapKey:JCFirstLevelMapKey];
}

+ (void)openSecondLevelViewController
{
    [[JCNavigator sharedNavigator] openWithMapKey:JCSecondLevelMapKey];
}@end
  • 從程式碼可以看出:
    1)每個module宣告並實現對應的JCNavigator類別;
    2)在JCNavigator類別中實現load類方法,新增對應子類化JCModuleMap物件;
    3)JCNavigator類別標頭檔案中提供統一的對外介面,實現檔案中封裝內部呼叫細節,從而解決modules之間的耦合問題。

  • 下圖概述了實現過程:


    圖片描述

    Modules介面服務.jpg

總結

對於元件化的介紹,到這裡接近尾聲,整個解決方案有優點也有缺點,需要開發者各自權衡:

  • 優點
    1)支援Apps之間跳轉;
    2)支援Modules之間跳轉及通訊(引數傳遞);
    3)所有跳轉只基於JCNavigator中間層框架;
    4)可實現Modules之間解耦、互不依賴;
    5)無需額外處理Modules頁面間的層級關係。

  • 缺點
    1)需要子類化JCModuleMap,並將例項化物件新增到JCNavigator,增加了記憶體消耗;
    2)如果viewController類名或傳遞的引數發生改變,Xcode不會報錯也沒有警告,需及時維護子類化JCModuleMap的實現,並更新JCNavigator類別中的呼叫程式碼。

關於框架的更多實現細節,請關注github開原始碼。



作者:joych
連結:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3349/viewspace-2810711/,如需轉載,請註明出處,否則將追究法律責任。

相關文章