美文网首页iOS底层iOS
iOS自定义注解器之旅

iOS自定义注解器之旅

作者: 熊猫人和熊猫君 | 来源:发表于2018-11-07 18:19 被阅读0次

1.什么是注解器

注解源自于java,可以理解为给代码打标记。我们可以给类定义打标签,也可以给方法,属性等打标签,被标签过的目标将按照我们所打的标签进行校验!如一个方法参数个数校验,参数类型校验!注解器,是与业务完全解耦的,完全删除注解也不会影响业务代码,当然类似于静态代码走查,注解也可以自定义。参考:https://blog.csdn.net/briblue/article/details/73824058

2.思考一个这样的问题

如果我需要给每一个UIViewController类,在不改类内部实现的情况下,标记一个ID,并且在该UIViewController类里面还能获取到该ID,我们会怎么做,在JAVA里面就比较方便了,在其紧挨着的类实现的地方加上一个组件标签[类的外面与类无关]。JAVA编译器会自动进行该标签所含有的规则校验。而objective-c中很遗憾,并没有提供注解相关实现,如我们再类外面加一段代码势必编译不过,只能想办法能不能加MARK而不影响编译。如下不能做到这样呢?

#pragma annotation(type:"default",param1:"value1",param2:"value2")
@interface UIApplicationDelegate()

3.iOS中的注解器

在objective-c中并没有注解的概念,那么我们可以尝试自己实现一套?确实可以,业内也实现了类似的方案,但是都不完美。
要模拟注解的过程,需要解决,1.不影响以前有的业务。 2.在被注解的源代码实现里面能方便的获取注解内容,可以理解为被注解的代码,在编译期间能自动生成一段代码在被注解类里面,或者我们需要建立一个“被注解者”与“注解代码”的对应关系。

