Target-Action方案的優(yōu)點(diǎn)。
- 充分的利用Runtime的特性,實(shí)現(xiàn)了組件間服務(wù)的自動發(fā)現(xiàn),無需注冊即可實(shí)現(xiàn)組件間的調(diào)用。
- Target-Action方案,只存在組件依賴Mediator中介的這一層依賴關(guān)系。
- 在每個組件中創(chuàng)建Mediator的Category分類,針對維護(hù)Mediator的Category分類即可。
- 每個組件的Category對應(yīng)一個Target類,Categroy中的Action方法對應(yīng)Target類中的Action場景。
- Target-Action方案統(tǒng)一了所有組件間調(diào)用入口。都是調(diào)用“performTarget: action: params: shouldCacheTarget:”方法。第三個參數(shù)是一個字典,這個字典里面可以傳很多參數(shù),只要Key-Value寫好就可以了。
- Target-Action方案處理錯誤的方式也統(tǒng)一在一個地方了,Target沒有,或者是Target無法響應(yīng)的Action方法,都可以在Mediator這里進(jìn)行統(tǒng)一出錯處理。
- Target-Action方案也能有一定的安全保證,它對URL中的Native前綴進(jìn)行安全驗(yàn)證。
- 因此,Target-Action方案不管從維護(hù)性、可讀性、擴(kuò)展性來說,都是一個比較完美的方案。
Target-Action方案的缺點(diǎn)。
- Target_Action在Category中將常規(guī)參數(shù)打包成字典,在Target處再把字典拆包成常規(guī)參數(shù),這就造成了一部分的硬編碼。
Target-Action路由方案的技術(shù)實(shí)現(xiàn)。
1、通過Target-Action的方式,創(chuàng)建一個Target類,Target類里面定義一些Action方法,這些方法的結(jié)果是返回一個Controller或其它Object對象。
2、在每個組件中,給Mediator中介類創(chuàng)建一個對應(yīng)這個組件的分類,來保證Mediator中介類的代碼純凈性。
3、在每個組件Mediator的分類中,定義組件外部可調(diào)用的接口方法。接口方法內(nèi)部,通過統(tǒng)一調(diào)用Mediator中介類的“performTarget: action: params: shouldCacheTarget:”方法,實(shí)現(xiàn)組件間解耦。(即調(diào)用者即使不導(dǎo)入其它組件的頭文件,也能調(diào)用其它組件。
4、在Mediator中介類的“performTarget: action: params: shouldCacheTarget:”方法中,主要通過Runtime中的NSClassFromString獲取Target類,通過NSSelectorFromString獲取Target類中的Action方法名。
5、路由方案最終實(shí)現(xiàn)是通過獲取到的Target類和Target類中的Action方法名,去匹配已經(jīng)創(chuàng)建好的Target類,由Target類中的Action方法,真正的實(shí)現(xiàn)創(chuàng)建需要的控制器或?qū)ο?。將這些對象作為方法的返回值,傳遞給調(diào)用者。(即在Target類中的Action方法中,才能真正調(diào)用當(dāng)前組件控制器的功能。)
相關(guān)鏈接
組件化架構(gòu)漫談
http://www.itdecent.cn/p/67a6004f6930iOS應(yīng)用架構(gòu)談 組件化方案
https://casatwy.com/iOS-Modulization.htmliOS中的三種路由方案實(shí)踐
http://www.itdecent.cn/p/72d705ecc177iOS 組件化 —— 路由設(shè)計(jì)思路分析
http://www.itdecent.cn/p/76da56b3bd55
Target-Action組件化方案(來自casatwy)
整體架構(gòu)
- casatwy組件化方案可以處理兩種方式的調(diào)用,遠(yuǎn)程調(diào)用和本地調(diào)用,對于兩個不同的調(diào)用方式分別對應(yīng)兩個接口。
- 遠(yuǎn)程調(diào)用通過AppDelegate代理方法傳遞到當(dāng)前應(yīng)用后,調(diào)用遠(yuǎn)程接口并在內(nèi)部做一些處理,處理完成后會在遠(yuǎn)程接口內(nèi)部調(diào)用本地接口,以實(shí)現(xiàn)本地調(diào)用為遠(yuǎn)程調(diào)用服務(wù)。
- 本地調(diào)用由performTarget:action:params:方法負(fù)責(zé),但調(diào)用方一般不直接調(diào)用performTarget:方法。CTMediator會對外提供明確參數(shù)和方法名的方法,在方法內(nèi)部調(diào)用performTarget:方法和參數(shù)的轉(zhuǎn)換。

架構(gòu)設(shè)計(jì)思路
casatwy是通過CTMediator類實(shí)現(xiàn)組件化的,在此類中對外提供明確參數(shù)類型的接口,接口內(nèi)部通過performTarget方法調(diào)用服務(wù)方組件的Target、Action。由于CTMediator類的調(diào)用是通過runtime主動發(fā)現(xiàn)服務(wù)的,所以服務(wù)方對此類是完全解耦的。
但如果CTMediator類對外提供的方法都放在此類中,將會對CTMediator造成極大的負(fù)擔(dān)和代碼量。解決方法就是對每個服務(wù)方組件創(chuàng)建一個CTMediator的Category,并將對服務(wù)方的performTarget調(diào)用放在對應(yīng)的Category中,這些Category都屬于CTMediator中間件,從而實(shí)現(xiàn)了感官上的接口分離。

