简介

组件化计划中会对组件分层宽和耦,上层事务组件依靠基层根底组件,不能反向依靠,而同层之间的组件需求通过组件通讯计划来防止彼此依靠的问题。

组件通讯计划解决的是同层组件之间彼此调用,而在编译层面上不发生代码依靠的问题。

  • 模块能够了解成高内聚,低耦合的事务或许功用集合
  • 组件更倾向于可复用的逻辑

iOS - 组件化 - 组件通信方案

  • 订阅,修正,日记属于事务层
  • Markdown功用和高档工具属于根底事务层
  • 网络库属于根底组件层

上层对基层直接依靠,基层不能对上层反向依靠,订阅,修正和日记这些同层事务直接的调用,需求运用组件通讯计划做同层解耦

因为解耦是解开编译依靠上的耦合,所以需求依据语言和系统库的特性找到除代码直接调用外的调用办法,能够看成是一种直接调用。直接代码调用的优点,编译查看,执行功率,缺点在于强耦合。从解耦视点考虑,很简单能够想到运用Protocol协议做解耦,而从动态化才能的视点考虑,基于Runtime也有灵敏解耦的办法。

完结计划有几种,运用Protocol-Service完结三方库首要有BeeHive库,而CTMediator运用了Target-Action办法

  • Protocol-Service
  • Target-Action

Protocol-Service

protocol与service是一一对应的联系,protocol用于声明组件间的依靠接口,service对protocol中供给的才能做对应的完结。其中不同的protocol,由哪个service来完结就需求一个映射联系,也能够看成protocol-service的映射表。

才能供给方完结对应的protocol,并在映射表注册。调用方需求运用protocol的才能,从表里找到对应的service完结,然后完结调用

BeeHive

iOS - 组件化 - 组件通信方案

BeeHive除了供给了组件间通讯的功用外,还供给了模块注册,应用和模块生命周期的工作分发等功用。此处首要分析组件通讯才能。

运用

声明需求供给的接口Protocol,继承于BHServiceProtocol

@protocol HomeServiceProtocol <NSObject, BHServiceProtocol>
-(void)registerViewController:(UIViewController *)vc title:(NSString *)title iconName:(NSString *)iconName;
@end

注册服务

动态注册

[[BeeHive shareInstance] registerService:@protocol(HomeServiceProtocol) service:[BHViewController class]];

静态注册

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
        <dict>
                <key>HomeServiceProtocol</key>
                <string>BHViewController</string>
        </dict>
</plist>

调用

获取到对应的Service,调用协议中声明的接口

#import "BHService.h"
id<HomeServiceProtocol> homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];
// use homeVc do invocation

原理

Protocol-Service表

iOS - 组件化 - 组件通信方案

  • key是protocol,value是protocolImplClass
NSString *protocolKey = [dict objectForKey:@"service"];
NSString *protocolImplClass = [dict objectForKey:@"impl"];
if (protocolKey.length > 0 && protocolImplClass.length > 0) {
    [self.allServicesDict addEntriesFromDictionary:@{protocolKey:protocolImplClass}];
}
注册

写入到Mach-O中DATA段

  • Mach-O的DATA段是可读可写的
  • Service信息,写入到Mach-O中DATA段
#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))
#define BeeHiveService(servicename,impl) \
class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ ""#servicename"" : ""#impl""}";

从Mach-O中DATA段读取存储的信息

NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp);
NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp)
{
    NSMutableArray *configs = [NSMutableArray array];
    unsigned long size = 0;
#ifndef __LP64__
    uintptr_t *memory = (uintptr_t*)getsectiondata(mhp, SEG_DATA, sectionName, &size);
#else
    const struct mach_header_64 *mhp64 = (const struct mach_header_64 *)mhp;
    uintptr_t *memory = (uintptr_t*)getsectiondata(mhp64, SEG_DATA, sectionName, &size);
#endif
    unsigned long counter = size/sizeof(void*);
    for(int idx = 0; idx < counter; ++idx){
        char *string = (char*)memory[idx];
        NSString *str = [NSString stringWithUTF8String:string];
        if(!str)continue;
        BHLog(@"config = %@", str);
        if(str) [configs addObject:str];
    }
    return configs;    
}

