日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

组件生命周期管理和通信方案

發布時間:2025/4/5 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 组件生命周期管理和通信方案 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

隨著移動互聯網的快速發展,項目的迭代速度越來越快,需求改變越來越頻繁,傳統開發方式的工程所面臨的一些,如代碼耦合嚴重、維護效率低、開發不夠敏捷等問題就凸現了出來。于是越來越多的公司開始推行"組件化",通過對原有業務或新業務進行組件(或模塊)拆分來提高并行開發效率。

在筆者面試過程中發現,很多同學口中的"組件化"也只是把代碼分庫,然后在主項目中使用 CocoaPods 把各個子庫聚合起來。對于怎樣合理地對組件分層、如何管理組件(主要包括組件的生命周期管理和組件的通信管理),如何管理不同版本的依賴,以及是否有整套集成和發布工具,這類問題的知之甚少。如果完全不了解這些問題,那么只是簡單的對主項目進行組件拆分,并不能提高多少開發效率。

筆者認為合理地進行組件拆分和管理各個組件之間的通信是組件化過程中最大的難點。合理地進行組件拆分是為了解耦,并且各個組件能更容易地獨立變化。而對于一個完整的應用來說,每個組件不可能孤零零地存在,必定會互相調用。這樣不同組件之間必須能進行通信而又沒有編譯期的依賴。

組件生命周期管理

可能很多同學在實施組件化的過程中知道要解決組件通信的問題,卻很少關注組件的生命周期。這里的生命周期主要是指 AppDelegate 中的生命周期方法。有時候一些組件需要在這些鉤子方法中做一些事情,這時候就需要一個能夠管理組件的工具,并在適當的時機執行組件相應的邏輯。

比如筆者在項目中是這樣做的:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {[[Ant shareInstance] application:application didFinishLaunchingWithOptions:launchOptions];return YES; }- (void)applicationWillResignActive:(UIApplication *)application {[[Ant shareInstance] applicationWillResignActive:application]; }- (void)applicationDidEnterBackground:(UIApplication *)application {[[Ant shareInstance] applicationDidEnterBackground:application]; }- (void)applicationWillEnterForeground:(UIApplication *)application {[[Ant shareInstance] applicationWillEnterForeground:application]; } 復制代碼

所有注冊的組件(模塊)會在 AppDelegate 相應的生命周期方法調用時自動調用。例如有如下組件定義:

ANT_MODULE_EXPORT(Module1App)@interface Module1App() <ATModuleProtocol> {NSInteger state; } @end@implementation Module1App - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {state = 0;NSLog(@"Module A state: %zd", state);return YES; }- (void)applicationWillEnterForeground:(UIApplication *)application {state += 1;NSLog(@"Module A state: %zd", state); } @end 復制代碼

上面示例代碼中第一行的 ANT_MODULE_EXPORT(Module1App) 是導出組件。Ant 會在 dyld 加載完 image 后將導出的組件進行注冊。當應用生命周期方法被調用時,會實例化所有注冊過的組件,調用組件相應的方法,并進行緩存,之后再次調用就會從緩存中取出組件的實例對象。

一般擁有完整生命周期的組件一般稱為一個模塊,一個模塊其實也是一個獨立的組件,它一般是包含一個完整的業務,列如:登錄模塊,外賣模塊,消息模塊等。

組件的生命周期管理并不復雜,實現方案都沒有太大區別,但它也是組件化中必不可少的部分。

組件通信

業界關于組件通信的方案比較多,主要有:url-block, target-action, protocol-class。下面筆者會對這三種方案做個簡單的介紹。

URL-Block

這是蘑菇街在組件化過程中使用的一種組件間通信方式,在應用啟動時注冊組件提供的服務,把調用組件使用的url和組件提供的服務block對應起來,保存到內存中。在使用組件的服務時,通過url找到對應的block,然后獲取服務。

[MGJRouter registerURLPattern:@"mgj://foo/bar" toHandler:^(NSDictionary *routerParameters) {NSLog(@"routerParameterUserInfo:%@", routerParameters[MGJRouterParameterUserInfo]); }];[MGJRouter openURL:@"mgj://foo/bar"]; 復制代碼

筆者是在15年開始學習組件化,那個時候就是使用的蘑菇街的這種發案。不過筆者從來沒有在實際項目中使用這種方案。casa 在這篇文章中批判了這種方案。筆者對 case 的觀點很是贊同。

