1. 程式人生 > >iOS專案元件化解耦

iOS專案元件化解耦

最近給公司的一個iOS專案進行元件化解耦。本身專案早期開發就不是很規範,而且剛剛開始熟悉這個專案對業務方面也不是很熟悉所以並沒有對所有的模組進行元件化。而且元件化解耦後還存在一些問題在文章中都會寫出來。
原理和蘑菇街 App 的元件化之路類似,但是也有一些不同並沒有加入「元件A」要呼叫「元件B」的某個方法這種業務場景。所有元件化的模組都是「元件A」要呼叫「元件B」的這種情況。「元件A」與「元件B」之間是透明的

為何要對專案元件化

  • 對每個模組間相互呼叫解耦
  • 統一wap與本地呼叫

首先說一下元件化帶來的最大好處就是給每個模組間解耦。之前模組間呼叫不得不相互引用,這就導致了各個模組間相互依賴。想象一種場景:A,B,C,D是四個VC,四個VC之間是這樣的關係,A與B相互跳轉、B與C相互跳轉、D與A,B相互跳轉。它們之間的關係如下圖:

而元件化在專案中引入了Mediator物件與Action物件,引入這兩個物件後,A,B,C,D之間的關係如下圖:

這樣結構就很清楚了而且不管多少個元件結構都是這個樣子。下面會說明Mediator物件與Action物件的實現。

對於一個App來說必不可免的會有wap頁面呼叫Native的功能。通過URL註冊實現元件化方案可以實現同一個URL即可以在本地呼叫元件也可以在wap頁面呼叫元件。這樣原本要在兩處同時處理的邏輯統一放在了Mediator物件中處理。
當然這樣做有一個缺點就是如果wap頁面呼叫與本地呼叫同一個模組需要做不同的處理時無法區分是在哪裡呼叫的,因為都是通過一樣的URL

來呼叫的。目前在我優化的專案中並沒有這樣的需求。所以當前的方案是可行的。如果真的要區分的話可以通過傳遞不同的引數來確定是從哪裡發起的呼叫。

元件化方案的實現

通過程式啟動時註冊URL來實現元件化,URL註冊用通過JLRoutes實現的。
具體使用如下:


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    JLRoutes *routes = [JLRoutes routesForScheme:scheme_url];

    [routes addRoute: LSYKey1 handler:^BOOL
(NSDictionary<NSString *,NSString *> * _Nonnull parameters) { //要處理的邏輯 return YES; //判斷返回值來決定是否執行該處程式碼 }]; }

在上面的程式碼中scheme_url是app自定義的協議,LSYKey1URL的路徑,parametersURL的引數。通過程式啟動初始化JLRoutes物件,並註冊不同的URL,當嘗試開啟scheme_url協議的URL時就會執行對應註冊路徑下block內的程式碼。

-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation   //當程式嘗試開啟URL或者處理完從其它應用返回時會呼叫該方法 (不同版本的系統需要實現的方法不同)
{   
    if ([url.scheme isEqualToString:scheme_url]) {
       return  [JLRoutes routeURL:url];
    }
    return YES;
}

如果嘗試開啟scheme_url://LSYKey1?oid=123&amount=456這樣的url時就會執行上面程式碼中註冊的block。通過parameters取得該url的引數。

    JLRoutes *routes = [JLRoutes routesForScheme:scheme_url];

    [routes addRoute: LSYKey1 handler:^BOOL(NSDictionary<NSString *,NSString *> * _Nonnull parameters) {
        NSInteger oid = [parameters[@"oid"] integerValue]; //獲取url中oid引數
        NSInteger amount = [parameters[@"amount"] integerValue]; //獲取url中amount引數

        return YES; 
    }];

上面說明如何通過JLRoutes註冊URL的,具體元件化解耦是Mediator物件與Action物件配合JLRoutes來實現的。
首先我們需要有一個檔案來統一管理本地模組的Key值,這裡在ComponentKey.h檔案中進行管理

ComponentKey.h

#import <Foundation/Foundation.h>

static NSString * const LSYKey1 = @"LSYKey1";

static NSString * const LSYKey2 = @"LSYKey2";

static NSString * const LSYKey3 = @"LSYKey3";

...

Mediator物件實現的方法如下:

Mediator.h

#import <Foundation/Foundation.h>

@interface Mediator : NSObject

/**
 JLRountes註冊的url
 */
+(void)componentRegister;

/**
 本地通過key開啟模組

 @param key 模組key
 @param dic 傳遞的引數
 */
+(void)openComponentForKey:(NSString *)key parameter:(NSDictionary *)dic;

/**
 該方法通過執行時的機制讓Mediator物件與Action物件解耦並將訊息轉發給Action物件執行

 @param targetName Action物件名
 @param actionName 方法名
 @param params     傳遞的引數
 */
+(void)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params;
@end

Mediator.m

@implementation Mediator


+(void)componentRegister
{
    JLRoutes *routes = [JLRoutes routesForScheme:scheme_url];

    [routes addRoute: LSYKey1 handler:^BOOL(NSDictionary<NSString *,NSString *> * _Nonnull parameters) {
        [self performTarget:@"Action" action:@"performActionA" params:parameters];
        return YES;
    }];

        [routes addRoute: LSYKey2 handler:^BOOL(NSDictionary<NSString *,NSString *> * _Nonnull parameters) {
        [self performTarget:@"Action" action:@"performActionB" params:parameters];
        return YES;
    }];


}
+(void)openComponentForKey:(NSString *)key parameter:(NSDictionary *)dic
{
    //將本地呼叫傳入的Key與引數轉拼接成url最後通過JLRoutes處理
    if (!key.length) {
        return;
    }
    NSMutableString *urlString = [NSMutableString stringWithString:[NSString stringWithFormat:@"%@://%@",scheme_url,key]];
    [urlString appendString:@"?"];
    [dic enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        [urlString appendString:[NSString stringWithFormat:@"%@=%@&",key,obj]];
    }];
    [urlString deleteCharactersInRange:NSMakeRange(urlString.length-1, 1)];
    NSURL *url = [NSURL URLWithString:[urlString copy]];
    [JLRoutes routeURL:url];
}

+(void)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params
{
    NSString *targetClassString = [NSString stringWithFormat:@"%@", targetName];
    NSString *actionString = [NSString stringWithFormat:@"%@:", actionName];

    Class targetClass = NSClassFromString(targetClassString);
    id target = [[targetClass alloc] init];
    SEL action = NSSelectorFromString(actionString);

    if ([target respondsToSelector:action]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
         [target performSelector:action withObject:params];
#pragma clang diagnostic pop
    } else {
        // 這裡是處理無響應請求的地方

        }
    }
}
@end

