美文网首页
Mach-O 学习小结(五)

Mach-O 学习小结(五)

作者: whlpkk | 来源:发表于2021-07-01 16:17 被阅读0次

    最近学习了一下 Mach-O ,这里做个笔记记录,整理思路,加深理解。

    概述

    第一章 描述了 Mach-O 文件的基本结构;
    第二章 概述了符号,分析了符号表(symbol table)。
    第三章 探寻动态链接。
    第四章 分析fishhook。
    第五章 分析BeeHive。
    第六章 App启动时间。

    BeeHive简介

    BeeHive是用于iOSApp模块化编程的框架实现方案,吸收了Spring框架Service的理念来实现模块间的API耦合。

    BeeHive github官方链接

    BeeHive源码分析

    BHAnnotation

    //BHAnnotation.h
    #ifndef BeehiveModSectName
    #define BeehiveModSectName "BeehiveMods"
    #endif
    
    #ifndef BeehiveServiceSectName
    #define BeehiveServiceSectName "BeehiveServices"
    #endif
    
    #define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))
    
    #define BeeHiveMod(name) \
    class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";
    
    #define BeeHiveService(servicename,impl) \
    class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";
    

    这是BeeHive中最重要的几个宏定义,对外主要使用BeeHiveMod(name)BeeHiveService(servicename,impl)。这里以BeeHiveService(servicename,impl)为例来讲。

    #: 在宏定义中,将其后的变量,转化为字符串。
    ## : 在宏定义中,将其前后的两个变量拼接在一起。
    

    将宏定义展开

    // eg: servicenameProtocol为协议,servicenameImp为实现协议的类
    @BeeHiveService(servicenameProtocol,servicenameImp) 
    // 可以展开为如下:
    @class BeeHive; char * kservicenameProtocol_service BeeHiveDATA(BeehiveServices) = "{ \"""servicenameProtocol""\" : \"""servicenameImp""\"}";
    // 再展开BeeHiveDATA(BeehiveServices):
    @class BeeHive; char * kservicenameProtocol_service __attribute((used, section("__DATA,""BeehiveServices"" "))) = "{ \"""servicenameProtocol""\" : \"""servicenameImp""\"}";
    // 在C语音中 "abc"也可以写成 "a""b"c",即相连的字符串可以合并,这里合并字符串
    @class BeeHive; char * kservicenameProtocol_service __attribute((used, section("__DATA, BeehiveServices "))) = "{ \"servicenameProtocol\" : \"servicenameImp\"}";
    

    这里可以看到,其实这个宏就是声明了一个字符串变量。class BeeHive;的用处,是可以在使用宏的时候,前面要拼@。这里重要的是__attribute((used, section("__DATA, BeehiveServices "))),__attribute第一个参数used。被used修饰以后,意味着即使符号没有被引用,在Release下也不会被优化。如果不加这个修饰,那么Release环境链接器会去掉。section("__DATA, BeehiveServices ")标记这个被放在哪个section中,这里即为__DATA segment下的BeehiveServices section

    也就是说,这个宏的作用,声明了一个不会被优化掉的字符串变量,变量的内容为"{ \"servicenameProtocol\" : \"servicenameImp\"}",该字符串存储在Mach-O文件__DATA BeehiveServices section中。

    static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide);
    NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp);
    
    __attribute__((constructor)) //这里标记的方法,会在main函数之前被系统自动调用
    void initProphet() {
        _dyld_register_func_for_add_image(dyld_callback); //添加image load的回调,和fishhook的一样
    }
    
    static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide) //image load 的回调
    {
        NSArray *mods = BHReadConfiguration(BeehiveModSectName, mhp); //从指定的section中取出数据
        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); //从指定的section中取出数据
        for (NSString *map in services) { //这里的map,即为上文中声明的json字符串 "{ \"servicenameProtocol\" : \"servicenameImp\"}"
            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) {
                    //转成字典,出去 protocol 和 impClass
                    NSString *protocol = [json allKeys][0];
                    NSString *clsName  = [json allValues][0];
                    
                    if (protocol && clsName) {
                      //将协议和实现类注册到 BHServiceManager
                        [[BHServiceManager sharedManager] registerService:NSProtocolFromString(protocol) implClass:NSClassFromString(clsName)];
                    }
                    
                }
            }
        }
        
    }
    
    //读取指定section中的内容
    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;
        // 获取 __DATA segment 指定 sectionName的 内存地址
        uintptr_t *memory = (uintptr_t*)getsectiondata(mhp64, SEG_DATA, sectionName, &size);
    #endif
        // 遍历指定section,取出存储的字符串,返回
        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;
    }
    
    

    这里我们可以看到,通过宏定义声明的协议和类,会在程序启动,加载image的时候,被从Mach-O文件中读取到,然后被自动注册到BHServiceManager类中。通过这种方式,即可方便的将协议和实现类绑定。

    BHServiceProtocol

    这是BeeHive内部的一个协议,主要在创建实现类的实例的时候,用来判断是否需要创建单例。

    @protocol BHServiceProtocol <NSObject>
    @optional
    + (BOOL)singleton;
    + (id)shareInstance;
    @end
    

    BHServiceManager

    #import <Foundation/Foundation.h>
    
    @class BHContext;
    
    @interface BHServiceManager : NSObject
    
    @property (nonatomic, assign) BOOL  enableException;
    
    + (instancetype)sharedManager;
    - (void)registerLocalServices;
    - (void)registerService:(Protocol *)service implClass:(Class)implClass;
    
    - (id)createService:(Protocol *)service;
    - (id)createService:(Protocol *)service withServiceName:(NSString *)serviceName;
    - (id)createService:(Protocol *)service withServiceName:(NSString *)serviceName shouldCache:(BOOL)shouldCache;
    
    - (id)getServiceInstanceFromServiceName:(NSString *)serviceName;
    - (void)removeServiceWithServiceName:(NSString *)serviceName;
    
    @end
    

    这里只重点说几个方法:

    // 上文中的注册方法,将协议及对应的实现类绑定
    - (void)registerService:(Protocol *)service implClass:(Class)implClass
    {
        NSParameterAssert(service != nil);
        NSParameterAssert(implClass != nil);
        // 判断类是否实现协议
        if (![implClass conformsToProtocol:service]) {
            if (self.enableException) {
                @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ module does not comply with %@ protocol", NSStringFromClass(implClass), NSStringFromProtocol(service)] userInfo:nil];
            }
            return;
        }
        
        //判断协议是否已经被注册绑定
        if ([self checkValidService:service]) {
            if (self.enableException) {
                @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol has been registed", NSStringFromProtocol(service)] userInfo:nil];
            }
            return;
        }
        
        NSString *key = NSStringFromProtocol(service);
        NSString *value = NSStringFromClass(implClass);
        
        //将协议和类,存储为key value键值对
        if (key.length > 0 && value.length > 0) {
            [self.lock lock];
            [self.allServicesDict addEntriesFromDictionary:@{key:value}]; 
            [self.lock unlock];
        }
    }
    
    

    这个方法也简单,其实就是用字典将 协议和类 存储起来,方便将来通过协议拿到对应的类。

    - (id)createService:(Protocol *)service
    {
        return [self createService:service withServiceName:nil];
    }
    
    - (id)createService:(Protocol *)service withServiceName:(NSString *)serviceName {
        return [self createService:service withServiceName:serviceName shouldCache:YES];
    }
    
    - (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];
            }
            
        }
        
        //判断是否使用缓存,缓存的key,如果不传,即使用协议名做key。
        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];
    }
    
    

    这几个方法,其实也就是根据对应的协议,取出对应的实例,是否是单例也由是否实现具体的方法来决定。

    小结

    很多同学好奇,这样做有什么用处呢?绕来绕去,还不如我直接实例化对应的实例,调用对应的方法呢。

    其实不然,这样之后,我们就可以将对指定类的依赖,改为对指定协议的依赖,也就实现了模块间的解耦。

    举个栗子:

    // AAA模块
    @interface AAAModule : NSObject
    - (void)printAAA;
    @end
    
    @implementation AAAModule
    - (void)printAAA{
        NSLog(@"AAAModule");
    }
    @end
    
    // BBB模块
    @interface BBBModule : NSObject
    - (void)printBBB;
    @end
    
    #import "AAAModule.h"
    @implementation AAAModule
    - (void)printAAA{
        AAAModule *m = [[AAAModule alloc] init];
        [m printAAA];
    }
    @end
    

    这里我们看到,B模块对A模块有强依赖,如果这个时候,我们有需求,需要下掉AAA模块,那么BBB模块就需要跟着一起改动,否则就会编译报错。图中的例子比较简单,实际中,往往可能需要改动成百的接口。更令人崩溃的是,过几天之后,产品又想把功能添加回来,又一次大动干戈。一想就都是泪。。。

    但是如果我们使用BeeHive 改造之后:

    // 抽出一个新的模块,专门的一个协议模块,用来存储各个模块的对外协议,这里就叫CCC模块
    // 这里将需要对外暴露的方法抽成一个协议
    // CCCModule.h
    @protocol AAAModuleService <BHServiceProtocol>
    - (void)printAAA; //将AAA模块需要对外暴露的方法,放到协议中
    @end
    
    
    // AAA模块
    @interface AAAModule : NSObject
    - (void)printAAA;
    @end
    
    //注意这里的改动,声明协议和类绑定 
    @BeeHiveService(AAAModuleService,AAAModule)
    @interface AAAModule() <AAAModuleService> //声明协议
    @end  
    @implementation AAAModule
    - (void)printAAA{ //实现协议
        NSLog(@"AAAModule");
    }
    @end
      
    // BBB模块
    @interface BBBModule : NSObject
    - (void)printBBB;
    @end
    
    #import "CCCModule.h"
    #import "BeeHive"
    @implementation AAAModule
    - (void)printAAA{
        id<AAAModuleService> m = [[BHServiceManager sharedManager] createService:@protocol(AAAModuleService)];
        if (m) {
            [m printAAA];
        }
    }
    @end
    

    这里我们就将BBB模块对 AAA模块的依赖拆开,现在BBB模块依赖CCC模块,AAA模块一样依赖CCC模块。有同学就提出疑问了,你这样做有什么用?虽然解除了对AAA模块的依赖,但是又添加了CCC模块的依赖,而且代码还复杂了一些。

    其实不然,因为CCC模块只是一层协议层,里面只有各个模块的对外协议,并不涉及具体的实现。所以CCC模块不依赖任何其他业务模块,即CCC模块不耦合业务。这样当我们需要下掉AAA模块的时候,直接去除对应的模块即可。CCC、BBB模块不需要任何的改动。

    这在做大型项目的组件化时,是非常重要的,配合Cocoapod使用,引入对应pod组件,就可以实现对应功能。删掉对应pod组件,就下掉对应功能。真正意义上的实现组件的效果。

    相关文章

      网友评论

          本文标题:Mach-O 学习小结(五)

          本文链接:https://www.haomeiwen.com/subject/qsnjultx.html