如果項目中需要很多組件的服務,那么就需要在內存中維護大量的 url-block項,造成內存問題,對于服務注冊的代碼應該放在什么地方也是一個問題。筆者一直認為 url-block 注冊是一種很粗暴的方式,比如某個應用在啟動時注冊了100個服務,但某些服務在用戶使用過程中根本就沒有觸發,這就造成了內存浪費。比如我們點擊應用中的按鈕跳轉到某個頁面,如果用戶沒有點擊按鈕,下個頁面就永遠不會創建,我們一般不會提前創建這個頁面的。筆者更傾向于在需要服務的時候才進行服務對象的創建,在特定場景下也提供服務對象的緩存。

使用 url 傳參也是一個不可忽略的問題,對于一些基礎數據類型,使用這種方案倒是沒有問題,但是對于一些非常規對象就無能為力了,如 UIImage, NSData 等類型。

還有一個問題是 casa 在文章中沒有指出的,這個問題在他的 target-action 方案中也存在。下面用一個例子來說明一下。

比如在一個組件 A 中提供了一個服務:

[MGJRouter registerURLPattern:@"mgj://foo/bar" toHandler:^(NSDictionary *routerParameters) {NSLog(@"routerParameterUserInfo:%@", routerParameters[MGJRouterParameterUserInfo]); }]; 復制代碼

然后在一個組件 B 中使用了服務:

[MGJRouter openURL:@"mgj://foo/bar"]; 復制代碼

從上面示例代碼中可以看到,兩個不同組件能通信其實是通過一個字符串來定義的。如果服務使用方在寫代碼時寫錯了一個字符,那么使用方根本就不可能調起正確的服務,一旦出現這個問題,在開發過程中很難被發現。如果我們對組件多,注冊的服務多,那么在使用時就存在很大的溝通問題,提供方和接入方可能會在每個字符串所代表的意義上浪費大量的時間。而這些問題都可以在工程設計上避免的。雖說我們在寫代碼時要低耦合,但并不代表不要耦合,有時候需要一些耦合來提高代碼的健壯性和可維護性。

在 Swift 中可以使用枚舉來解決上面的問題,我們可以像下面這樣做:

protocol URLPatternCompatible {var URLPattern: String { get } }enum SomeService {case orderDetailcase others }enum SomeService: URLPatternCompatible {var URLPattern: String {switch self {case .orderDetail:return "mgj://foo/bar/orderdetail"case .others:return "mgj://foo/bar/others"}} }// 組件 A (服務提供方) MGJRouter.register(.orderDetail) { ... }// 組件 B (服務使用方) MGJRouter.open(.orderDetail) 復制代碼

SomeService 的定義可以放到一個專門的組件中,服務提供方和使用方都依賴這個專門的組件。我們這里不僅將字符串放到了一個統一的地方進行維護,而且還將一些在運行期才能發現的問題提前暴露到編譯器。這里我們通過耦合來達到提高代碼的健壯性和可維護性的目的。

Target-Action

Target-actin 是 casa 在批判蘑菇街的方案時提出的一種方案。它解決了 url-block 方案中內存問題、url 傳參問題、沒有區分本地調用和遠程調用等問題。其核心就是使用了 NSObject 的 - (id)performSelector:(SEL)aSelector withObject:(id)object; 方法。

在本地應用調用中,本地組件A在某處調用 [[CTMediator sharedInstance] performTarget:targetName action:actionName params:@{...}] 向 CTMediator 發起跨組件調用,CTMediator 根據獲得的 target 和 action 信息,通過 objective-C 的 runtime 轉化生成 target 實例以及對應的 action 選擇子,然后最終調用到目標業務提供的邏輯,完成需求。

casa 在文章中也給出了 demo,在具體的項目中,我們可以這樣使用:

// CTMediator+SomeAction.h - (UIViewController *)xx_someAction:(NSDictionary *)params;// CTMediator+SomeAction.m - (UIViewController *)xx_someAction:(NSDictionary *)params {return [self performTarget:@"A" action:@"someAction" params:params shouldCacheTarget:NO] } 復制代碼

上面是提供給服務調用方的一個簡潔的接口。其實就是對 CTMediator 方法的封裝。我們一般將 CTMediator 的這個分類放到一個獨立的組件中。調用方依賴這個獨立的組件就可以了。

在某個組件中調用服務:

// 組件 A 中 UIViewController *vc = [CTMediator sharedInstance] xx_someAction:@{@"key": value}]; 復制代碼

針對上面服務的定義,服務提供方的定義就必須是下面這樣:

// TargetA.h @interface Target_A : NSObject - (UIViewController *)someAction:(NSDictionary *)params; @end// TargetA.m - (UIViewController *)someAction:(NSDictionary *)params { ... }復制代碼