Action物件是處理從Mediator物件轉發過來的訊息。上面分別執行了performActionAperformActionB的方法。我們可以根據功能給這些方法分類,如果performActionAperformActionB的方法都是執行跳轉操作,那麼我們新增一個Action(Jump)的category專門來處理跳轉操作。將這兩個方法寫入category中。

#import "Action+Jump.h"

@implementation Action (Jump)
-(void)performActionA:(NSDictionary *)params
{
    //跳轉A介面
}
-(void)performActionB:(NSDictionary *)params
{
    //跳轉B介面
}
@end

此時我們想要在本地呼叫LSYKey1模組並要傳入oid與amount這兩個引數只要執行下面程式碼即可:

[Mediator openComponentForKey:LSYKey1 parameter:@{@"oid":@123,@"amount":@"456"}];

遠端呼叫直接返回:

    [JLRoutes routeURL:url];

因為我們在程式啟動已經註冊了所有的url

    [Mediator componentRegister];

存在的問題

上面雖然實現了元件化但是還是存在一些問題需要後續改進

  • 本地呼叫與遠端呼叫混在一起無法區別開
  • 無法實現「元件A」要呼叫「元件B」的某個方法這種業務場景
  • 元件過多有可能影響程式啟動速度

相關推薦

iOS專案元件化解

最近給公司的一個iOS專案進行元件化解耦。本身專案早期開發就不是很規範,而且剛剛開始熟悉這個專案對業務方面也不是很熟悉所以並沒有對所有的模組進行元件化。而且元件化解耦後還存在一些問題在文章中都會寫出來。 原理和蘑菇街 App 的元件化之路類似,但是也有一些不同

元件化解的架構設計思考

目錄 元件化 一個需求引發的思考 業務與實現分離 為啥要這麼做呢? 元件之間的通訊 小結 元件化 最近幾天在整理專案中的要點,元件化相信大家都不陌生,還是複用以前的一張專案架構圖,可以看到,專案的架構目前看起來比較清晰了,在最下層沉

iOS專案元件化搭建

點選上方“iOS開發”,選擇“置頂公眾號”關鍵時刻,第一時間送達!專案元件化,顧名思義,就是將專

大型專案模組化解實踐

軟體的高內聚,低耦合是一個成熟專案的目標,也對專案的持續發展有利。隨著一個商業軟體的發展和功能的豐富擴充套件,對應的軟體專案會逐漸變得龐大,後期的維護工作之一便是模組化解耦。 本文針對近期在iOS專案中的模組化解耦進行簡單探討,歡迎大家一起交流。 按照 京東

iOS 元件化架構及建立私有專案元件

一、元件化架構的產生        隨著移動網際網路的不斷髮展,很多程式程式碼量和業務越來越多,現有的單一式架構已經不能滿足公司發展的需求,很多專案都面臨著無法繼續迭代或迭代成本很高,而不得不重構的問題。           我在以前的工作中許多工作任務就是重構專案。單一式架

