基於 message forwarding 的輕量依賴注入容器實現

TL.DR

將多個 protocol 的 implementation 封裝到一個類中,運行時經過 message forwarding 將消息轉發給內部持有的 implementation。git

問題描述:init 愈來愈長了

在 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

  • ManagerFactory,根據傳入的 protocol 初始化並持有對應的 manager 實例,最終返回 CompoundManager
  • CompoundManager,接收到 vm 的方法調用消息後,轉發給實現了該方法的對應 manager

一步步來看。spa

解決方案

第一步:ManagerFactory

好比有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;
複製代碼

這裏就不細說了。

第二步: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 不該該影響到這樣的邏輯。

相關文章
相關標籤/搜索