iOS 组件化详解 - CTMediator 原理与实践
📌 核心概念速记
┌─────────────────────────────────────────────────┐
│ CTMediator 组件化方案 │
│ ├─ 核心思想:Target-Action 模式 │
│ ├─ 实现方式:Runtime 反射调用 │
│ ├─ 优势:完全解耦,无需 import │
│ └─ 适用:OC 和 Swift(需桥接) │
└─────────────────────────────────────────────────┘
一、为什么要做组件化开发?
传统单体架构的问题
随着项目规模扩大,传统单体架构会暴露以下问题:
1. 代码耦合严重
// ❌ 传统方式:直接依赖
#import "PayViewController.h" // 订单模块直接依赖支付模块
#import "UserViewController.h" // 订单模块直接依赖用户模块
- (void)onPayClick {
PayViewController *payVC = [[PayViewController alloc] init];
[self.navigationController pushViewController:payVC animated:YES];
}
问题:
- 修改支付模块会影响订单模块
- 删除支付模块会导致订单模块编译失败
- 无法独立测试订单模块
2. 团队协作低效
问题场景:
- 开发A修改了支付模块
- 开发B正在开发订单模块(依赖支付模块)
- 两人同时提交代码 → 冲突频繁
- 需要频繁合并代码 → 效率低下
3. 编译速度慢
单工程问题:
- 代码量:10万+ 行
- 每次编译:全量编译所有代码
- 编译时间:5-10 分钟
- 开发效率:严重下降
4. 复用性差
场景:公司有多个 APP
- APP A:需要登录模块
- APP B:需要登录模块
- APP C:需要登录模块
传统方式:每个 APP 都复制一份登录代码
组件化:登录模块独立,多个 APP 直接引用
5. 测试成本高
问题:
- 修改支付模块 → 需要测试整个 APP
- 修改订单模块 → 需要测试整个 APP
- 回归测试范围大 → 测试时间长
组件化的解决方案
核心目标:解耦
将项目拆分为独立、可复用的组件,通过"中间件"实现组件间通信。
传统架构:
订单模块 ──直接依赖──> 支付模块
订单模块 ──直接依赖──> 用户模块
组件化架构:
订单模块 ──中间件──> 支付模块
订单模块 ──中间件──> 用户模块
↑
CTMediator
二、iOS 组件化如何分层?
分层架构图
┌─────────────────────────────────────────┐
│ 壳工程(Main Project) │
│ - 置文件、启动页、根控制器 配 │
│ - 无业务逻辑,只负责组装组件 │
└─────────────────────────────────────────┘
↓ 依赖
┌─────────────────────────────────────────┐
│ 中间件层(CTMediator) │
│ - 组件间通信桥梁 │
│ - 路由跳转、方法调用 │
└─────────────────────────────────────────┘
↓ 依赖
┌─────────────────────────────────────────┐
│ 业务组件层 │
│ - 首页组件、订单组件、支付组件 │
│ - 购物车组件、个人中心组件 │
└─────────────────────────────────────────┘
↓ 依赖
┌─────────────────────────────────────────┐
│ 业务基础层 │
│ - 登录、支付、用户信息管理 │
│ - 埋点统计 │
└─────────────────────────────────────────┘
↓ 依赖
┌─────────────────────────────────────────┐
│ 基础层 │
│ - 网络(AFNetworking) │
│ - 存储(FMDB) │
│ - 工具类(Category) │
│ - 基础 UI 组件 │
└─────────────────────────────────────────┘
各层详细说明
| 层级 |
职责 |
示例组件 |
依赖关系 |
| 基础层 |
提供全局通用能力,不依赖任何上层模块 |
网络、存储、工具类、基础 UI |
无依赖 |
| 业务基础层 |
封装跨业务的通用能力 |
登录、支付、用户信息、埋点 |
依赖基础层 |
| 业务组件层 |
拆分独立业务模块 |
首页、购物车、订单、个人中心 |
依赖基础层 + 业务基础层 |
| 中间件层 |
负责组件间通信 |
CTMediator、URLRouter |
依赖基础层 |
| 壳工程 |
整合所有组件 |
Main Project |
依赖所有层 |
依赖原则
关键规则:单向依赖,禁止反向依赖
✅ 正确:
上层 → 依赖 → 下层
❌ 错误:
下层 → 依赖 → 上层(禁止!)
三、CTMediator 核心原理
3.1 什么是 CTMediator?
CTMediator 是基于 Target-Action 模式的组件化中间件。
核心思想:
- 调用方通过中间件发送"指令"
- 中间件找到目标组件的 Target 类
- 执行对应的 Action 方法
- 全程无需 import,完全解耦
3.2 Target-Action 模式
调用流程:
订单组件
↓ 调用
CTMediator.payWithOrderId()
↓ 查找
Target_Pay 类(支付组件的 Target)
↓ 执行
action_payWithOrderId:callback: 方法(支付组件的 Action)
↓ 调用
PayService(支付组件内部逻辑)
3.3 命名约定
CTMediator 通过字符串查找类和方法,必须遵循命名约定:
Target 类命名:Target_ + 组件名
示例:
- 支付组件 → Target_Pay
- 订单组件 → Target_Order
- 用户组件 → Target_User
Action 方法命名:action_ + 方法名
示例:
- payWithOrderId:callback: → action_payWithOrderId:callback:
- showOrderDetail: → action_showOrderDetail:
四、CTMediator OC 实践详解
4.1 完整代码示例
假设场景:"订单组件"调用"支付组件"的支付功能
步骤 1:中间件 CTMediator(基础类,全局唯一)
// CTMediator.h
#import <UIKit/UIKit.h>
@interface CTMediator : NSObject
+ (instancetype)sharedInstance;
// 支付组件调用方法(中间件声明)
- (void)payWithOrderId:(NSString *)orderId
callback:(void(^)(BOOL success))callback;
@end
// CTMediator.m
#import "CTMediator.h"
#import <objc/runtime.h>
@implementation CTMediator
+ (instancetype)sharedInstance {
static CTMediator *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [CTMediator new];
});
return instance;
}
// 调用支付组件的支付方法(通过Target-Action调用)
- (void)payWithOrderId:(NSString *)orderId
callback:(void(^)(BOOL success))callback {
// 1. 通过字符串查找 Target 类
// Target_Pay:支付组件的Target类(约定命名:Target_+组件名)
Class targetClass = NSClassFromString(@"Target_Pay");
if (!targetClass) {
NSLog(@"❌ 找不到 Target_Pay 类");
if (callback) callback(NO);
return;
}
// 2. 创建 Target 实例
id target = [[targetClass alloc] init];
// 3. 构造 Action 方法名
// action_payWithOrderId:callback::支付组件的Action方法
SEL action = NSSelectorFromString(@"action_payWithOrderId:callback:");
// 4. 检查 Target 是否实现了 Action 方法
if (![target respondsToSelector:action]) {
NSLog(@"❌ Target_Pay 未实现 action_payWithOrderId:callback:");
if (callback) callback(NO);
return;
}
// 5. 通过 performSelector 调用 Action 方法
// 注意:performSelector 最多支持 2 个参数
// 如果参数超过 2 个,需要使用 NSInvocation
[target performSelector:action withObject:orderId withObject:callback];
}
@end
关键点解析:
-
NSClassFromString(@"Target_Pay")
-
NSSelectorFromString(@"action_payWithOrderId:callback:")
- 通过字符串构造方法选择器
- 方法名必须完全匹配(包括冒号)
-
performSelector:withObject:withObject:
- Runtime 动态调用方法
- 最多支持 2 个参数
- 超过 2 个参数需要使用 NSInvocation
步骤 2:支付组件(业务组件层)的实现
// Target_Pay.h(仅组件内部可见,不对外暴露)
#import <UIKit/UIKit.h>
@interface Target_Pay : NSObject
// 支付Action(参数与中间件声明一致)
- (void)action_payWithOrderId:(NSString *)orderId
callback:(void(^)(BOOL success))callback;
@end
// Target_Pay.m
#import "Target_Pay.h"
#import "PayService.h" // 组件内部的支付逻辑
@implementation Target_Pay
- (void)action_payWithOrderId:(NSString *)orderId
callback:(void(^)(BOOL success))callback {
NSLog(@"✅ Target_Pay 收到支付请求,订单ID:%@", orderId);
// 调用组件内部的支付逻辑
[[PayService shared] pay:orderId completion:^(BOOL success) {
if (callback) {
callback(success);
}
}];
}
@end
// PayService.h(支付组件内部逻辑)
#import <Foundation/Foundation.h>
@interface PayService : NSObject
+ (instancetype)shared;
- (void)pay:(NSString *)orderId
completion:(void(^)(BOOL success))completion;
@end
// PayService.m(支付组件内部逻辑)
#import "PayService.h"
@implementation PayService
+ (instancetype)shared {
static PayService *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [PayService new];
});
return instance;
}
- (void)pay:(NSString *)orderId
completion:(void(^)(BOOL success))completion {
// 模拟支付请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 模拟网络请求延迟
sleep(1);
// 模拟支付成功
BOOL success = YES;
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(success);
}
});
});
}
@end
步骤 3:调用方(订单组件)使用
// OrderViewController.h
#import <UIKit/UIKit.h>
#import "CTMediator.h" // 只依赖中间件,不依赖支付组件
@interface OrderViewController : UIViewController
@end
// OrderViewController.m
#import "OrderViewController.h"
// ❌ 不需要 #import "PayService.h"
// ❌ 不需要 #import "Target_Pay.h"
// ✅ 只需要 #import "CTMediator.h"
@implementation OrderViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建支付按钮
UIButton *payButton = [UIButton buttonWithType:UIButtonTypeSystem];
[payButton setTitle:@"支付" forState:UIControlStateNormal];
[payButton addTarget:self
action:@selector(onPayClick)
forControlEvents:UIControlEventTouchUpInside];
payButton.frame = CGRectMake(100, 100, 100, 44);
[self.view addSubview:payButton];
}
// 点击支付按钮
- (void)onPayClick {
NSString *orderId = @"ORDER_123";
// 通过中间件调用支付功能,完全解耦
[[CTMediator sharedInstance] payWithOrderId:orderId callback:^(BOOL success) {
if (success) {
NSLog(@"✅ 支付成功");
// 更新UI
} else {
NSLog(@"❌ 支付失败");
// 显示错误提示
}
}];
}
@end
4.2 多参数处理(NSInvocation)
当 Action 方法参数超过 2 个时,需要使用 NSInvocation:
// CTMediator.m 中处理多参数的方法
- (void)performTarget:(NSString *)targetName
action:(NSString *)actionName
params:(NSDictionary *)params {
Class targetClass = NSClassFromString(targetName);
id target = [[targetClass alloc] init];
SEL action = NSSelectorFromString(actionName);
NSMethodSignature *signature = [target methodSignatureForSelector:action];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = target;
invocation.selector = action;
// 设置参数(跳过 self 和 _cmd)
NSInteger index = 2;
for (NSString *key in params.allKeys) {
id value = params[key];
[invocation setArgument:&value atIndex:index];
index++;
}
[invocation invoke];
// 获取返回值
__unsafe_unretained id returnValue;
if (signature.methodReturnLength > 0) {
[invocation getReturnValue:&returnValue];
}
}
五、CTMediator Swift 实践详解
5.1 Swift 与 OC 的桥接问题
关键挑战:
- CTMediator 是 OC 写的
- Swift 组件需要暴露给 OC 调用
- 需要使用
@objc 标记
5.2 完整代码示例
步骤 1:中间件扩展(Swift 中调用 CTMediator)
// CTMediator+SwiftExtension.swift
import UIKit
extension CTMediator {
// Swift中声明支付调用方法
func pay(with orderId: String, callback: @escaping (Bool) -> Void) {
// 调用OC的Target-Action
// 注意:Swift 的闭包需要转换为 OC 的 Block
let ocCallback: (Bool) -> Void = { success in
callback(success)
}
// 使用 performSelector(需要桥接)
// 注意:这里需要将 Swift 闭包转换为 OC Block
self.perform(NSSelectorFromString("payWithOrderId:callback:"),
with: orderId,
with: ocCallback)
}
}
更好的方式:直接调用 OC 方法
// CTMediator+SwiftExtension.swift
import UIKit
// 创建 OC 兼容的 Block 类型
typealias PayCallback = @convention(block) (Bool) -> Void
extension CTMediator {
// Swift中声明支付调用方法
@objc func pay(with orderId: String, callback: @escaping (Bool) -> Void) {
// 将 Swift 闭包转换为 OC Block
let ocCallback: PayCallback = callback
// 调用 OC 方法
self.perform(NSSelectorFromString("payWithOrderId:callback:"),
with: orderId,
with: ocCallback)
}
}
步骤 2:Swift 支付组件的 Target 实现
// Target_Pay.swift(需@objc暴露给OC)
import UIKit
// ⚠️ 关键:必须指定OC类名,否则CTMediator找不到
@objc(Target_Pay)
class Target_Pay: NSObject {
// Action方法需@objc,参数匹配
@objc func action_payWithOrderId(_ orderId: String,
callback: @escaping (Bool) -> Void) {
print("✅ Target_Pay 收到支付请求,订单ID:\(orderId)")
// 调用Swift支付逻辑
PayService.shared.pay(orderId: orderId) { success in
callback(success)
}
}
}
// 支付逻辑(组件内部)
class PayService {
static let shared = PayService()
func pay(orderId: String, completion: @escaping (Bool) -> Void) {
// 模拟支付请求
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
// 模拟支付成功
completion(true)
}
}
}
关键点:
-
@objc(Target_Pay)
- 必须指定 OC 类名
- 否则 CTMediator 通过
NSClassFromString(@"Target_Pay") 找不到类
-
@objc func action_payWithOrderId
- Action 方法必须用
@objc 标记
- 参数类型必须与 OC 兼容(String、Int、Bool 等)
-
闭包转换
- Swift 闭包需要转换为 OC Block
- 使用
@convention(block) 或 @escaping
步骤 3:Swift 调用方(订单组件)
// OrderViewController.swift
import UIKit
class OrderViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 创建支付按钮
let payButton = UIButton(type: .system)
payButton.setTitle("支付", for: .normal)
payButton.addTarget(self,
action: #selector(onPayClick),
for: .touchUpInside)
payButton.frame = CGRect(x: 100, y: 100, width: 100, height: 44)
view.addSubview(payButton)
}
@objc func onPayClick() {
let orderId = "ORDER_456"
// 通过中间件调用支付功能,完全解耦
CTMediator.sharedInstance().pay(with: orderId) { success in
if success {
print("✅ 支付成功")
// 更新UI
} else {
print("❌ 支付失败")
// 显示错误提示
}
}
}
}
5.3 Swift 桥接配置
如果 CTMediator 是 OC 写的,需要在桥接头文件中导入:
// YourProject-Bridging-Header.h
#import "CTMediator.h"
六、CTMediator 的优势与劣势
6.1 优势
✅ 完全解耦
订单组件 ←→ CTMediator ←→ 支付组件
(无需 import)
✅ 类型安全(相对 URL 路由)
// CTMediator 提供方法声明,编译期检查
- (void)payWithOrderId:(NSString *)orderId
callback:(void(^)(BOOL success))callback;
// URL 路由是字符串,运行时才发现错误
[Router open:@"app://pay?orderId=123"]; // 拼写错误不会编译报错
✅ 支持复杂参数
// 支持对象、字典、数组等复杂参数
- (void)showOrderDetail:(NSDictionary *)params;
✅ 支持返回值
// 可以返回对象
- (UIViewController *)getProfileViewController;
6.2 劣势
❌ 需要维护中间件方法
// 每增加一个组件调用,需要在 CTMediator 中添加方法
- (void)payWithOrderId:callback:;
- (void)showOrderDetail:;
- (void)getUserInfo:;
// ... 方法越来越多
❌ 命名约定严格
必须遵循:
- Target 类:Target_组件名
- Action 方法:action_方法名
- 拼写错误会导致运行时崩溃
❌ Swift 支持不够优雅
需要 @objc 标记
需要桥接头文件
闭包转换复杂
七、组件化 vs 工程化 vs 插件化
7.1 概念对比
| 概念 |
核心目标 |
技术手段 |
优势 |
适用场景 |
| 组件化 |
拆分模块,解决代码耦合 |
静态拆分模块(编译期整合),通过中间件通信 |
解耦彻底、编译速度快、团队协作高效、模块可复用 |
大型 APP、多团队协作、业务稳定 |
| 工程化 |
规范开发流程,提升效率 |
自动化工具(Jenkins、Fastlane)、代码规范、CI/CD |
减少人为操作、标准化流程、降低出错率 |
所有项目(基础保障) |
| 插件化 |
动态加载模块,解决包体积和动态更新 |
动态库(.framework)、反射加载、沙盒隔离 |
按需加载、减小包体积、支持动态更新(无需发版) |
模块频繁更新、包体积敏感场景 |
7.2 详细对比
组件化(Componentization)
特点:
- ✅ 编译期整合(静态链接)
- ✅ 完全解耦
- ✅ 编译速度快(模块独立编译)
- ✅ 兼容性好(无动态加载风险)
实现方式:
订单组件.framework
支付组件.framework
用户组件.framework
↓ 编译期整合
Main Project(壳工程)
工程化(Engineering)
特点:
- ✅ 流程规范
- ✅ 自动化工具
- ✅ CI/CD 流程
- ✅ 代码规范检查
实现方式:
开发 → Git 提交 → Jenkins 构建 → 自动化测试 → 打包 → 发布
与组件化的关系:
- 工程化是"流程规范"
- 组件化是"架构设计"
- 工程化为组件化提供落地保障
插件化(Pluginization)
特点:
- ✅ 运行时加载(动态链接)
- ✅ 按需加载
- ✅ 减小包体积
- ⚠️ 实现复杂
- ⚠️ 受 iOS 审核限制(动态库可能被拒)
实现方式:
主 APP
↓ 运行时下载
插件 A.framework
插件 B.framework
↓ 动态加载
运行
iOS 限制:
- App Store 不允许下载可执行代码
- 动态库需要主 APP 签名
- 审核可能被拒
7.3 选择建议
项目规模小(< 5人):
→ 不需要组件化,工程化即可
项目规模中(5-20人):
→ 推荐组件化 + 工程化
项目规模大(> 20人):
→ 必须组件化 + 工程化
需要动态更新:
→ 考虑插件化(但要注意审核风险)
八、CTMediator 最佳实践
8.1 错误处理
// CTMediator.m
- (void)payWithOrderId:(NSString *)orderId
callback:(void(^)(BOOL success))callback {
Class targetClass = NSClassFromString(@"Target_Pay");
if (!targetClass) {
NSLog(@"❌ 找不到 Target_Pay 类,请检查组件是否已集成");
if (callback) callback(NO);
return;
}
id target = [[targetClass alloc] init];
SEL action = NSSelectorFromString(@"action_payWithOrderId:callback:");
if (![target respondsToSelector:action]) {
NSLog(@"❌ Target_Pay 未实现 action_payWithOrderId:callback:");
if (callback) callback(NO);
return;
}
// 使用 NSInvocation 安全调用
NSMethodSignature *signature = [target methodSignatureForSelector:action];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = target;
invocation.selector = action;
[invocation setArgument:&orderId atIndex:2];
[invocation setArgument:&callback atIndex:3];
[invocation invoke];
}
8.2 参数验证
// CTMediator.m
- (void)payWithOrderId:(NSString *)orderId
callback:(void(^)(BOOL success))callback {
// 参数验证
if (!orderId || orderId.length == 0) {
NSLog(@"❌ 订单ID不能为空");
if (callback) callback(NO);
return;
}
if (!callback) {
NSLog(@"⚠️ 回调不能为空");
return;
}
// ... 后续逻辑
}
8.3 日志记录
// CTMediator.m
- (void)payWithOrderId:(NSString *)orderId
callback:(void(^)(BOOL success))callback {
#ifdef DEBUG
NSLog(@"🔍 CTMediator: 调用支付功能,订单ID:%@", orderId);
#endif
// ... 调用逻辑
#ifdef DEBUG
NSLog(@"✅ CTMediator: 支付功能调用成功");
#endif
}
8.4 性能优化
// CTMediator.m
@interface CTMediator ()
@property (nonatomic, strong) NSMutableDictionary *targetCache; // Target 缓存
@end
@implementation CTMediator
- (instancetype)init {
if (self = [super init]) {
_targetCache = [NSMutableDictionary dictionary];
}
return self;
}
- (id)getTarget:(NSString *)targetName {
// 先从缓存获取
id target = self.targetCache[targetName];
if (target) {
return target;
}
// 缓存中没有,创建并缓存
Class targetClass = NSClassFromString(targetName);
if (targetClass) {
target = [[targetClass alloc] init];
self.targetCache[targetName] = target;
}
return target;
}
@end
8.5 扩展性优化:新增支付方式的正确姿势
问题场景
当需要新增支付方式(如Apple Pay)时,常见的错误做法是在PayService中添加分支:
// ❌ 错误做法:修改核心代码
- (void)pay:(NSString *)orderId paymentType:(NSString *)paymentType {
if ([paymentType isEqualToString:@"alipay"]) {
// 支付宝逻辑
} else if ([paymentType isEqualToString:@"applepay"]) { // 新增的
// Apple Pay 逻辑 ⭐ 需要修改核心代码
}
}
✅ 推荐方案:注册机制
步骤1:定义支付协议
@protocol PaymentProtocol <NSObject>
@required
- (NSString *)paymentType;
- (void)pay:(NSString *)orderId completion:(void(^)(BOOL success))completion;
@end
步骤2:CTMediator支持注册
@interface CTMediator ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, id<PaymentProtocol>> *paymentHandlers;
@end
- (void)registerPaymentHandler:(id<PaymentProtocol>)handler forPaymentType:(NSString *)paymentType {
self.paymentHandlers[paymentType] = handler;
}
- (void)payWithOrderId:(NSString *)orderId
paymentType:(NSString *)paymentType
callback:(void(^)(BOOL success))callback {
id<PaymentProtocol> handler = self.paymentHandlers[paymentType];
if (handler) {
[handler pay:orderId completion:callback];
} else {
// 降级到Target-Action模式
[self performTargetActionWithOrderId:orderId paymentType:paymentType callback:callback];
}
}
步骤3:各支付方式实现协议
// ApplePayHandler.h
@interface ApplePayHandler : NSObject <PaymentProtocol>
@end
// ApplePayHandler.m
@implementation ApplePayHandler
- (NSString *)paymentType {
return @"applepay";
}
- (void)pay:(NSString *)orderId completion:(void(^)(BOOL success))completion {
// Apple Pay 支付逻辑
// 完全独立,不需要修改任何现有代码
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
completion(YES);
});
}
@end
步骤4:在组件初始化时注册
// 在支付组件或App启动时
ApplePayHandler *applePayHandler = [[ApplePayHandler alloc] init];
[[CTMediator sharedInstance] registerPaymentHandler:applePayHandler
forPaymentType:@"applepay"];
优势对比
| 方式 |
是否修改核心代码 |
扩展性 |
维护性 |
测试性 |
| 分支判断 |
✅ 需要 |
❌ 差 |
❌ 差 |
❌ 难 |
| 注册机制 |
❌ 不需要 |
✅ 好 |
✅ 好 |
✅ 易 |
存储注册表的建议
// 使用NSMutableDictionary存储映射关系
@property (nonatomic, strong) NSMutableDictionary<NSString *, id<PaymentProtocol>> *paymentHandlers;
// 线程安全考虑
@property (nonatomic, strong) dispatch_queue_t registryQueue;
- (void)registerPaymentHandler:(id<PaymentProtocol>)handler forPaymentType:(NSString *)paymentType {
dispatch_barrier_async(self.registryQueue, ^{
self.paymentHandlers[paymentType] = handler;
});
}
九、常见问题 FAQ
Q1: CTMediator 和 URL 路由有什么区别?
CTMediator(Target-Action):
- ✅ 类型安全(编译期检查)
- ✅ 支持复杂参数
- ✅ 支持返回值
- ❌ 需要维护中间件方法
URL 路由:
- ✅ 简单易用
- ✅ 无需维护方法
- ❌ 字符串不安全(运行时错误)
- ❌ 参数传递受限(只能字符串)
Q2: Swift 组件如何暴露给 CTMediator?
// 必须使用 @objc 标记
@objc(Target_Pay)
class Target_Pay: NSObject {
@objc func action_payWithOrderId(_ orderId: String,
callback: @escaping (Bool) -> Void) {
// ...
}
}
Q3: 如何避免 Target 类找不到?
-
检查命名约定
Target 类名:Target_组件名(首字母大写)
示例:Target_Pay、Target_Order
-
检查组件是否已集成
在 Build Phases → Link Binary With Libraries 中检查
-
添加日志调试
Class targetClass = NSClassFromString(@"Target_Pay");
if (!targetClass) {
NSLog(@"❌ 找不到 Target_Pay,已加载的类:%@",
[self getAllLoadedClasses]);
}
Q4: 如何支持多参数方法?
使用 NSInvocation:
- (void)performTarget:(NSString *)targetName
action:(NSString *)actionName
params:(NSArray *)params {
Class targetClass = NSClassFromString(targetName);
id target = [[targetClass alloc] init];
SEL action = NSSelectorFromString(actionName);
NSMethodSignature *signature = [target methodSignatureForSelector:action];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = target;
invocation.selector = action;
// 设置参数
for (NSInteger i = 0; i < params.count; i++) {
id param = params[i];
[invocation setArgument:¶m atIndex:i + 2]; // 跳过 self 和 _cmd
}
[invocation invoke];
}
Q5: 新增支付方式为什么要用注册机制而不是直接修改代码?
传统方式的问题
// ❌ 在PayService中添加分支判断
- (void)pay:(NSString *)orderId paymentType:(NSString *)paymentType {
if ([paymentType isEqualToString:@"alipay"]) {
// 支付宝逻辑
} else if ([paymentType isEqualToString:@"applepay"]) { // 新增
// Apple Pay 逻辑 - 需要修改核心代码
}
}
问题:
- 需要修改已有代码
- 违反开闭原则
- 难以测试
- 增加代码复杂度
注册机制的优势
// ✅ 注册新支付方式,无需修改核心代码
ApplePayHandler *handler = [[ApplePayHandler alloc] init];
[[CTMediator sharedInstance] registerPaymentHandler:handler forPaymentType:@"applepay"];
优势:
- ✅ 零侵入:不修改现有代码
- ✅ 可扩展:动态添加新支付方式
- ✅ 易测试:各支付方式独立测试
- ✅ 解耦合:支付逻辑完全隔离
注册机制的本质
注册机制就是从硬编码的字符串映射,转换为手动维护的映射表:
// 硬编码方式:NSClassFromString(@"Target_Pay")
// 注册方式:维护一个 @{@"pay": [Target_Pay class]} 的字典
这种转变虽然增加了一层注册,但换来了更好的扩展性和维护性。
Q6: 注册表用什么数据结构存储?
最常用:NSMutableDictionary
@interface CTMediator ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, id<PaymentProtocol>> *paymentHandlers;
@end
存储方式对比
| 方式 |
查找性能 |
内存占用 |
线程安全 |
适用场景 |
| NSDictionary |
O(1) |
中等 |
✅ |
大多数项目 |
| NSMapTable |
O(1) |
低 |
⚠️ |
需要弱引用 |
| NSArray |
O(n) |
中等 |
✅ |
需要排序 |
| 数据库 |
O(log n) |
高 |
✅ |
需要持久化 |
线程安全实现
@property (nonatomic, strong) dispatch_queue_t registryQueue;
- (void)registerPaymentHandler:(id<PaymentProtocol>)handler forPaymentType:(NSString *)paymentType {
dispatch_barrier_async(self.registryQueue, ^{
self.paymentHandlers[paymentType] = handler;
});
}
- (id<PaymentProtocol>)getHandlerForPaymentType:(NSString *)paymentType {
__block id handler = nil;
dispatch_sync(self.registryQueue, ^{
handler = self.paymentHandlers[paymentType];
});
return handler;
}
十、总结
CTMediator 核心要点
-
Target-Action 模式
- Target 类:
Target_组件名
- Action 方法:
action_方法名
-
Runtime 反射调用
-
NSClassFromString 查找类
-
NSSelectorFromString 构造方法
-
performSelector 或 NSInvocation 调用
-
完全解耦
- 调用方无需 import 目标组件
- 通过中间件通信
-
适用场景
与其他方案对比
| 方案 |
类型安全 |
易用性 |
性能 |
适用场景 |
| CTMediator |
✅ |
⭐⭐⭐ |
✅ |
大型项目 |
| URL 路由 |
❌ |
⭐⭐⭐⭐⭐ |
✅ |
中小型项目 |
| Protocol |
✅ |
⭐⭐⭐⭐ |
✅ |
中型项目 |
最后更新:2024年