Object-C Runtime 运行时

Runtime 运行时

参考文章:
玉令天下的博客
特酷吧
南峰子的技术博客
顾 鹏


Objective-C是动态语言,所以需要编译器的同时也需要一个运行时系统来动态创建对象、传递消息。在你需要的时候还能对其进行扩展,解决问题。Objective-C的Runtime是一个运行时的库(Runtime Library),主要使用C和汇编写的库,为C添加了面向对象的能力并创造了Objective-C。这个运行时系统就像一个操作系统一样:它让所有的工作正常运行。有动态类型(Dynamic typing),动态绑定(Dynamic binding)和动态加载(Dynamic loading)
Runtime有两个版本:ModernLegacy。Object—C2.0采用的是Modern版本的Runtime,只能在iOS和OS X10.5之后的64位程序上运行。32位程序采用Legacy版本的Runtime。它们的区别在于更改实例变量时,Legary需要重新编译其子类,而Modern`则不需要。

  • 封装,在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,这些结构体和函数被runtime封装后,可以创建检查修改类对象和他们的方法。

  • 找出方法的最终执行代码:当程序执行object doSomething时,会向消息接受者发送一条消息,runtime会根据消息接受者是否能响应该消息而做出不同的反应。

  • 在编译时你写的Objective-C 函数调用的语法都会被翻译成一个C的函数调用,例如我们发送一个消息[receiver message]会被编译器转化为[objc_msgSend(receiver,selector)]如果有参数的话则为[objc_msgSend(receiver,selector,arg1,arg2)],如果消息的接受者能够找到对应的方法,就执行此方法,如果没有,要么消息被转发,或是动态添加此消息,以上都没有的话程序就会crash。

  • 从Object-C是动态语言理解的话就是,[receiver message]在编译的时候只是知道我将要向receiver发送一个message但是还没发送甚至不知道是否有这个消息以及这个消息有没有具体实现,只有在程序 运行时才回去查找实现,所以说Runtime对于Object-C来说非常重要,但其实通过终端我们是可以看到转换后的样子的。

打开终端进入工程目录

找到工程的.main文件层级 输入命令 clang -rewrite-objc main.m
文件夹内就可以找到.cpp文件,打开可以看到很多C的代码所以有人说OC是假的面向对象

  • isMemberOfClass 只判断是否属于当前类的 实例 至于父类是什么关系它不管
  • isKindOfClass 则能判断继承关系
  • respondsToSelector 判断实例是否有这样方法
  • instancesRespondToSelector 判断类是否有这个方法不能用在类的对象

Class为指向类的结构体指针typedef struct objc_class *Class;,该类的结构体包含:

struct objc_class {

    Class isa  OBJC_ISA_AVAILABILITY; // isa指针


#if !__OBJC2__

    Class super_class                  OBJC2_UNAVAILABLE;  // 父类

    const char *name                   OBJC2_UNAVAILABLE;  // 类名

    long version                       OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0

    long info                          OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识

    long instance_size                 OBJC2_UNAVAILABLE;  // 该类的实例变量大小

    struct objc_ivar_list *ivars       OBJC2_UNAVAILABLE;  // 该类的成员变量链表

    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表

    struct objc_cache *cache           OBJC2_UNAVAILABLE;  // 方法缓存

    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表

#endif


} OBJC2_UNAVAILABLE;

isa 指针指向objc_class结构体的指针

  • 每一个类的实例对象都有一个isa指针指向它的类,而每个类也有个isa指针指向它的元类 [metaClass] 元类里保存了类方法的列表。
  NSObject *objc = [[NSObject alloc]init];
  objc->isa; // 警告:会提示你用 **object_getClass(objc);**替换
  • 当实例对象调用实例方法时,isa指针会到该实例方法的类Subclass(class)中去查询是否有此实例方法,如果没有则会去它的父类Superclass(class)中去查找,一层一层向上查找。
  • 当调用类方法时,isa指针会去该类元类中去查找Subclass(meta)如果没有则会去到元类的父类中去查找 Superclass(meta)
  • 万物皆对象,元类也不例外,元类也有它的isa指针,元类的isa指针只指向元类的根 根元类Root Class(meta) 同理根源类也是对象,它的isa指针指向 自己本身 因为根元类已经是最顶层的根类就像NSObject或NSProxy,它的父类则指向nil。
    上面所述的三条总结起来就如下图:
SunnyXX之前的题

为什么[NSObject foo]可以调用实例方法?

Method 方法的类型

typedef struct objc_method *Method;
objc_method结构体如下

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}  
  • method_name很显然是方法名 类型是SEL稍后解释。
  • method_types char类型的指针用来存储参数类型和返回值类型。
  • method_imp IMP类型 是个函数指针 稍后解释。

SEL

等同于Object-C中selectorselector是方法选择器
typedef struct objc_selector *SEL;SEL是指向objc_selector的指针,不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器 因为不同类的实例对象用相同的方法选择器时,会在各自的消息选择、实现地址、方法链表中根据 selector 去查找具体的方法实现IMP。这也是动态的过程,也就是说在运行之前甚至编译的时候我们都不知道最终会执行哪行代码。

IMP

IMP在objc中的定义为`typedef id (*IMP)(id, SEL, ...);
前面也提到它是个 函数指针上面SEL里提到只有在编译的时候才会知道最终实现哪行代码,就是因为编译时IMP这个函数指针指向了最终实现的代码。方法名相同的时候通过IMP的参数类型就能将它们区分开找到特定的那个方法的实现。

