iOS的元件化開發
在一個APP開發過程中,如果專案較小且團隊人數較少,使用最基本的MVC、MVVM開發就已經足夠了,因為維護成本比較低。
但是當一個專案開發團隊人數較多時,因為每個人都會負責相應元件的開發,常規開發模式耦合會越來越嚴重,而且導致大量程式碼衝突,會使後期維護和升級過程中程式碼“牽一髮而動全身”,額外帶來很大的工作量,並且會導致一些潛在的BUG。
在這時,元件化開發就派上很大用場了,所謂的元件化開發,就是把APP根據業務拆分為各獨立的元件,各個元件相互寫作,組成完整的APP。
一、各元件的引入
關於元件的拆分,就根據具體專案進行拆分,假如APP被拆分了AModule、BModule、CModule,那麼,應該如何引入這些元件呢?你可能會想到APP的入口AppDelegate。在平時開發中,AppDelegate中往往初始化了好多元件,比如推送、統計等元件,這樣就會導致AppDelegate的臃腫。
所以,我們可以增加一個ModuleManager,專門用來初始化各元件。 首先增加一個 ModuleProtocol:
#import <Foundation/Foundation.h>
@import UIKit;
@import UserNotifications;
@protocol ModuleProtocol <UIApplicationDelegate, UNUserNotificationCenterDelegate>
@end
我們在ModuleManager中hook住UIApplicationDelegate和 UNUserNotificationCenterDelegate中的方法,使相應的元件中實現了對應方法,在相應時機就會呼叫組建裡的對應方法:
#import "ModuleManager.h"
#import "AppDelegate.h"
#import <objc/runtime.h>
#define ALL_MODULE [[ModuleManager sharedInstance] allModules]
#define SWIZZLE_METHOD(m) swizzleMethod(class, @selector(m),@selector(module_##m));
@interface ModuleManager ()
@property (nonatomic, strong) NSMutableArray<id<ModuleProtocol>> *modules;
@end
@implementation ModuleManager
+ (instancetype)sharedInstance { ...... }
- (NSMutableArray<id<ModuleProtocol>> *)modules { ...... }
- (void)addModule:(id<ModuleProtocol>) module { ...... }
- (void)loadModulesWithPlistFile:(NSString *)plistFile { ...... }
- (NSArray<id<ModuleProtocol>> *)allModules { ...... }
@end
@implementation AppDelegate (Module)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SWIZZLE_METHOD(application:willFinishLaunchingWithOptions:);
SWIZZLE_METHOD(application:didFinishLaunchingWithOptions:);
......
});
}
static inline void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector) { ...... }
- (BOOL)module_application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
BOOL result = [self module_application:application willFinishLaunchingWithOptions:launchOptions];
for (id<ModuleProtocol> module in ALL_MODULE) {
if ([module respondsToSelector:_cmd]) {
[module application:application willFinishLaunchingWithOptions:launchOptions];
}
}
return result;
}
- (BOOL)module_application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
BOOL result = [self module_application:application didFinishLaunchingWithOptions:launchOptions];
for (id<ModuleProtocol> module in ALL_MODULE) {
if ([module respondsToSelector:_cmd]) {
[module application:application didFinishLaunchingWithOptions:launchOptions];
}
}
return result;
}
......
@end
ModuleManager.h:
#import <Foundation/Foundation.h>
#import "ModuleProtocol.h"
@interface ModuleManager : NSObject
+ (instancetype)sharedInstance;
- (void)loadModulesWithPlistFile:(NSString *)plistFile;
- (NSArray<id<ModuleProtocol>> *)allModules;
@end
之後我們通過一個 ModulesRegister.plist檔案管理需要引入的元件:
如上圖,假如我們要引入AModule、BModule、CModule,那麼這三個Module只需要實現協議ModuleProtocol,然後實現AppDelegate中對應的方法,在對應方法中初始化自身即可: AModule.h:
#import <Foundation/Foundation.h>
#import "ModuleProtocol.h"
@interface AModule : NSObject<ModuleProtocol>
@end
AModule.m:
#import "AModule.h"
@implementation AModule
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
//初始化AModule
return YES;
}
@end
之後在AppDelegate的load方法中通過ModulesRegister.plist引入各元件即可:
@implementation AppDelegate
+ (void)load {
//load modules
NSString* plistPath = [[NSBundle mainBundle] pathForResource:@"ModulesRegister" ofType:@"plist"];
[[ModuleManager sharedInstance] loadModulesWithPlistFile:plistPath];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
......
}
@end
這樣,各元件的開發者在自己的元件中初始化自己,其他人需要使用時只需要加入ModulesRegister.plist檔案中即可。
二、元件間協作
簡單來看,假設APP的每個頁面就是一個元件,假如我們的APP有AViewController、BViewController、CViewController、DViewController、EViewController,各ViewController必然設定各種相互跳轉。那麼,我們APP的跳轉邏輯可能是下面這個樣子:
為了解決這種複雜的耦合關係,我們可以增加一個Router中間層去管理各ViewController之間的跳轉關係(也就是實際開發中元件間相互呼叫的關係)。
所以,根據需要,我開發並開源了一個支援URL Rewrite的iOS路由庫— FFRouter,通過FFRouter去管理各ViewController之間的跳轉關係:
這樣,各ViewController之間的跳轉關係就變的清晰了許多。
FFRouter通過提前註冊對應的URL,之後就直接通過開啟URL去控制各ViewController之間的跳轉(或各元件間的呼叫)。 FFRouter支援元件間傳遞非常規物件,如UIImage等,並支援獲取元件返回值。 基本使用如下:
/**
註冊 url
@param routeURL 要註冊的 URL
@param handlerBlock URL 被 Route 後的回撥
*/
+ (void)registerRouteURL:(NSString *)routeURL handler:(FFRouterHandler)handlerBlock;
/**
註冊 URL,通過該方式註冊的 URL 被 Route 後可返回一個 Object
@param routeURL 要註冊的 URL
@param handlerBlock URL 被 Route 後的回撥,可在回撥中返回一個 Object
*/
+ (void)registerObjectRouteURL:(NSString *)routeURL handler:(FFObjectRouterHandler)handlerBlock;
/**
判斷 URL 是否可被 Route(是否已經註冊)
@param URL 要判斷的 URL
@return 是否可被 Route
*/
+ (BOOL)canRouteURL:(NSString *)URL;
/**
Route 一個 URL
@param URL 要 Router 的 URL
*/
+ (void)routeURL:(NSString *)URL;
/**
Route 一個 URL,並帶上額外引數
@param URL 要 Router 的 URL
@param parameters 額外引數
*/
+ (void)routeURL:(NSString *)URL withParameters:(NSDictionary<NSString *, id> *)parameters;
/**
Route 一個 URL,可獲得返回的 Object
@param URL 要 Router 的 URL
@return 返回的 Object
*/
+ (id)routeObjectURL:(NSString *)URL;
/**
Route 一個 URL,並帶上額外引數,可獲得返回的 Object
@param URL 要 Router 的 URL
@param parameters 額外引數
@return 返回的 Object
*/
+ (id)routeObjectURL:(NSString *)URL withParameters:(NSDictionary<NSString *, id> *)parameters;
/**
Route 一個未註冊 URL 時回撥
@param handler 回撥
*/
+ (void)routeUnregisterURLHandler:(FFRouterUnregisterURLHandler)handler;
/**
取消註冊某個 URL
@param URL 要被取消註冊的 URL
*/
+ (void)unregisterRouteURL:(NSString *)URL;
/**
取消註冊所有 URL
*/
+ (void)unregisterAllRoutes;
/**
是否顯示 Log,用於除錯
@param enable YES or NO,預設為 NO
*/
+ (void)setLogEnabled:(BOOL)enable;
而且參考天貓的方案增加了URL Rewrite功能: 可以使用正則新增一條 Rewrite 規則,例如: 要實現開啟 URL:https://www.taobao.com/search/原子彈時,將其攔截,改用本地已註冊的 URL:protocol://page/routerDetails?product=原子彈開啟。 首先新增一條 Rewrite 規則:
[FFRouterRewrite addRewriteMatchRule:@"(?:https://)?www.taobao.com/search/(.*)" targetRule:@"protocol://page/routerDetails?product=$1"];
之後在開啟URL:https://www.taobao.com/search/原子彈時,將會 Rewrite 到URL:protocol://page/routerDetails?product=原子彈。
[FFRouter routeURL:@"https://www.taobao.com/search/原子彈"];
可以通過以下方法同時增加多個規則:
+ (void)addRewriteRules:(NSArray<NSDictionary *> *)rules;
其中 rules 格式必須為以下格式:
@[@{@"matchRule":@"YourMatchRule1",@"targetRule":@"YourTargetRule1"},
@{@"matchRule":@"YourMatchRule2",@"targetRule":@"YourTargetRule2"},
@{@"matchRule":@"YourMatchRule3",@"targetRule":@"YourTargetRule3"},]
Rewrite 規則中的保留字:
- 通過 $scheme、$host、$port、$path、$query、$fragment 獲取標準 URL 中的相應部分。通過$url獲取完整 URL
- 通過 $1、$2、$3…獲取matchRule的正則中使用圓括號取出的引數
- $:原變數的值、$$:原變數URL Encode後的值、$#:原變數URL Decode後的值
例如: https://www.taobao.com/search/原子彈對於Rewrite 規則(?:https://)?www.taobao.com/search/(.*)
$1=原子彈
$$1=%e5%8e%9f%e5%ad%90%e5%bc%b9
同樣,https://www.taobao.com/search/%e5%8e%9f%e5%ad%90%e5%bc%b9對於Rewrite 規則(?:https://)?www.taobao.com/search/(.*)
$1=%e5%8e%9f%e5%ad%90%e5%bc%b9
$#1=原子彈
考慮到經常用路由配置UIViewController之間的跳轉,所以增加了額外的工具FFRouterNavigation來更方便地控制UIViewController之間的跳轉。
三、其他元件化方案
目前這種元件化方案參考了蘑菇街、天貓、京東的的實現方案。除這種方案外,Casa(檢視文章)之前提出瞭解耦程度更高的方案,這種方案元件仍然使用中介軟體通訊,但中介軟體通過 runtime 介面解耦,然後使用 target-action 簡化寫法,通過 category 分離元件介面程式碼。 但是,這種方案雖然解耦程度更高,但是也增加了元件化的成本,綜合考慮,直接使用中介軟體通訊的方式更好一點。具體哪種方案好,也就仁者見仁、智者見智了~
本文來自雲棲社群合作伙伴“開源中國”
相關文章
- iOS 元件化開發(一):遠端私有庫的基本使用iOS元件化
- iOS 元件化開發(三):載入資原始檔iOS元件化
- iOS 元件化開發(四):fastlane實現pod自動化iOS元件化AST
- 元件化開發的思考元件化
- iOS 元件化開發(二):遠端私有庫的更新與子庫iOS元件化
- iOS的元件化思路分享iOS元件化
- iOS的元件化(模組化)之路iOS元件化
- iOS-元件化iOS元件化
- Vue 元件化開發Vue元件化
- Vue元件化開發Vue元件化
- 元件化開發(二)元件化
- iOS元件化實踐iOS元件化
- 化整為零的Vue元件化開發Vue元件化
- 元件化開發與黑箱元件化
- Flutter元件化開發方案Flutter元件化
- 元件化開發和模組化開發概念辨析元件化
- 降本增效的利器——元件化開發元件化
- iOS元件化之私有庫iOS元件化
- IOS元件化方案總結iOS元件化
- Android元件化開發實踐(一):為什麼要進行元件化開發?Android元件化
- Flutter元件化混合開發-AndroidFlutter元件化Android
- Vue 元件化開發之插槽Vue元件化
- 在Flutter專案中開發IOS桌面元件(WidgetExtension)FlutteriOS元件
- iOS專案元件化研究(一)iOS元件化
- iOS專案元件化歷程iOS元件化
- iOS 一個輕量級的元件化思路iOS元件化
- LLDebugTool - 便捷的IOS除錯工具(支援元件化)iOS除錯元件化
- 玩轉iOS開發:iOS中的NSOperation開發(一)iOS
- NOW直播——Flutter元件化開發方案Flutter元件化
- IOS Widget(3):SwiftUI開發小元件佈局入門iOSSwiftUI元件
- iOS 解藕、元件化最佳實踐iOS元件化
- iOS元件化通用工具淺析iOS元件化
- iOS 元件化實戰篇(私有庫)iOS元件化
- webpack+jquery 元件化、模組化開發的解決方案WebjQuery元件化
- iOS開發規範篇:清晰的初始化方法iOS
- 元件化開發瞭解一下?元件化
- 基於 MVP 的 Android 元件化開發框架實踐MVPAndroid元件化框架
- 為什麼要用Vue.js的元件化開發Vue.js元件化