窥探runtime

一:什么是Runtime?

1.Oc是一门动态性比较强的编程语言,和C/C++等语言有着很大的不同,OC的动态性是由Runtime API来支撑的, Runtime API提供的接口信息都是C语言的,源码由C/C++/汇编语言编写的

二:isa详解

在arm64之前,isa就是一个普通的指针,存储着Class,Meta-Class对象的内存地址,在arm64之后,对isa进行了优化,变成了一个共用体union结构,还是用位域来存储更多信息,补充(按位与&运算:都是1才为1,其他都是0,可以取出特定位的二进制值,特定位为1,其他为0,然后去按位与运算;按位或|运算:有1就为1,其他都是0,~按位取反)

位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。

struct{
    char tall :1; 倒数第一位
    char rich:1; 倒数第二位
    char handsome:1; 倒数第三位
} 占1个字节,不要以为char占1个字节,struct就以为是3个字节,:1位域就代表1个位,总共3个位,但是内存对其导致struct占1个字节->0b0000 0111
三:类对象 Class的结构
    struct objc_class {
          Class isa;
          Class superclass;
          cache_t cache;//方法缓存
          class_data_bits_t bits;//用于获取具体的类信息
    } 当这个bits & FAST_DATA_MASK得到的事
   
    struct class_rw_t {
          uint32_t flags;
          uint32_t  version;
          const class_ro_t *ro;
          method_list_t  *methods; //方法列表
          property_list_t  *properties;//属性列表
          const protocol_list_t  *protocols;//协议列表
          Class firstSubclass;
          Class nextSiblingClass;
          char demangledName;
     } 而ro
      struct class_rw_t {
          uint32_t flags;
          uint32_t  instanceStart;
          uint32_t  instanceSize;
          uint32_t  reserved;
          const uint8_t ivarLayout;
          const char *name;//类名
          method_list_t  *baseMethodList; 
          property_list_t  *baseProtocols;
           property_list_t  *baseproperties;
          const ivar_list_t *ivars;//成员变量列表
          const uint8_t *weakIvarLayout;
     }

现在我们研究一下这个class_rw_t中的方法列表/属性列表/协议列表是如何存放类对像的方法/属性/协议的,而class_ro_t也有基础方法列表/属性列表/协议列表,两个的区别是什么?
1.class_rw_t里面的methods/properties/protocols是可读可写的二维数组,包含了类的初始内容和分类的内容

方法列表

2.class_ro_t里面的baseMethodList /baseProtocols/baseProperties/ivars是一维数组,是只读的,包含了类的初始信息
方法列表

最终方法是放在method_t中,method_t其实是对方法/函数的封装,那么method_t底层是什么呢?

  struct method_t {
    SEL name;// 函数名
    const char *types;//编码(返回值/参数类型)
    IMP imp;// 指向函数的指针(函数地址)
  }

IMP代表函数的具体实现

typedef id _Nullable (*IMP)(id _Null,SEL _Nunnull,....);

SEL代表方法/函数名,一般叫选择器,底层结构和char 类似
types包含了函数返回至/参数编码的字符串v16@0:8(V代表void返回值,@代表id类型.:代表cmd)
通过isa,找到类对象,在class_rw_t中知道到方法列表.再找到对应的method_t,就可以直接调用函数了,因为method_t里面包含了name函数名,types返回值类型和参数类型,imp指向函数的指针

四:方法缓存

上面可知,类对象中底层有一个cache_t cache,叫方法缓存,用散列表来缓存曾经调用过的函数,可以提高方法的调用速度;主要原理:当调用对象方法时,通过实例对象的isa指针找到类对象,再在类对象的方法列表中找到对应的方法,如果没有就会通过superclass找到父类的类对象,再在其中查找方法,当找到后,会把这个方法缓存到这个cache中,以便下次调用的时候直接读取,调用方法是的sel,如果等于bucket_t中的_key,那么就会通过_imp内存地址找到函数,在调用

        struct chache_t{
            struct bucket_t *buckets;//散列表 数组,里面缓存这方法
            mask_t _mask;// 散列表的长度 - 1;
            mask_t _occupied//已经缓存的方法数量
        }

        struct  bucket_t{
            cache_key_t _key;//SEL作为key
            IMP _imp;//函数的内存地址
        }

散列表的具体操作是如何的呢?
调用方法时:objc_msgSend(person,@selector(person));在散列表struct bucket_t *buckets中是如何查找呢?就是用@selector(person)&_mask = 这个方法在散列表中的索引;就会将这个方法缓存到这个索引中,取方法的时候也是这样去取,实际上是牺牲内存空间来换取读取效率;

注:当缓存某一个方法的时候,如果散列表的长度不够的时候,系统会进行扩容处理,但是之前缓存过的方法会清空掉

五:消息调用机制objc_msgSend(receiver对象,SEL函数名称)

OC方法的调用,其实都是转化为objc_msgSend函数的调用,执行流程可以分为3步

1.消息发送
2.动态方法解析
3.消息转发

