iOS自定义注解器之旅

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支持。我们上面方案模拟出来的都有一定的性能损耗!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,061评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,711评论 2 59
  • 寸寸刮过的风 肆意凌虐着田野 屋里的那盆碳火 啪啪的打着节拍 不管不顾的窗扉 犹如你的心海 紧闭着上了锁 窗外的山...
    柏浅歌阅读 281评论 7 20
  • 南有乔木,不可休思,汉有游女,不可求思。 汉之广矣,不可泳思。江之永矣,不可方思。 翘翘错薪,言刈其楚。之子于归,...
    赣江边的锤子阅读 510评论 0 0
  • 初中的时候,我总觉得自己将来一定是个干大事的人,穿着当下最流行的衣服,画着美美的妆容,走路带风,回牟一笑,迷倒众生...
    天上人间00阅读 180评论 0 0