attribute((constructor))中增加_dyld_register_func_for_add_image回调

iOS - 组件化 - 组件通信方案

  • attribute((constructor))在main函数之前被调用

  • 虽然此刻dyld相关的操作比如add_image的现已完结,_dyld_register_func_for_add_image的回调有2种状况

    • 一个是在add_image的机遇
    • 如果注册回调的时分add_image现已完结,也会直接回调
__attribute__((constructor))
void init() {
    _dyld_register_func_for_add_image(dyld_callback);
}

dyld_callback回调函数中,完结动态注册

static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide)
{
    NSArray *mods = BHReadConfiguration(BeehiveModSectName, mhp);
    for (NSString *modName in mods) {
        Class cls;
        if (modName) {
            cls = NSClassFromString(modName);
            if (cls) {
                [[BHModuleManager sharedManager] registerDynamicModule:cls];
            }
        }
    }
    //register services
    NSArray<NSString *> *services = BHReadConfiguration(BeehiveServiceSectName,mhp);
    for (NSString *map in services) {
        NSData *jsonData =  [map dataUsingEncoding:NSUTF8StringEncoding];
        NSError *error = nil;
        id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
        if (!error) {
            if ([json isKindOfClass:[NSDictionary class]] && [json allKeys].count) {
                NSString *protocol = [json allKeys][0];
                NSString *clsName  = [json allValues][0];
                if (protocol && clsName) {
                    [[BHServiceManager sharedManager] registerService:NSProtocolFromString(protocol) implClass:NSClassFromString(clsName)];
                }
            }
        }
    }
}
注册机遇

BeeHive中也运用静态注册的办法,运用plist文件。动态注册是运用了Annotation注解的办法,结合在Mach-O文件的__DATA段存储,之后在_dyld_register_func_for_add_image的回调中,读取对应的数据再注册到映射表中

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
    <dict>
        <key>service</key>
        <string>UserTrackServiceProtocol</string>
        <key>impl</key>
        <string>BHUserTrackViewController</string>
    </dict>
</array>
</plist>

Swift的项目首要有几个问题,一个是难以对Mach-O直接操作,还有一个是无法灵敏取得反射联系,实例,类,protocol与父protocol的联系。关于无法直接运用Mach-O的DATA段完结Annotation的问题,咱们只能把注册机遇后移到Application -didFinishLaunchingWithOptions阶段。只要service与protocol的绑定,在service调用之前完结。

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // Codes
        ServiceManager.shared.register(service: "(HomeServiceProtocol.self)", implClass: HomeServiceImpelementor.self)
        // Codes
    }
获取
- (id)createService:(Protocol *)service withServiceName:(NSString *)serviceName shouldCache:(BOOL)shouldCache {
    if (!serviceName.length) {
        serviceName = NSStringFromProtocol(service);
    }
    id implInstance = nil;
    if (![self checkValidService:service]) {
        if (self.enableException) {
            @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol does not been registed", NSStringFromProtocol(service)] userInfo:nil];
        }
    }
    NSString *serviceStr = serviceName;
    if (shouldCache) {
        id protocolImpl = [[BHContext shareInstance] getServiceInstanceFromServiceName:serviceStr];
        if (protocolImpl) {
            return protocolImpl;
        }
    }
    Class implClass = [self serviceImplClass:service];
    if ([[implClass class] respondsToSelector:@selector(singleton)]) {
        if ([[implClass class] singleton]) {
            if ([[implClass class] respondsToSelector:@selector(shareInstance)])
                implInstance = [[implClass class] shareInstance];
            else
                implInstance = [[implClass alloc] init];
            if (shouldCache) {
                [[BHContext shareInstance] addServiceWithImplInstance:implInstance serviceName:serviceStr];
                return implInstance;
            } else {
                return implInstance;
            }
        }
    }
    return [[implClass alloc] init];
}

Target-Action

Target指的是需求交互的对象,Action指的是Target中被调用的办法,Target-Action模式在iOS开发中运用广泛。得益于OC的动态特性,运用和完结都很便利。

运用