目前业内的实现有两个方案,方案1,基于正则匹配,然后生成对应框架代码,加上自己OC实现的自定义规则,配合扫描结果关系表,来模拟注解的过程;方案2,居于类似FB的编译可配置来模拟的,用到__attribute((used, section("__DATA,"#sectname" "))),后面细聊;方案3,LLVM代码插桩;

3.1. 阿里的OCAnnotation

仓库地址:https://github.com/alibaba/OCAnnotation
是以方案1实现的,工程包括看一套ruby脚本,需要让其嵌入到我们的目标工程的build script里面。在我们编译期间将执行该脚本,该脚本将会扫码我们所有的源代码,并按规则生成对应的模板OC文件,该文件为一个配置对应关系。可理解为一个hashmap字典,以类注解为例 :

第一步:使用OCAnnotation Ruby工具条件,把编译期间要执行的扫码脚本集成到我们的目标工程。

第二步:注册

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{
    OCAAnnotationManager *annotationManager = [OCAAnnotationManager sharedManager];

    // custom annotation type registration (optional)
    [annotationManager registerAnnotationType:@"RemoteLog"
                                     position:OCAAnnotationPositionMethod
                                        class:[AFWRemoteLogMethodAnnotation class]];

    // annotation setup
    NSDictionary *configs = kAFWAnnotationConfigs; // using the macro name you write in the Annotation/.config file
    [[OCAAnnotationManager sharedManager] addConfigsWithConfigDic:configs];
}

需要注意的是AFWRemoteLogMethodAnnotation就是我们自定义的规则,配合我们的mark标记来实现对应的校验的类。

第三步:标记具体需要的类

#pragma annotation(type:"default",param1:"value1",param2:"value2")
@interface UIApplicationDelegate()

第四步:编译
编译后将产生一个类名对应一个ID的枚举头文件,这个头文件是通过ruby脚本扫码所有文件,如果有第二步的标记就加入到该枚举头文件。

第五步:加入该头文件到工程

总结:说白了就是,通过在编译期间,调用正则匹配脚本,扫码并获取注解与目标对象之间的关系(类,方法,属性)。并且把这个对应关系保存到一个字典里面去,这个字典以头文件,是ruby脚本扫码结束后自动创建的OC文件。当我们把这OC文件导入进去目标工程,在启动后马上加载进入内存,作为全局可访问数据,后我们就可以使用该全局数据【配置表】和 我们自己定义的规则,来达到运行期间的注解校验。当然该工程有很大缺陷是,每次编译都要扫码源代码,虽然作者做了缓存,还有就是不支持framework.在组件化遍地开花的今天,这也很尴尬!

备注:为了解决“被注解者”与“注解代码”的桥梁问题,还有一种办法是生成注解对象的类别,如当前目标是被注解者,那么久生成改其类别扩展,并导入工程预编译中。这样的“类代码插庄”,也能间接的让我们获取到类外的注解内容。当然这个也一样存在编译期间扫描代码的问题,而且如果注解多,还会增加代码量。

3.来源于FB编译期配置
目前beehive组件化框架也使用了此类方案[https://github.com/alibaba/BeeHive]
简要实现:

#define KGAppModuleDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))
#define KGAppModule(name) \
class NSObject;char * k##name##_mod KGAppModuleDATA(KGAppMods) = ""#name"";

NSArray<NSString *>* KGReadConfiguration(char *sectionName,const struct mach_header *mhp);

static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide)
{
    NSArray *mods = KGReadConfiguration(KGModSectName, mhp);
    for (NSString *modName in mods) {
        Class cls;
        if (modName) {
            cls = NSClassFromString(modName);
            if (cls) {
                [KGAppModuleLifeCycle kgRegisterModuleClass:cls config:@{
                                                                         } priority:1];
            }
        }
    }
}

//注册main之前的析构函数,析构函数仅爱周注解才能生效
//__attribute__((constructor))
//void initProphet() {
//    //动态链接库加载的时候的hook,可能会回调次数比较多,可能不建议
//    _dyld_register_func_for_add_image(dyld_callback);
//}

NSArray<NSString *>* KGReadConfiguration(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;
        if(str) [configs addObject:str];
    }
    
    return configs;
}

使用,在implementation之前实现一个类似的注解,就能在启动过程像load函数一样加载指定函数。

@KGAppModule(KGWatchMoudleLifecyleMounter)
@implementation KGWatchMoudleLifecyleMounter
//启动过程中通过注解调用的
- (instancetype)initKGAppMoudleWithConfiguration:(NSDictionary *)configuration
{
    if (self = [self init]) {
    }
    return self;
}

4.iOS注解器的模拟
5.总结
iOS客户端无完美方案,如果要做到更完美需要clang支持。我们上面方案模拟出来的都有一定的性能损耗!

相关文章

  • iOS自定义注解器之旅

    1.什么是注解器 注解源自于java,可以理解为给代码打标记。我们可以给类定义打标签,也可以给方法,属性等打标签,...

  • 注解学习笔记

    什么是注解注解分类注解作用分类 元注解 Java内置注解 自定义注解自定义注解实现及使用编译时注解注解处理器注解处...

  • Spring Boot 从配置文件注入到自动配置(二)

    先回顾一下自定义注解 1.创建自定义注解 2.创建注解使用实体 3.创建注解解释器 执行结果: 熟悉完自定义注解在...

  • Annotation Processor(注解处理器)详解

    前言 注解处理器 自定义处理器 前言 Java中的注解(Annotation)如果要被识别,离不开注解处理器。所以...

  • 登录拦截器

    定义注解和拦截器 自定义注解,对标记了注解的方法进行拦截 定义拦截器,根据注解进行方法拦截 配置拦截器 使用

  • 自定义redis缓存注解

    1、自定义缓存注解使用示例 2、自定注解,作用于方法上 3、自定义注解,作用于参数上 4、缓存注解拦截器

  • mybatis自定义拦截器

    自定义注解 自定义拦截器 在MybatisPlusConfig中注册bean 使用注解 2.详细关于mybatis...

  • Java注解知识梳理—自定义注解处理器

    Java注解知识梳理—自定义注解处理器 前言 前面介绍了如何自定义注解以及Java中关于注解的一些元素作用,学会了...

  • Java 自定义注解及使用场景

    Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优...

  • Java自定义注解

    Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优...

网友评论

    本文标题:iOS自定义注解器之旅

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