對于服務(wù)方的組件來說,每個組件都提供一個或多個Target類,在Target類中聲明Action方法。Target類是當(dāng)前組件對外提供的一個“服務(wù)類”,Target將當(dāng)前組件中所有的服務(wù)都定義在里面,CTMediator通過runtime主動發(fā)現(xiàn)服務(wù)。
在Target中的所有Action方法,都只有一個字典參數(shù),所以可以傳遞的參數(shù)很靈活,這也是casatwy提出的去Model化的概念。在Action的方法實(shí)現(xiàn)中,對傳進(jìn)來的字典參數(shù)進(jìn)行解析,再調(diào)用組件內(nèi)部的類和方法。
架構(gòu)分析
casatwy為我們提供了一個Demo,通過這個Demo可以很好的理解casatwy的設(shè)計(jì)思路,下面按照我的理解講解一下這個Demo。
Demo鏈接
https://github.com/casatwy/CTMediator
文件目錄

打開Demo后可以看到文件目錄非常清楚,在上圖中用藍(lán)框框出來的就是中間件部分,紅框框出來的就是業(yè)務(wù)組件部分。我對每個文件夾做了一個簡單的注釋,包含了其在架構(gòu)中的職責(zé)。
遠(yuǎn)程調(diào)用和本地調(diào)用
在CTMediator中定義遠(yuǎn)程調(diào)用和本地調(diào)用的兩個方法,其他業(yè)務(wù)相關(guān)的調(diào)用由Category完成。
// 遠(yuǎn)程App調(diào)用入口
- (id)performActionWithUrl:(NSURL *)url completion:(void(^)(NSDictionary *info))completion;
// 本地組件調(diào)用入口
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params;
在CTMediator中定義的ModuleA的Category,為其他組件提供了一個獲取控制器并跳轉(zhuǎn)的功能,下面是代碼實(shí)現(xiàn)。由于casatwy的方案中使用performTarget的方式進(jìn)行調(diào)用,所以涉及到很多硬編碼字符串的問題,casatwy采取定義常量字符串來解決這個問題,這樣管理也更方便。
#import "CTMediator+CTMediatorModuleAActions.h"
NSString * const kCTMediatorTargetA = @"A";
NSString * const kCTMediatorActionNativFetchDetailViewController = @"nativeFetchDetailViewController";
@implementation CTMediator (CTMediatorModuleAActions)
- (UIViewController *)CTMediator_viewControllerForDetail {
UIViewController *viewController = [self performTarget:kCTMediatorTargetA
action:kCTMediatorActionNativFetchDetailViewController
params:@{@"key":@"value"}];
if ([viewController isKindOfClass:[UIViewController class]]) {
// view controller 交付出去之后,可以由外界選擇是push還是present
return viewController;
} else {
// 這里處理異常場景,具體如何處理取決于產(chǎn)品邏輯
return [[UIViewController alloc] init];
}
}
下面是ModuleA組件中提供的服務(wù),被定義在Target_A類中,這些服務(wù)可以被CTMediator通過runtime的方式調(diào)用,這個過程就叫做發(fā)現(xiàn)服務(wù)。
在Target_A中對傳遞的參數(shù)做了處理,以及內(nèi)部的業(yè)務(wù)邏輯實(shí)現(xiàn)。方法是發(fā)生在ModuleA內(nèi)部的,這樣就可以保證組件內(nèi)部的業(yè)務(wù)不受外部影響,對內(nèi)部業(yè)務(wù)沒有侵入性。
- (UIViewController *)Action_nativeFetchDetailViewController:(NSDictionary *)params {
// 對傳過來的字典參數(shù)進(jìn)行解析,并調(diào)用ModuleA內(nèi)部的代碼
DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
viewController.valueLabel.text = params[@"key"];
return viewController;
}
命名規(guī)范
在大型項(xiàng)目中代碼量比較大,需要避免命名沖突的問題。
對于這個問題casatwy采取的是加前綴的方式,從casatwy的Demo中也可以看出,其組件ModuleA的Target命名為Target_A,可以區(qū)分各個組件的Target。
被調(diào)用的Action命名為Action_nativeFetchDetailViewController:,可以區(qū)分組件內(nèi)的方法與對外提供的方法。
casatwy將類和方法的命名,都統(tǒng)一按照其功能做區(qū)分當(dāng)做前綴,這樣很好的將組件相關(guān)和組件內(nèi)部代碼進(jìn)行了劃分。
CTMediator 代碼注釋
注釋是自己對代碼的理解,可能會有不對的地方,僅供參考。
調(diào)用路徑
- 主框架調(diào)用模塊的分類。
- 模塊的分類調(diào)用統(tǒng)一的中間件CTMediator
- 中間件CTMediator通過runtime主動發(fā)現(xiàn)服務(wù)。
- 重點(diǎn)注釋:在這里就實(shí)現(xiàn)了主框架對各個模塊之間的解耦。(主框架不需要導(dǎo)入模塊的頭文件,也能實(shí)現(xiàn)對模塊的調(diào)用)
- 中間件CTMediator調(diào)用模塊Target中的Action方法。實(shí)現(xiàn)引入模塊的功能。
- 模塊將創(chuàng)建的控制器等需要實(shí)現(xiàn)的業(yè)務(wù)邏輯返回給主框架。
==主框架==
ViewController
//
// ViewController.h
// CTMediator
//
// Created by casa on 16/3/13.
// Copyright ? 2016年 casa. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
//
// ViewController.m
// CTMediator
//
// Created by casa on 16/3/13.
// Copyright ? 2016年 casa. All rights reserved.
//
#import "ViewController.h"
#import <HandyFrame/UIView+LayoutMethods.h>
#import "CTMediator+CTMediatorModuleAActions.h"
#import "TableViewController.h"
// 全局常量
NSString * const kCellIdentifier = @"kCellIdentifier";
@interface ViewController () <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *dataSource;
@end
@implementation ViewController
#pragma mark - life cycle
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.tableView];
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self.tableView fill];
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.dataSource.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
cell.textLabel.text = self.dataSource[indexPath.row];
return cell;
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 取消選定由索引路徑標(biāo)識的給定行,并使用使取消選定具有動畫效果的選項(xiàng)。
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.row == 0) {
UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail];
// 獲得view controller之后,在這種場景下,到底push還是present,其實(shí)是要由使用者決定的,mediator只要給出view controller的實(shí)例就好了
[self presentViewController:viewController animated:YES completion:nil];
}
if (indexPath.row == 1) {
UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail];
[self.navigationController pushViewController:viewController animated:YES];
}
if (indexPath.row == 2) {
// 這種場景下,很明顯是需要被present的,所以不必返回實(shí)例,mediator直接present了
[[CTMediator sharedInstance] CTMediator_presentImage:[UIImage imageNamed:@"image"]];
}
if (indexPath.row == 3) {
// 這種場景下,參數(shù)有問題,因此需要在流程中做好處理
[[CTMediator sharedInstance] CTMediator_presentImage:nil];
}
if (indexPath.row == 4) {
[[CTMediator sharedInstance] CTMediator_showAlertWithMessage:@"casa" cancelAction:nil confirmAction:^(NSDictionary *info) {
// 做你想做的事
}];
}
if (indexPath.row == 5) {
TableViewController *tableViewController = [[TableViewController alloc] init];
[self presentViewController:tableViewController animated:YES completion:nil];
}
if (indexPath.row == 6) {
[[CTMediator sharedInstance] performTarget:@"InvalidTarget" action:@"InvalidAction" params:nil shouldCacheTarget:NO];
}
}
#pragma mark - getters and setters
- (UITableView *)tableView
{
if (_tableView == nil) {
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
// 注冊用于創(chuàng)建新表單元格的類。
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellIdentifier];
}
return _tableView;
}
- (NSArray *)dataSource
{
if (_dataSource == nil) {
_dataSource = @[@"present detail view controller",
@"push detail view controller",
@"present image",
@"present image when error",
@"show alert",
@"table view cell",
@"No Target-Action response"
];
}
return _dataSource;
}
@end
中間件Mediator
CTMediator+CTMediatorModuleAActions.h
//
// CTMediator+CTMediatorModuleAActions.h
// CTMediator
//
// Created by casa on 16/3/13.
// Copyright ? 2016年 casa. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "CTMediator.h"
@interface CTMediator (CTMediatorModuleAActions)
/// 中介 視圖詳情控制器
- (UIViewController *)CTMediator_viewControllerForDetail;
/**
中介 顯示警報信息
@param message 警報信息
@param cancelAction 取消動作回調(diào)
@param confirmAction 確定動作回調(diào)
*/
- (void)CTMediator_showAlertWithMessage:(NSString *)message cancelAction:(void(^)(NSDictionary *info))cancelAction confirmAction:(void(^)(NSDictionary *info))confirmAction;
/// 中介 模態(tài)彈出圖片
- (void)CTMediator_presentImage:(UIImage *)image;
/// 中介 返回cell
- (UITableViewCell *)CTMediator_tableViewCellWithIdentifier:(NSString *)identifier tableView:(UITableView *)tableView;
/// 中介 配置cell
- (void)CTMediator_configTableViewCell:(UITableViewCell *)cell withTitle:(NSString *)title atIndexPath:(NSIndexPath *)indexPath;
/// 中介 清除cell點(diǎn)擊
- (void)CTMediator_cleanTableViewCellTarget;
@end
//
// CTMediator+CTMediatorModuleAActions.m
// CTMediator
//
// Created by casa on 16/3/13.
// Copyright ? 2016年 casa. All rights reserved.
//
#import "CTMediator+CTMediatorModuleAActions.h"
/// 中介目標(biāo) A
NSString * const kCTMediatorTargetA = @"A";
/// 中介動作 本地獲取詳細(xì)視圖控制器
NSString * const kCTMediatorActionNativeFetchDetailViewController = @"nativeFetchDetailViewController";
/// 中介動作 本地模態(tài)彈出圖片
NSString * const kCTMediatorActionNativePresentImage = @"nativePresentImage";
/// 中介動作 本地沒有圖片
NSString * const kCTMediatorActionNativeNoImage = @"nativeNoImage";
/// 中介動作 展示警報
NSString * const kCTMediatorActionShowAlert = @"showAlert";
/// 中介動作 返回cell
NSString * const kCTMediatorActionCell = @"cell";
/// 中介動作 配置cell
NSString * const kCTMediatorActionConfigCell = @"configCell";
@implementation CTMediator (CTMediatorModuleAActions)
/// 中介 視圖詳情控制器
- (UIViewController *)CTMediator_viewControllerForDetail
{
UIViewController *viewController = [self performTarget:kCTMediatorTargetA
action:kCTMediatorActionNativeFetchDetailViewController
params:@{@"key":@"value"}
shouldCacheTarget:NO
];
if ([viewController isKindOfClass:[UIViewController class]]) {
// view controller 交付出去之后,可以由外界選擇是push還是present
return viewController;
} else {
// 這里處理異常場景,具體如何處理取決于產(chǎn)品
return [[UIViewController alloc] init];
}
}
/// 中介 模態(tài)彈出圖片
- (void)CTMediator_presentImage:(UIImage *)image
{
if (image) {
[self performTarget:kCTMediatorTargetA
action:kCTMediatorActionNativePresentImage
params:@{@"image":image}
shouldCacheTarget:NO];
} else {
// 這里處理image為nil的場景,如何處理取決于產(chǎn)品
[self performTarget:kCTMediatorTargetA
action:kCTMediatorActionNativeNoImage
params:@{@"image":[UIImage imageNamed:@"noImage"]}
shouldCacheTarget:NO];
}
}
/**
中介 顯示警報信息
@param message 警報信息
@param cancelAction 取消動作回調(diào)
@param confirmAction 確定動作回調(diào)
*/
- (void)CTMediator_showAlertWithMessage:(NSString *)message cancelAction:(void(^)(NSDictionary *info))cancelAction confirmAction:(void(^)(NSDictionary *info))confirmAction
{
// 可變參數(shù)字典,將要發(fā)送出去的參數(shù)
NSMutableDictionary *paramsToSend = [[NSMutableDictionary alloc] init];
if (message) {
paramsToSend[@"message"] = message;
}
if (cancelAction) {
paramsToSend[@"cancelAction"] = cancelAction;
}
if (confirmAction) {
paramsToSend[@"confirmAction"] = confirmAction;
}
[self performTarget:kCTMediatorTargetA
action:kCTMediatorActionShowAlert
params:paramsToSend
shouldCacheTarget:NO];
}
/// 中介 返回cell
- (UITableViewCell *)CTMediator_tableViewCellWithIdentifier:(NSString *)identifier tableView:(UITableView *)tableView
{
return [self performTarget:kCTMediatorTargetA
action:kCTMediatorActionCell
params:@{
@"identifier":identifier,
@"tableView":tableView
}
shouldCacheTarget:YES];
}
/// 中介 配置cell
- (void)CTMediator_configTableViewCell:(UITableViewCell *)cell withTitle:(NSString *)title atIndexPath:(NSIndexPath *)indexPath
{
// perform Target 執(zhí)行目標(biāo)
[self performTarget:kCTMediatorTargetA
action:kCTMediatorActionConfigCell
params:@{
@"cell":cell,
@"title":title,
@"indexPath":indexPath
}
shouldCacheTarget:YES];
}
/// 中介 清除cell點(diǎn)擊
- (void)CTMediator_cleanTableViewCellTarget
{
[self releaseCachedTargetWithTargetName:kCTMediatorTargetA];
}
@end
CTMediator
//
// CTMediator.h
// CTMediator
//
// Created by casa on 16/3/13.
// Copyright ? 2016年 casa. All rights reserved.
//
#import <UIKit/UIKit.h>
/// 全局常量 中介參數(shù)關(guān)鍵字 Swift目標(biāo)模塊名稱
extern NSString * const kCTMediatorParamsKeySwiftTargetModuleName;
@interface CTMediator : NSObject
+ (instancetype)sharedInstance;
// 遠(yuǎn)程App調(diào)用入口
/**
遠(yuǎn)程App調(diào)用入口
performActionWithUrl 通過遠(yuǎn)程URL執(zhí)行動作
@param url 遠(yuǎn)程URL
@param completion 動作完成后的回調(diào) 字典block參數(shù)
@return 返回結(jié)果
*/
- (id)performActionWithUrl:(NSURL *)url completion:(void(^)(NSDictionary *info))completion;
// 本地組件調(diào)用入口
/**
本地組件調(diào)用入口
performTarget 執(zhí)行目標(biāo)
@param targetName 目標(biāo)名稱
@param actionName 動作名稱
@param params 參數(shù)字典
@param shouldCacheTarget 是否緩存目標(biāo)
@return 返回結(jié)果
*/
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget;
/**
釋放緩存的目標(biāo) 通過目標(biāo)名稱
@param targetName 目標(biāo)名稱
*/
- (void)releaseCachedTargetWithTargetName:(NSString *)targetName;
@end
//
// CTMediator.m
// CTMediator
//
// Created by casa on 16/3/13.
// Copyright ? 2016年 casa. All rights reserved.
//
#import "CTMediator.h"
#import <objc/runtime.h>
NSString * const kCTMediatorParamsKeySwiftTargetModuleName = @"kCTMediatorParamsKeySwiftTargetModuleName";
@interface CTMediator ()
/// 緩存目標(biāo)的可變字典
@property (nonatomic, strong) NSMutableDictionary *cachedTarget;
@end
@implementation CTMediator
#pragma mark - public methods
/// 單例
+ (instancetype)sharedInstance
{
static CTMediator *mediator;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mediator = [[CTMediator alloc] init];
});
return mediator;
}
/*
遠(yuǎn)程App調(diào)用入口
核心方法
scheme://[target]/[action]?[params]
url sample:
aaa://targetA/actionB?id=1234
*/
- (id)performActionWithUrl:(NSURL *)url completion:(void (^)(NSDictionary *))completion
{
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
// 查詢字符串,符合RFC 1808。
NSString *urlString = [url query];
// componentsSeparatedByString 返回一個包含已被給定分隔符分隔的來自接收器的子字符串的數(shù)組。
// 遍歷 被 & 分隔的 urlString數(shù)組
for (NSString *param in [urlString componentsSeparatedByString:@"&"]) {
// 取出被等號分隔的數(shù)組
NSArray *elts = [param componentsSeparatedByString:@"="];
// continue 忽略了當(dāng)次循環(huán)continue語句后的代碼
if([elts count] < 2) continue;
[params setObject:[elts lastObject] forKey:[elts firstObject]];
}
// 這里這么寫主要是出于安全考慮,防止黑客通過遠(yuǎn)程方式調(diào)用本地模塊。這里的做法足以應(yīng)對絕大多數(shù)場景,如果要求更加嚴(yán)苛,也可以做更加復(fù)雜的安全邏輯。
NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
if ([actionName hasPrefix:@"native"]) {
return @(NO);
}
// 這個demo針對URL的路由處理非常簡單,就只是取對應(yīng)的target名字和method名字,但這已經(jīng)足以應(yīng)對絕大部份需求。如果需要拓展,可以在這個方法調(diào)用之前加入完整的路由邏輯
id result = [self performTarget:url.host action:actionName params:params shouldCacheTarget:NO];
// 判斷是否有執(zhí)行完成動作后的回調(diào)字典
if (completion) {
// 判斷是否有結(jié)果
if (result) {
completion(@{@"result":result});
} else {
completion(nil);
}
}
return result;
}
/**
本地組件調(diào)用入口
performTarget 執(zhí)行目標(biāo)
@param targetName 目標(biāo)名稱
@param actionName 動作名稱
@param params 參數(shù)字典
@param shouldCacheTarget 是否緩存目標(biāo)
@return 返回結(jié)果
*/
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
// Swift模塊名稱
NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
//==============================================================
// generate target 生成目標(biāo)
// 拼接目標(biāo)類名 字符串
NSString *targetClassString = nil;
if (swiftModuleName.length > 0) {
targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
} else {
targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
}
// 通過目標(biāo)類名 從緩存字典 取出目標(biāo)
NSObject *target = self.cachedTarget[targetClassString];
if (target == nil) {
// 按名稱獲取類。(將目標(biāo)類的字符串 轉(zhuǎn)化為 目標(biāo)類對象)
Class targetClass = NSClassFromString(targetClassString);
// 創(chuàng)建目標(biāo)類的實(shí)例. alloc:分配內(nèi)存。 init:初始化。
target = [[targetClass alloc] init];
}
//==============================================================
// generate action 生成動作
NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
// NSSelectorFromString 返回帶有給定名稱的選擇器。
// 通過動作字符串 創(chuàng)建動作方法
SEL action = NSSelectorFromString(actionString);
//=============================================================
// 處理 target == nil 的情況.
// 但是上面已經(jīng)通過 targetClassString = Target_%@ 創(chuàng)建了target對象.
// 所以 這里可能是多余的.
if (target == nil) {
// 這里是處理無響應(yīng)請求的地方之一,這個demo做得比較簡單,如果沒有可以響應(yīng)的target,就直接return了。實(shí)際開發(fā)過程中是可以事先給一個固定的target專門用于在這個時候頂上,然后處理這種請求的
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
return nil;
}
//================================================================
// 判斷是否緩存目標(biāo)
if (shouldCacheTarget) {
// 將目標(biāo)對象 放入緩存目標(biāo)的可變字典
self.cachedTarget[targetClassString] = target;
}
//================================================================
// 處理 target 沒有 action 的情況
// respondsToSelector 返回一個布爾值,該值指示接收方是否實(shí)現(xiàn)或繼承可以響應(yīng)指定消息的方法。
if ([target respondsToSelector:action]) {
// 核心方法 安全的執(zhí)行action
return [self safePerformAction:action target:target params:params];
} else {
// 這里是處理無響應(yīng)請求的地方,如果無響應(yīng),則嘗試調(diào)用對應(yīng)target的notFound方法統(tǒng)一處理
SEL action = NSSelectorFromString(@"notFound:");
if ([target respondsToSelector:action]) {
// 核心方法 安全的執(zhí)行action
return [self safePerformAction:action target:target params:params];
} else {
// 這里也是處理無響應(yīng)請求的地方,在notFound都沒有的時候,這個demo是直接return了。實(shí)際開發(fā)過程中,可以用前面提到的固定的target頂上的。
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
[self.cachedTarget removeObjectForKey:targetClassString];
return nil;
}
}
}
/**
釋放緩存的目標(biāo) 通過目標(biāo)名稱
@param targetName 目標(biāo)名稱
*/
- (void)releaseCachedTargetWithTargetName:(NSString *)targetName
{
NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
[self.cachedTarget removeObjectForKey:targetClassString];
}
#pragma mark - private methods 私有方法
/// 沒有目標(biāo)動作響應(yīng)時 執(zhí)行的方法
- (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams
{
SEL action = NSSelectorFromString(@"Action_response:");
NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
params[@"originParams"] = originParams;
params[@"targetString"] = targetString;
params[@"selectorString"] = selectorString;
[self safePerformAction:action target:target params:params];
}
/**
確保安全執(zhí)行動作的方法 通過OC運(yùn)行時實(shí)現(xiàn)的核心方法
@param action 執(zhí)行動作的方法
@param target 目標(biāo)對象(執(zhí)行動作的主體調(diào)用者)
@param params 參數(shù)字典
@return 返回模塊通過Action_方法創(chuàng)建出來的對象
*/
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
{
// methodSignatureForSelector 返回一個NSMethodSignature對象,該對象包含由給定選擇器標(biāo)識的方法的描述。
NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
if(methodSig == nil) {
return nil;
}
// methodReturnType 在Objective-C類型編碼中 對方法的返回類型進(jìn)行編碼的C字符串。
const char* retType = [methodSig methodReturnType];
//=============================================================
/** 注釋
1、iOS 的基礎(chǔ)數(shù)據(jù)類型及其包裝類型:
1.1、iOS 的基礎(chǔ)數(shù)據(jù)類型
int、float、double、long、char、NSInteger、NSUInteger、CGFloat、BOOL等
基本數(shù)據(jù)類型——char類型(在計(jì)算機(jī)內(nèi)部以int類型存儲)
在 64 位平臺和類似于 64 位平臺的各種平臺上,
NSInteger-> long,
NSUInteger-> unsigned long,
CGFloat-> double.
1.2、iOS 的基礎(chǔ)數(shù)據(jù)類型與包裝類型的轉(zhuǎn)換
由于只有對象類型才能放入數(shù)組、字典中,所以需要將基本數(shù)據(jù)類型轉(zhuǎn)換成包裝類型,OC 中提供的包裝類是 NSNumber,NSValue。其中NSNumber 繼承于 NSValue。NSNumber 主要針對于基本數(shù)據(jù)類型的包裝,NSValue 主要針對結(jié)構(gòu)體進(jìn)行包裝。
http://www.itdecent.cn/p/ff2274430b1c
*/
/** 注釋
strcmp函數(shù)
是string compare(字符串比較)的縮寫,用于比較兩個字符串并根據(jù)比較結(jié)果返回整數(shù)。
基本形式為strcmp(str1,str2),
若str1=str2,則返回零;若str1<str2,則返回負(fù)數(shù);若str1>str2,則返回正數(shù)。
@encode(Type)
@encode()
作用:用來判斷類型,常和strcmp(ObjCType, @encode(Type))合用。
@encode(Type) 可以返回該類型的 C 字符串(char *)的表示。
*/
// 下面這些if語句的作用,應(yīng)該是排除返回值是基本數(shù)據(jù)類型的情況.
// @encode() 作用:用來判斷類型,常和strcmp(ObjCType, @encode(Type))合用。
// 判斷 安全執(zhí)行動作方法(即這個方法) 的返回類型 是否為(void)
if (strcmp(retType, @encode(void)) == 0) {
// 返回一個能夠使用給定方法簽名構(gòu)造消息的NSInvocation對象。Invocation 調(diào)用
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
// 設(shè)置接收者的參數(shù)。
/** 注釋
atIndex 指數(shù)
指定參數(shù)索引的整數(shù)。
索引0和1分別表示隱藏參數(shù)self和_cmd;您應(yīng)該使用target和selector屬性直接設(shè)置這些值。對于通常在消息中傳遞的參數(shù),使用索引2或更大。
*/
[invocation setArgument:¶ms atIndex:2];
// 接收器的選擇器,如果沒有設(shè)置則為0。
[invocation setSelector:action];
// 接收者的目標(biāo),如果接收者沒有目標(biāo),則為nil。
[invocation setTarget:target];
// 將接收方的消息(帶有參數(shù))發(fā)送到其目標(biāo),并設(shè)置返回值。
[invocation invoke];
return nil;
}
// 判斷 安全執(zhí)行動作方法(即這個方法) 的返回類型 是否為(NSInteger)
if (strcmp(retType, @encode(NSInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSInteger result = 0;
// 獲取接收者的返回值。
// 將模塊中 方法的返回值得實(shí)際值 賦值給result
[invocation getReturnValue:&result];
// 返回oc對象(將返回值的實(shí)際值 包裝成OC對象)
return @(result);
}
// 判斷 安全執(zhí)行動作方法(即這個方法) 的返回類型 是否為(BOOL)
if (strcmp(retType, @encode(BOOL)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
BOOL result = 0;
[invocation getReturnValue:&result];
return @(result);
}
// 判斷 安全執(zhí)行動作方法(即這個方法) 的返回類型 是否為(CGFloat)
if (strcmp(retType, @encode(CGFloat)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
CGFloat result = 0;
[invocation getReturnValue:&result];
return @(result);
}
// 判斷 安全執(zhí)行動作方法(即這個方法) 的返回類型 是否為(NSUInteger)
if (strcmp(retType, @encode(NSUInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSUInteger result = 0;
[invocation getReturnValue:&result];
return @(result);
}
/** 注釋
詳情鏈接
https://www.cnblogs.com/lurenq/p/7709731.html
首先#pragma在本質(zhì)上是聲明,常用的功能就是注釋,尤其是給Code分段注釋;
而且它還有另一個強(qiáng)大的功能是處理編譯器警告,但卻沒有上一個功能用的那么多。
clang diagnostic 是#pragma 第一個常用命令:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-相關(guān)命令"
// 你自己的代碼
#pragma clang diagnostic pop
*/
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
// 在這里寫 自己的代碼;
// 這三行 #pragma clang diagnostic 代碼,是忽略警告的固定格式.
// 如果你確定不會發(fā)生內(nèi)存泄漏的情況下,可以使用如下的語句來忽略掉這條警告 "-Warc-performSelector-leaks"
// 以對象作為參數(shù) 向接收者發(fā)送消息。
// 中介的核心代碼,目標(biāo)通過參數(shù)執(zhí)行動作以后,得到Target_A執(zhí)行后返回的對象.
// 實(shí)際上,真正創(chuàng)建出所需要的實(shí)例對象的類 是Target_A的類.(Target_A是真正干活的)
id Target_Object = [target performSelector:action withObject:params];
return Target_Object;
#pragma clang diagnostic pop
}
#pragma mark - getters and setters
- (NSMutableDictionary *)cachedTarget
{
if (_cachedTarget == nil) {
_cachedTarget = [[NSMutableDictionary alloc] init];
}
return _cachedTarget;
}
@end
模塊部分
Target_A
//
// Target_A.h
// CTMediator
//
// Created by casa on 16/3/13.
// Copyright ? 2016年 casa. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface Target_A : NSObject
/// 動作 本地獲取詳情視圖控制器
- (UIViewController *)Action_nativeFetchDetailViewController:(NSDictionary *)params;
/// 動作 本地模態(tài)彈出圖片
- (id)Action_nativePresentImage:(NSDictionary *)params;
/// 動作 展示警報
- (id)Action_showAlert:(NSDictionary *)params;
// 容錯
- (id)Action_nativeNoImage:(NSDictionary *)params;
@end
//
// Target_A.m
// CTMediator
//
// Created by casa on 16/3/13.
// Copyright ? 2016年 casa. All rights reserved.
//
#import "Target_A.h"
#import "DemoModuleADetailViewController.h"
typedef void (^CTUrlRouterCallbackBlock)(NSDictionary *info);
@implementation Target_A
- (UIViewController *)Action_nativeFetchDetailViewController:(NSDictionary *)params
{
// 因?yàn)閍ction是從屬于ModuleA的,所以action直接可以使用ModuleA里的所有聲明
DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
viewController.valueLabel.text = params[@"key"];
return viewController;
}
- (id)Action_nativePresentImage:(NSDictionary *)params
{
DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
viewController.valueLabel.text = @"this is image";
// 通過參數(shù)字典 取出字典中的對象
viewController.imageView.image = params[@"image"];
// 窗口的根控制器 模態(tài)彈出 視圖控制器
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:viewController animated:YES completion:nil];
// id 的返回值可以為空
return nil;
}
- (id)Action_showAlert:(NSDictionary *)params
{
// 創(chuàng)建并返回具有指定標(biāo)題和行為的操作。
// handler : 處理程序
// 當(dāng)用戶選擇操作時要執(zhí)行的塊。這個塊沒有返回值,只接受選擇的action對象作為它的唯一參數(shù)。
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
// 重點(diǎn)
// 通過參數(shù)字典 取出回調(diào)的block;參數(shù)字典中,包含回調(diào)block對象.
CTUrlRouterCallbackBlock callback = params[@"cancelAction"];
// 如果回調(diào)有值,執(zhí)行回調(diào)動作.
if (callback) {
callback(@{@"alertAction":action});
}
}];
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"confirm" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
CTUrlRouterCallbackBlock callback = params[@"confirmAction"];
if (callback) {
callback(@{@"alertAction":action});
}
}];
// 創(chuàng)建 警報控制器
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"alert from Module A" message:params[@"message"] preferredStyle:UIAlertControllerStyleAlert];
// 添加動作
[alertController addAction:cancelAction];
[alertController addAction:confirmAction];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
return nil;
}
/// 動作 本地沒有圖片(用來容錯處理)
- (id)Action_nativeNoImage:(NSDictionary *)params
{
DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
viewController.valueLabel.text = @"no image";
viewController.imageView.image = [UIImage imageNamed:@"noImage"];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:viewController animated:YES completion:nil];
return nil;
}
/// 通過動作拿到cell
- (UITableViewCell *)Action_cell:(NSDictionary *)params
{
UITableView *tableView = params[@"tableView"];
NSString *identifier = params[@"identifier"];
// 這里的TableViewCell的類型可以是自定義的,我這邊偷懶就不自定義了。
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
return cell;
}
- (id)Action_configCell:(NSDictionary *)params
{
NSString *title = params[@"title"];
NSIndexPath *indexPath = params[@"indexPath"];
UITableViewCell *cell = params[@"cell"];
// 這里的TableViewCell的類型可以是自定義的,我這邊偷懶就不自定義了。
cell.textLabel.text = [NSString stringWithFormat:@"%@,%ld", title, (long)indexPath.row];
// if ([cell isKindOfClass:[XXXXXCell class]]) {
// 正常情況下在這里做特定cell的賦值,上面就簡單寫了
// }
return nil;
}
@end
DemoModuleADetailViewController
//
// DemoModuleADetailViewController.h
// CTMediator
//
// Created by casa on 16/3/13.
// Copyright ? 2016年 casa. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface DemoModuleADetailViewController : UIViewController
/// 只讀;外界只能拿到數(shù)據(jù) 但是不能修改
@property (nonatomic, strong, readonly) UILabel *valueLabel;
@property (nonatomic, strong, readonly) UIImageView *imageView;
@end
//
// DemoModuleADetailViewController.m
// CTMediator
//
// Created by casa on 16/3/13.
// Copyright ? 2016年 casa. All rights reserved.
//
#import "DemoModuleADetailViewController.h"
#import <HandyFrame/UIView+LayoutMethods.h>
@interface DemoModuleADetailViewController ()
/// 讀寫;重寫屬性,使 點(diǎn)m文件 中的屬性 可以被操作
@property (nonatomic, strong, readwrite) UILabel *valueLabel;
@property (nonatomic, strong, readwrite) UIImageView *imageView;
@property (nonatomic, strong) UIButton *returnButton;
@end
@implementation DemoModuleADetailViewController
#pragma mark - life cycle
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor grayColor];
[self.view addSubview:self.valueLabel];
[self.view addSubview:self.imageView];
[self.view addSubview:self.returnButton];
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self.valueLabel sizeToFit];
[self.valueLabel topInContainer:70 shouldResize:NO];
// 橫坐標(biāo)居中
[self.valueLabel centerXEqualToView:self.view];
// // 等效于
// self.valueLabel.ct_x = self.view.ct_centerX;
self.imageView.ct_size = CGSizeMake(100, 100);
[self.imageView centerEqualToView:self.view];
self.returnButton.ct_size = CGSizeMake(100, 100);
[self.returnButton bottomInContainer:0 shouldResize:NO];
[self.returnButton centerXEqualToView:self.view];
}
#pragma mark - event response 事件響應(yīng)
- (void)didTappedReturnButton:(UIButton *)button
{
// 判斷當(dāng)前導(dǎo)航控制器是否為空;從而得知當(dāng)前控制器彈出的方式
if (self.navigationController == nil) {
// 解散由視圖控制器模態(tài)呈現(xiàn)的視圖控制器。
[self dismissViewControllerAnimated:YES completion:nil];
} else {
[self.navigationController popViewControllerAnimated:YES];
}
}
#pragma mark - getters and setters
- (UILabel *)valueLabel
{
if (_valueLabel == nil) {
_valueLabel = [[UILabel alloc] init];
_valueLabel.font = [UIFont systemFontOfSize:30];
_valueLabel.textColor = [UIColor blackColor];
}
return _valueLabel;
}
- (UIImageView *)imageView
{
if (_imageView == nil) {
_imageView = [[UIImageView alloc] init];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
}
return _imageView;
}
- (UIButton *)returnButton
{
if (_returnButton == nil) {
_returnButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_returnButton addTarget:self action:@selector(didTappedReturnButton:) forControlEvents:UIControlEventTouchUpInside];
[_returnButton setTitle:@"return" forState:UIControlStateNormal];
[_returnButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
}
return _returnButton;
}
@end
主框架調(diào)用的TableView二級頁面
TableViewController
//
// TableViewController.h
// CTMediator
//
// Created by casa on 2016/10/20.
// Copyright ? 2016年 casa. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface TableViewController : UIViewController
@end
//
// TableViewController.m
// CTMediator
//
// Created by casa on 2016/10/20.
// Copyright ? 2016年 casa. All rights reserved.
//
#import "TableViewController.h"
#import <HandyFrame/UIView+LayoutMethods.h>
#import "CTMediator+CTMediatorModuleAActions.h"
@interface TableViewController () <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UIButton *closeButton;
@end
@implementation TableViewController
#pragma mark - life cycle
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.tableView];
[self.view addSubview:self.closeButton];
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self.tableView fillWidth];
[self.tableView topInContainer:0 shouldResize:YES];
[self.tableView bottomInContainer:50 shouldResize:YES];
[self.closeButton fillWidth];
[self.closeButton top:0 FromView:self.tableView];
[self.closeButton bottomInContainer:0 shouldResize:YES];
}
- (void)dealloc
{
// 在Controller被回收的時候,把相關(guān)的target也回收掉
[[CTMediator sharedInstance] CTMediator_cleanTableViewCellTarget];
// [CTMediator.sharedInstance CTMediator_cleanTableViewCellTarget];
}
#pragma mark - UITableViewDelegate
// cell即將顯示的時候
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
// 通過Mediator來獲取cell實(shí)例,由于target已經(jīng)被cache了,高頻調(diào)用不是問題。
[[CTMediator sharedInstance] CTMediator_configTableViewCell:cell withTitle:@"cell title" atIndexPath:indexPath];
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 100;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 通過mediator來配置cell實(shí)例,由于target已經(jīng)被cache了,高頻調(diào)用不是問題。
return [[CTMediator sharedInstance] CTMediator_tableViewCellWithIdentifier:@"cell" tableView:tableView];
}
#pragma mark - event response 事件響應(yīng)
// 點(diǎn)擊關(guān)閉按鈕
- (void)didTappedCloseButton:(UIButton *)button
{
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - getters and setters
- (UITableView *)tableView
{
if (_tableView == nil) {
_tableView = [[UITableView alloc] init];
_tableView.delegate = self;
_tableView.dataSource = self;
}
return _tableView;
}
- (UIButton *)closeButton
{
if (_closeButton == nil) {
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_closeButton setTitle:@"Close" forState:UIControlStateNormal];
[_closeButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
// 點(diǎn)擊按鈕要執(zhí)行的方法
[_closeButton addTarget:self action:@selector(didTappedCloseButton:) forControlEvents:UIControlEventTouchUpInside];
_closeButton.backgroundColor = [UIColor grayColor];
}
return _closeButton;
}
@end
Demo鏈接
https://github.com/casatwy/CTMediator
引用
組件化架構(gòu)漫談 http://www.itdecent.cn/p/67a6004f6930
iOS應(yīng)用架構(gòu)談 組件化方案 https://casatwy.com/iOS-Modulization.html
iOS中的三種路由方案實(shí)踐 http://www.itdecent.cn/p/72d705ecc177
iOS 組件化 —— 路由設(shè)計(jì)思路分析 http://www.itdecent.cn/p/76da56b3bd55