1.消息发送
先判断是否有消息接受者,如果没有那么直接退出报错,如果有,那么会通过消息接受者的isa指针找到他的receiverClass,在receiverClass的cache方法缓存中查找方法,能够找到,那么调用,如果不能找到,那么会在receiverClass的 class_rw_t *data中查找方法列表(已经排序的二分查找,没有排序的按顺序查找,),找到了那么结束查找并将方法缓存到receiverClass的caches中,如果没有找到,会通过receiverClass的superClass指针找到superClass父类的类对象,先查找superClass的cache_t 方法缓存,能找到,那么结束查找并将方法缓存到receiverClass的caches中,找不到,那么会在superClass的 class_rw_t *data中查找方法列表,找到那么结束查找并将方法缓存到receiverClass的caches中,找不到那么继续查找上一个父类,直到找到,如果还是没找到,那么会进入到第二个阶段:动态方法解析

消息发送流程

2.动态方法解析
先判断是否曾经有动态解析,如果是,那么进入第一阶段消息转发阶段的流程;否的话,那么调用
+resolveInstanceMethod:或者是+resolveClassMethod方法来动态解析方法,并且标记为已经动态解析了,然后进入第一阶段的消息发送阶段;如果没有实现这个方法,那么还是标记为已经动态解析,进入消息发送阶段,查找方法,没找到那么进度到第三个阶段:消息转发

动态方法解析流程

+ (Bool)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)){
        Method method = class_getInstanceMethod(self,@selector(other));
        class_addMethod(self,sel,method_getImplementrtion(method),method_getTypeEncoding(method));
        return YES;
    }
      return [super resolveInstanceMethod:sel];
} 或者是
void other (id self,SEL _cmd){
    NSLog(@"%@-%S--%s",self,sel_getName(_cmd),__func__);
}

+ (Bool)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)){
   
        class_addMethod(self,sel,(IMP)other,"V16@0:8");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
} 
六:消息转发,将消息转发给别人
//将aSelector消息转发给target对象MJCat对象
-  (id)forwardingTargetForSeletor:(SEL)aSelector{
    if(aSelecor == @selector(test)){
        return [[MJCat alloc]init];
    }
}//如果这个方法没有实现,那么会调用下面两个方法
//方法签名:返回值类型,参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
      if(aSelector == @selector(test)) {
            return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
      }
      return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括:方法调用者/方法名/方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    //anInvocation.target 方法调用者
    //anInvocation.selector 方法名
    //[anInvocation getArgument: atIndex:];参数等
    aninvocation.target = [[MJCat alloc]init];
    [anInvocation invoke];//方法调用
}

当来到消息转发的流程后,会调用forwardingTargetForSelector:方法,如果返回值不为空,那么直接objc_msgSend(对象,SEL);如果返回值为nill,调用methodSignatureForSelector:方法,返回值不为空,那么调用forwardInvocation:方法,如果返回值为nill,调用doesNotRecognizeSelector:方法

七:super的本质
在CJStudent中实现init方法, CJStudent继承至CJPerson
- (instancetype)init{
      if(self == [super init]){
          NSLog(@"%@",[self class]);//CJStudent
          NSLog(@"%@",[self superclass]);//CJPerson
          NSLog(@"%@",[super class]);//CJStudent
          NSLog(@"%@",[super superclass]);//CJPerson
      }
}

为什么呢?[super class]本质上是调用的objc_msgSendSuper方法, objc_msgSendSuper(struct objc_super{__unsafe_unretained _Nunnull id receiver;__unsafe_unretained _Nunnull Class super_class},@selector(class)); objc_super是一个结构体, receiver成员代表消息接受者, super_class代表消息接受者的父类,作用是在父类中查找该方法的实现;实际上objc_msgSendSuper({self,[CJPerson class]},@selector(class));就是说在父类中查找class的实现,但是class返回的是谁调用,谁是消息调用者,就返回谁的类对象, superclass原理一样

八:-isMemberOfclass:和+isMemberOfclass:和-isKindOfClass:和+isKindOfClass:的区别

-isMemberOfclass:方法调用者的class时候就是传进去的class
-isKindOfClass:方法调用者的class时候就是传进去的class或者是方法调用者的superclass就是传进去的class,类方法以此类推

九:什么是runtime?项目中的作用有哪些?

OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时在进行,OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性的函数,平时编写的OC代码,底层都是转换成对Runtime的API调用
1.关联对象(associatedObject)给分类添加属性
2.遍历类的所有成员变量(修改testFiled的占位文字颜色,字典转模型,接档归档)
3.交换方法实现。。。。

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

推荐阅读更多精彩内容

  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,195评论 0 7
  • 本文转载自:http://southpeak.github.io/2014/10/25/objective-c-r...
    idiot_lin阅读 935评论 0 4
  • Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的...
    有一种再见叫青春阅读 585评论 0 3
  • runtime 和 runloop 作为一个程序员进阶是必须的,也是非常重要的, 在面试过程中是经常会被问到的, ...
    made_China阅读 1,210评论 0 7
  • 下了班回到宿舍,不知是怎的心血来潮,翻箱倒柜,笑盈盈挖出来一台剃毛器,朝着某个地方跃跃欲试。 打住!客官请留步!不...
    人余人阅读 1,117评论 4 5