iOS專案跳轉解實戰(一)

今天寫的這個主題分為四篇文章,今天是第一篇,我們回講解的比較簡單一些,主要看看大工程中各種各樣的跳轉模式,各個控制器傳引數。在這種情況下,就會出現你中有我,我中有你,十分臃腫,牽一髮而動全身。那麼怎麼才能夠解除這種繁重的耦合情況呢,下面看看我是怎麼做的吧!(一)首先我們針對P

CocoaPods -- ios專案中安裝和使用CocoaPods

CocoaPods是什麼? 當你開發iOS應用時,會經常使用到很多第三方開源類庫,比如JSONKit,AFNetWorking等等。可能某個類庫又用到其他類庫,所以要使用它,必須得另外下載其他類庫,而其他類庫又用到其他類庫,“子子孫孫無窮盡也”,這也許是比較特殊的情況。總之小編

iOS專案優化記錄帖子!

僅作為個人筆記!會持續更新! 1:開啟Xcode啟動時間 通過新增環境變數可以打印出APP的啟動時間分析 Edit scheme -> Run -> Arguments DYLD_PRINT_STATISTICS設定為1 如果需要更詳細的資訊,那就將DYLD_PRINT_

Cocos2d-x專案建立之 原生ios專案匯入Cocos2d框架

上一章: Cocos2d-x專案建立之 靜態庫生成 第一步,“Cocos2d-X原始碼”和“預編譯靜態庫”準備 Cocos2d-X原始碼可在官網下載,如: Cocos2d-X 預編譯靜態庫生成方法: Cocos2d-x專案建立之 靜態庫生成

Android 專案元件化之建立module,生成aar,引入aar

導言: 在android平時的開發中,經常自己寫的東西讓別人使用,那麼就有module,aar,jar等方式. 1:module通過import module並dependencies完成 2:aar,包括所有檔案的android專用包,通過右邊的gradle->assembl

【cocos2dx】建立ios專案

1.開啟終端 2.將下載的3.6版本中的setup.py拖進終端,這時候會提醒做一些環境變數的配置資訊,如果不需要搭建安卓的開發環境這些東西就不需要配置 3.關閉一下重新開啟,進入到cocos2dx的版本目錄下,直接可以拖進去 cd 拖動版本資料夾 4.輸入cocos new 專案名

iOS專案上傳至AppStore問題彙總

  Problem 1:  Invalid Swift Support - The files libswiftPhotos.dylib, ......  don’t match  /Payload/stylist.app/Framewo

Android:關於專案元件化/模組化的設計

隨著技術越來越成熟,這兩年,元件化開發與外掛化開發的熱度一度高漲。對於元件化,有的人也喜歡稱之為模組化開發,我也比較喜歡稱之為模組化開發。使用模組化開發也已經有一段時間了,特此總結一下模組化開發的心得,防止以後忘記。 什麼是模組化開發 對於模組化開發的概念,有的人可

XamarinSQLite教程在Xamarin.iOS專案中定位資料庫檔案

XamarinSQLite教程在Xamarin.iOS專案中定位資料庫檔案 開發者可以在指定的路徑中找到複製的資料庫檔案,具體的操作步驟如下: (1)單擊Mac電腦中Finder選單中的“前往”|“前往資料夾…”命令,彈出前往資料夾對話方塊,如圖1.27所示。 (2)將輸出的路徑複

在Xamarin.iOS專案中使用預設資料庫

在Xamarin.iOS專案中使用預設資料庫 當開發者準備好一個預設資料庫檔案後,就可以將這個資料庫檔案新增到建立的專案中了。本節將分別在Xamarin.iOS和Xamarin.Android專案中使用預設資料庫。 在Xamarin.iOS專案中使用 在Xamarin.iOS專案中使用預

IOS控制元件-普通警告視窗的使用⚠️

func test6() {         let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))         butt

IOS控制元件-UITextField文字框控制元件的基本使用

首先檢視要繼承UITextFieldDelegate //UITextField文字框控制元件的基本使用     func test5() {         let textField = UITextField(frame

IOS控制元件-UIStepper步進控制元件的使用

func test4() {         //用來顯示步進控制元件的值         let label=UILabel(frame: CGRect(x: 80, y: 100, width: 100,

IOS控制元件-UISwitch開關控制元件的使用

UISwitch開關控制元件的使用 func test3() {         //建立一個顯示區域         let rect = CGRect(x: 130, y: 100, width: 0, he

IOS控制元件-UILabel的使用 及字型樣式自定義

Label的簡單使用 //建立一個label標籤  並給它一個顯示區域         let label=UILabel(frame: CGRect(x: 10, y: 100, width: 300 , height: 150