TL.DR
将多个 protocol 的 implementation 封装到一个类中,运行时通过 message forwarding 将消息转发给内部持有的 implementation。
问题描述:init 越来越长了
在 MVVM 中,VM 通过 init 接收底层的功能模块
- (instancetype)initWithSimpleManager:(id<SimpleManager>)simpleManager
otherParams:(WhatEverType)otherParams
NS_DESIGNATED_INITIALIZER;
随着时间的推移,VM 依赖的模块越来越多,渐渐变成这样
- (instancetype)initWithThisManager:(id<ThisManager>)thisManager
thatManager:(id<ThatManager>)thatManager
evenMoreManager:(id<EvenMoreManager>)evenMoreManager
//...
otherParams:(WhatEverType)otherParams
NS_DESIGNATED_INITIALIZER;
看着难受,用着难受,重构起来更难受。
理想中的效果
// ViewModel.h
- (instancetype)initWithManager:(id<ThisManager, ThatManager, EvenMoreManager, ...>)manager
otherParams:(WhatEverType)otherParams
NS_DESIGNATED_INITIALIZER;
传入的 manager 支持指定任意个 protocol
// 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。
也就是说需要两个部件
- ManagerFactory,根据传入的 protocol 初始化并持有对应的 manager 实例,最终返回 CompoundManager
- CompoundManager,接收到 vm 的方法调用消息后,转发给实现了该方法的对应 manager
一步步来看。
解决方案
第一步:ManagerFactory
比如有
@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;
这里就不细说了。
第二步:CompoundManager
先要持有这些初始化了的 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];
}
实现 - NSFPrioritizedDelegateContainer
这实际上是第二步 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 不应该影响到这样的逻辑。