在這整個過程中可以看到,服務的調用方只需要依賴 CTMediator 這個中間件及其分類(定義服務)。服務提供方和調用方沒有任何依賴。確實做到了組件解耦。可以肯定的是 target-action 方案確實解決了 url-block 方案的一些問題。但是仔細一看,也是存在一些問題的。

跟 url-block 方案一樣,兩個不同組件能通信其實仍然是通過一個字符串來定義的。為什么這么說呢,我們可以看一下下面的代碼:

// CTMediator+SomeAction.m - (UIViewController *)xx_someAction:(NSDictionary *)params {return [self performTarget:@"A" action:@"someAction" params:params shouldCacheTarget:NO] }// TargetA.h @interface Target_A : NSObject - (UIViewController *)someAction:(NSDictionary *)params; @end 復制代碼

從上面的代碼中可以看到,服務能調起主要是調用了 CTMediator 的

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget; 方法。這里不管是 targetName 還是 action 都是字符串,在實現中 CTMediator 會示例化一個 Target_targetName 類的對象,并且創建一個 Action_actionName 的 selector,所有我們在服務提供的組件中的 Target 以及 Action 是不能隨便定義的。Target 必須是以 Target_開頭,方法必須以 Action_開頭。這種強制要求感覺不是一種工程師的思維。這里想去耦合,卻以一種不是很正確的方式造成了隱式的耦合。這也是讓我拋棄 CTMediator 轉而去開發自己的組件化通信方案的原因之一。

Protocol-Class

Protocol-Class 方案也是常用的組件化通信方式之一。這里把它放到最后,肯定是因為筆者使用的是這種方案咯(笑)。

Protocol-Class 方案就是通過 protocol 定義服務接口,服務提供方通過實現該接口來提供接口定義的服務。具體實現就是把 protocol 和 class 做一個映射,同時在內存中保存一張映射表,使用的時候,就通過 protocol 找到對應的 class 來獲取需要的服務。這種方案的優缺點先不說,可以先看一下具體的實踐:

示例圖:

示例代碼:

// TestService.h (定義服務) @protocol TestService <NSObject> /// 測試 - (void)service1;@end// 組件 A (服務提供方) ANT_REGISTER_SERVICE(TestServiceImpl, TestService) @interface TestServiceImpl() <TestService> @end@implementation TestServiceImpl- (void)service1 {NSLog(@"Service test from Impl"); }@end// 組件 B (服務使用方) id <TestService> obj = [Ant serviceImplFromProtocol:@protocol(TestService)]; [obj service1]; 復制代碼

像上面的方案一樣,我們會將服務的定義放到獨立的組件中。這個組件僅僅只包含了服務的聲明。不管是服務提供方還是服務使用方都依賴這個獨立的組件,服務提供方還是服務使用方互不依賴。

這里將系統提供的服務定義為協議,通過耦合提高了代碼的健壯性和可維護性。這里定義服務的 protocol 對服務提供方做了一個限定:你可以提供哪些服務,同時也給服務使用方做了限定:你可以使用哪些服務。這種設計將系統有哪些服務都交代的清清楚楚,通過服務的 protocol 我們就知道了每個服務的功能,調用需要的參數,返回值等。這里的定義服務的同時也可以作為系統服務的接口文檔,這節省了服務提供方和使用方很多的溝通時間,讓其能關注業務的開發。這在大型項目,多團隊開發中優勢尤為明顯。

當然 protocol-class 這種方案缺點也很明顯,需要在內存中保存 protocol 到 Class 的映射關系。但是我們可以通過將服務分類,讓系統注冊的 protocol-class 項盡量少一些,不要一個服務定義一個實現。對于一個有100個服務的系統,定義10個服務實現,每個實現提供10個服務,肯定要比100個服務實現占用的內存少很多。這就要求我們在實踐過程中能對系統中的服務能做好劃分。

總結

以上就是筆者對組件化的一些思考,很多觀點可能也不太成熟,如果有什么不合理的地方,也歡迎各位同學提出建議。組件解耦在 iOS 中其實有多種解決方案,各位同學可以根據項目實際情況選擇適合自己的方案。

上面代碼中的 Ant 是筆者最近開發的一個負責組件生命周期管理和通信的開源工具。因為筆者公司從17年開始就一直使用 Swift 進行開發,原來的工具是用 Swift 編寫的,使用了很多 Swift 的特性,在 OC 中使用就顯得不倫不類了,就針對 OC 進行了重新設計,于是就有了 Ant 。

轉載于:https://juejin.im/post/5c6b576d5188256204690203

總結

以上是生活随笔為你收集整理的组件生命周期管理和通信方案的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。