创建供给给外部依靠的module,在module中给CTMediator类增加分类

@interface CTMediator (CTMediatorModuleAActions)
- (UIViewController *)CTMediator_viewControllerForDetail;
@end
NSString * const kCTMediatorTargetA = @"A";
NSString * const kCTMediatorActionNativeFetchDetailViewController = @"nativeFetchDetailViewController";
@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 {
        // 这儿处理异常场景,具体怎么处理取决于产品
        return [[UIViewController alloc] init];
    }
}
@end

在内部完结的module中,供给对应的Target,完结对应的Action

#import "Target_A.h"
@implementation Target_A
- (UIViewController *)Action_nativeFetchDetailViewController:(NSDictionary *)params
{
    // 因为action是从属于ModuleA的,所以action直接能够运用ModuleA里的一切声明
    DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
    viewController.valueLabel.text = params[@"key"];
    return viewController;
}
@end

调用的模块,引进刚才对外供给的module,不必引进实践完结Target_Action的module,直接调用

#import "CTMediator+CTMediatorModuleAActions.h"
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail];
    [self.navigationController pushViewController:viewController animated:YES];
}
@end

原理

CTMediator中的中间件代码十分简略,首要是对Target_Action的解析调用

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    if (targetName == nil || actionName == nil) {
        return nil;
    }
    NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
    // generate target
    NSString *targetClassString = nil;
    if (swiftModuleName.length > 0) {
        targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
    } else {
        targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    }
    NSObject *target = [self safeFetchCachedTarget:targetClassString];
    if (target == nil) {
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }
    // generate action
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    SEL action = NSSelectorFromString(actionString);
    if (target == nil) {
        // 这儿是处理无呼应恳求的当地之一,这个demo做得比较简单,如果没有能够呼应的target,就直接return了。实践开发过程中是能够事先给一个固定的target专门用于在这个时分顶上,然后处理这种恳求的
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    if (shouldCacheTarget) {
        [self safeSetCachedTarget:target key:targetClassString];
    }
    if ([target respondsToSelector:action]) {
        return [self safePerformAction:action target:target params:params];
    } else {
        // 这儿是处理无呼应恳求的当地,如果无呼应,则测验调用对应target的notFound办法统一处理
        SEL action = NSSelectorFromString(@"notFound:");
        if ([target respondsToSelector:action]) {
            return [self safePerformAction:action target:target params:params];
        } else {
            // 这儿也是处理无呼应恳求的当地,在notFound都没有的时分,这个demo是直接return了。实践开发过程中,能够用前面说到的固定的target顶上的。
            [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
            @synchronized (self) {
                [self.cachedTarget removeObjectForKey:targetClassString];
            }
            return nil;
        }
    }
}

结构联系

适配层

从调用者的视点来说,因为模块外部依靠的不稳定性,通常会规划适配层(adapter)来阻隔外部可能的api改变导致的内部多处调用都需求修正的状况。

iOS - 组件化 - 组件通信方案

模块中适配层要做的工作,举例来说无论是依靠SDWebImage仍是Kingfisher完结图片加载,对事务模块内部都适配成loadImage一类的接口,外部接口不会对内部形成侵入。

调停者

iOS - 组件化 - 组件通信方案

原本各个module的Protocol都会集在ServiceLib库中,或许以文件夹的办法,或许以subspec的办法做区分。所以一切的module都只需求依靠ServiceLib即可,但是关于ServiceLib这个根底组件来说,它对应的指责和代码权限都不应该开放给一切事务线,Protocol作为各个module对外供给的才能是跟事务相关的才能和代码,并不适合放在Lib这样的根底组件中。

ServiceLib作为一个根底组件,不应该有事务代码的侵入,然后演变为以下结构。关于Target-Action计划,从调用标准的视点,把CTMediator扩展的分类规划成单独的module,供给给外部依靠。

iOS - 组件化 - 组件通信方案

Protocol都放在每个Extension的库中,由对应的事务域负责模块维护。因为Service需求完结Protocol,这边的事务库会对Extension有依靠联系。

引用

BeeHive Github

CTMediator Github

iOS 组件化计划探究