將多個 protocol 的 implementation 封裝到一個類中,運行時經過 message forwarding 將消息轉發給內部持有的 implementation。git
在 MVVM 中,VM 經過 init 接收底層的功能模塊github
- (instancetype)initWithSimpleManager:(id<SimpleManager>)simpleManager
otherParams:(WhatEverType)otherParams
NS_DESIGNATED_INITIALIZER;
複製代碼
隨着時間的推移,VM 依賴的模塊愈來愈多,漸漸變成這樣swift
- (instancetype)initWithThisManager:(id<ThisManager>)thisManager
thatManager:(id<ThatManager>)thatManager
evenMoreManager:(id<EvenMoreManager>)evenMoreManager
//...
otherParams:(WhatEverType)otherParams
NS_DESIGNATED_INITIALIZER;
複製代碼
看着難受,用着難受,重構起來更難受。ide
// ViewModel.h
- (instancetype)initWithManager:(id<ThisManager, ThatManager, EvenMoreManager, ...>)manager
otherParams:(WhatEverType)otherParams
NS_DESIGNATED_INITIALIZER;
複製代碼
傳入的 manager 支持指定任意個 protocolpost
// ManagerFactory.h
@interface ManagerFactory: NSObject
+ (CompoundManager *)managerConform2:(NSArray<Protocol *> *)protocols;
@end
複製代碼
而後單元測試
id<ThisManager, ThatManager, EvenMoreManager, ...> manager = [ManagerFactory managerConform2:@[@protocol(ThisManager), @protocol(ThatManager), @protocol(EvenMoreManager), ...]];
ViewModel *vm = [[ViewModel alloc] initWithManager:manager
otherParams:...];
複製代碼
One manager to rule them all。測試
也就是說須要兩個部件this
一步步來看。spa
好比有code
@protocol ThisManager<NSObject>
// ...
@end
@interface ThisManager<XXXThisManager>
//...
@end
複製代碼
若是應用內 class 量級較小,能夠經過 runtime 查找
NSArray<Class> *NSFClassesThatConformsToProtocol(Protocol *protocol)
{
Class *classes = NULL;
NSMutableArray *collection = [NSMutableArray array];
int numClasses = objc_getClassList(NULL, 0);
if (numClasses == 0)
{
return @[];
}
classes = (__unsafe_unretained Class*)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
for (int index = 0; index < numClasses; index++)
{
Class aClass = classes[index];
if (class_conformsToProtocol(aClass, protocol))
{
[collection addObject:aClass];
}
}
free(classes);
return collection.copy;
}
複製代碼
因而有
// ManagerFactory.m
+ (id)managerConform2:(NSArray<Protocol *> *)protocols
{
// ...
for (Protocol *protocol in protocols)
{
id manager = NSFClassesThatConformsToProtocol(protocol);
// ...
}
// ...
}
複製代碼
若是 class 不少,能夠簡單地採用註冊制
// ManagerFactory.h
+ (void)registerManager:(Protocol)managerProtocl withClass:(Class)managerClass;
複製代碼
這裏就不細說了。
先要持有這些初始化了的 manager 實例
// CompoundManager.h
@interface CompoundManager : NSObject
- (instancetype)initWithManagers:(NSArray<id<NSObject>> *)managers
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
// CompoundManager.m
@implementation CompoundManager
- (instancetype)initWithManagers:(NSArray<id<NSObject>> *)managers
{
if (self = [super init])
{
self.managers = managers;
}
return self;
}
// ...
@end
複製代碼
收到一個 selector 以後,要從 self.managers 中找出合適的一個
// CompoundManager.m
- (id<NSObject>)rules:(SEL)selector
{
// 多個 manager 實現了相同的 selector,排前面的優先響應
// 能夠繼承後 override 此方法來定製特定場景的 rules
for (id<NSObject> manager in self.managers)
{
if ([manager respondsToSelector:selector])
{
return manager;
}
}
return nil;
}
複製代碼
消息轉發很簡單
#pragma mark - Forwarding
- (BOOL)respondsToSelector:(SEL)selector
{
return [self rules:selector] != nil;
}
- (id)forwardingTargetForSelector:(SEL)selector
{
return [self rules:selector];
}
複製代碼
這其實是第二步 CompoundManager 的實現,能夠和不一樣類型的 "ManagerFactory" 組合使用。
一個比較有趣的使用場景是 MVVM 與 tableView,避免膠水代碼。
代碼見 NSFPrioritizedDelegateContainer,單元測試見 NSFPrioritizedDelegateContainerSpec。
實際的實現中,還支持所有或部分地弱引用傳入的 delegates
@interface NSFPrioritizedDelegate : NSObject @property (readonly) id<NSObject> content; /// 是否須要在 container 中弱引用 delegate @property (readonly) BOOL weakRef; @end @interface NSFPrioritizedDelegateContainer : NSObject - (instancetype)initWithPrioritizedDelegate:(NSArray<NSFPrioritizedDelegate *> *)delegates NS_DESIGNATED_INITIALIZER; @end 複製代碼
這適用於一些特定場景,好比某個 Manager 僅在用戶登陸狀態下存活,登出時即銷燬。
NSFPrioritizedDelegateContainer 不該該影響到這樣的邏輯。