Cache

  • [receiver message]当对象接收message时isa指针会去对应的对象中去查找该message,就相当于遍历MethodList,但是只有message这一个方法是我们所需要的,每次接收message时就要遍历一遍MethodList,这样太低效。所以引入了cache,将我们使用过的方法放到缓存中下次调用时先去cache中查找如果没有再去MethodList中遍历。

  • objc_cache结构体指针:

struct objc_cache {

    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;

    unsigned int occupied                                    OBJC2_UNAVAILABLE;

    Method buckets[1]                                        OBJC2_UNAVAILABLE;

};

南峰子的技术博客对objc_cache结构体描述的很详细:

  • mask:一个整数,指定分配的缓存bucket的总数。在方法查找过程中,Objective-C runtime使用这个字段来确定开始线性查找数组的索引位置。指向方法selector的指针与该字段做一个AND位操作(index = (mask & selector))。这可以作为一个简单的hash散列算法。
  • occupied:一个整数,指定实际占用的缓存bucket的总数。
  • buckets:指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长

Ivar 实例变量的类型

typedef struct objc_ivar *Ivar;

struct objc_ivar {
  char *ivar_name                                          OBJC2_UNAVAILABLE; // 名称
  char *ivar_type                                          OBJC2_UNAVAILABLE; // 类型
  int ivar_offset                                          OBJC2_UNAVAILABLE; // 偏移量
#ifdef __LP64__
  int space                                                OBJC2_UNAVAILABLE;
#endif
}  

id是一个objc_object类型指针

  • typedef struct objc_object *id;

引入头文件
#import <objc/runtime.h>
#import <objc/message.h>

`#import <objc/runtime.h>`
`#import <objc/message.h>`
Class cls = [per class];// 获取自己的类
const char *className =  class_getName(cls); // 获取类名
NSLog(@"%s",className);

 // 2. meta-class 元类
 // 1>当我们调用实例方法时,系统会去类的列表里找对应名字的方法
 // 2>当我们调用类方法时,系统会去meta-class(元类)的列表中找对应名字的方法
        
  // 寻找对象的元类 object_getClass(填入的是类对象)
       Class metaClass =  object_getClass(cls);
       BOOL isMetaClass = class_isMetaClass(metaClass);// 判断是不是元类
       NSLog(@"是否是元类%d",isMetaClass);
        
       // 3. 获取属性列表 如果有属性count就有值
        unsigned int count = 0;
        Ivar *ivarList = class_copyIvarList(cls,&count);
        // 循环遍历类获取其属性
        for (int i = 0; i < count; i++) {
            Ivar ivar = ivarList[i];
            NSLog(@"Person类的成员变量列表下标%d以及属性名%s",i,ivar_getName(ivar)); // _name _sex _age _hobby
        }
        
        // 4.获取属性列表
        unsigned int outCount = 0;
        objc_property_t *properList = class_copyPropertyList(cls, &outCount);
        for (int i = 0; i < outCount; i++) {
            objc_property_t property = properList[i];
            
            unsigned int attributeCount = 0;
         objc_property_attribute_t *attribute =  property_copyAttributeList(property,&attributeCount); // 属性列表内部
            for (int j = 0 ; j < attributeCount; j++) {
                NSLog(@"%s : %s",attribute[j].name,attribute[j].value);
            }
            
            NSLog(@"Person类的属性列表下标%d以及属性名%s",i,property_getName(property));
        }
        
        // 方法列表
        unsigned int methCount= 0;
       Method *methList = class_copyMethodList(cls,&methCount);
        for (int i = 0; i < methCount; i++) {
           Method meth = methList[i];
            NSLog(@"Person方法名%@",NSStringFromSelector(method_getName(meth)));
        }

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,635评论 0 9
  • 原文出处:南峰子的技术博客 Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了...
    _烩面_阅读 1,192评论 1 5
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,109评论 0 7
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 694评论 0 2
  • 这是预览模式 一级标题 二级标题 三级标题 四级标题 五级标题 六级标题 文本1 文本2 文本3 另外一种格式 文...
    waderwu阅读 115评论 0 0