iOS RunTime 学习记录1_类和对象

前言:我是参考 南峰子 的博客加上自己理解写的,原著专辑大家自己可看:http://southpeak.github.io/categories/objectivec/

iOS中的类(Class)和对象(objc)都是在RunTime 中实现的,自己学习人家的博客做点笔记,提醒一下自己:

类(Class)

为什么RunTime和这些扯上关系了,好吧,看看人家的目录结构你会发现在OC代码的执行都是离不开RunTime。而代码无非就是类与方法,所以运行时定义了类和方法,并控制他们的执行方式。目录结构如下:

Paste_Image.png

Objective-C 中的类使用的是objc_class 这个结构体表示,在目录objc/runtime.h中查看:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    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

对象指针,Class结构本身也是一个对象,里面的isa指针指向的就是这个对象对应的类,但是Class对象指向的这个类叫做元类metaClass(下面介绍),在这个类调用类方法就可以看出元类的作用了。

super_class

父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL

name

类名

cache

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

应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。比如:

NSArray *array = [[NSArray alloc] init];

方法执行流程
1.[NSArray alloc]先被执行。因为NSArray没有+alloc方法,于是去父类NSObject去查找。
2.检测NSObject是否响应+alloc方法,发现响应,于是检测NSArray类,并根据其所需的内存空间大小开始分配内存空间,然后把isa指针指向NSArray类。同时,+alloc也被加进cache列表里面。
3.接着,执行-init方法,如果NSArray响应该方法,则直接将其加入cache;如果不响应,则去父类查找。
4,在后期的操作中,如果再以[[NSArray alloc] init]这种方式来创建数组,则会直接从cache中取出相应的方法,直接调用。

对象(objc)

在objc/objc.h 中我们看到如下定义

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

可以发现对象结构体就一个isa指针(刚才不是说Class也是元类(metaClass)的对象,所以Class 数据结构中也有一个isa指针)。它指向这个对象对应的类Class上。

NSObject类的alloc和allocWithZone:方法使用函数创建对象时,会调用class_createInstance来创建这个对象结构体objc_objc。

当我们这个调用这个对象的方法selector,RunTime 会通过isa指针找到这个对象对应的类,然后在这个类的方法列表(objc_method_list)中查找这个方法selector,如果找不到就往这个类的父类上面找。找到后执行这个方法。

特殊的类--元类(Meta Class)

上面我们说了,Class本身也是各对象,它也有isa指针,那么它的isa指向了什么地方了?答案就是Meta Class。

meta-class是一个类对象的类。

当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。

比如下面我们初始化NSArray用类方法实现:

NSArray *array = [NSArray array];

这里 +array 方法被NSArray调用,这里NSArray是个类,也是个对象objc_object,这个对象不是通过alloc或allocWithZone:方法创建的,所以没用通过NSArray 父类NSObject最后通过Class 的class_createInstance创建objc_object。它本身就是objc_object,也有isa指针,为了找到这个方法,这个指针指向那了,其实就是指向NSArray的元类。

meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。

再深入一下,meta-class也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。

结构图:
[图片上传失败...(image-70292-1513910965638)]

其中 Instance of SubClass是某个类的实例对象,它的isa 指针指向SubClass类,如果把Subclass类看成一个对象,那么它的isa指向指向Subclass的元类(meta)。其中元类和元类之间继承关系,不一定依赖于类于类之间继承,但是最后指向了RootClass 根类和RootClass meta根类的元类。其中根类的元类(RootClass meta)又指向根类自己Root Class。

下面有个小例子,帮组自己理解一下

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //实例方法-(Class)class,是获取这个实例对象对应的类
    //类方法+(Class)class 是获取这个类(本身是个实例对象)对应的类
    
    NSLog(@"这个类名是:%@ 父类名是:%@  父父类名:%@ 父父父类:%@",[self class],[self superclass],[[self superclass] superclass],[[[self superclass] superclass] superclass]);
    
    NSLog(@"NSObject这个类名:%@ 的地址:%p",[NSObject class],[NSObject class]);
    NSLog(@"NSOject meta 地址:%p",objc_getClass((__bridge void *)[NSObject class]));
    
    NSLog(@"这个类的地址:%p",[ViewController class]);
    NSLog(@"这个对象地址:%p",self);
    
    
    Class prClass = [self class];
    /**
     *  for循环打印对象的isa地址
     *  第一次打印的是这个对象对应的类(ViewController)的地址,也就是对象isa地址会发现其指向。
     *  第二次打印的是ViewController看成对象后对应类的地址,也就是ViewController 的isa指向地址,也就是ViewController的元类。
     *  第三次,第四次是元类的元类.....
     *
     */
    for (int i = 0; i< 5; i++) {
        
        NSLog(@"循环第%i次打印对象isa地址:%p",i+1,prClass);
        //objc_getClass得到一个类,根据类名(字符串)得到。如果类再
        prClass = objc_getClass((__bridge void *)prClass);
    }
}

输出结果:

**2016-07-27 11:45:40.088 RunTime_****类与对象****[73165:31955041] ****这个类名是:****ViewController ****父类名是:****UIViewController  ****父父类名:****UIResponder ****父父父类****:NSObject**
**2016-07-27 11:45:40.088 RunTime_****类与对象****[73165:31955041] NSObject****这个类名:****NSObject ****的地址:****0x109145170**
**2016-07-27 11:45:40.089 RunTime_****类与对象****[73165:31955041] NSOject meta ****地址:****0x0**
**2016-07-27 11:45:40.089 RunTime_****类与对象****[73165:31955041] ****这个类的地址:****0x1088e7d58**
**2016-07-27 11:46:54.516 RunTime_****类与对象****[73165:31955041] ****这个对象地址:****0x7facd249fb40**
**2016-07-27 11:47:21.049 RunTime_****类与对象****[73165:31955041] ****循环第****1****次打印对象****isa****地址****:0x1088e7d58**
**2016-07-27 11:47:54.710 RunTime_****类与对象****[73165:31955041] ****循环第****2****次打印对象****isa****地址****:0x0**
**2016-07-27 11:47:54.711 RunTime_****类与对象****[73165:31955041] ****循环第****3****次打印对象****isa****地址****:0x0**
**2016-07-27 11:47:54.711 RunTime_****类与对象****[73165:31955041] ****循环第****4****次打印对象****isa****地址****:0x0**
**2016-07-27 11:47:54.711 RunTime_****类与对象****[73165:31955041] ****循环第****5****次打印对象****isa****地址****:0x0**

代码注释中我说明了这种关系。我画了一个简易的图如下:

元类关系.jpg

好吧,第一课学习到这吧,我要改代码去了! 屌丝........

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

推荐阅读更多精彩内容