runtime 之 Class 和 MetaClass

首先说明,这篇文章几乎都是抄录的别人的博客,简书文章,在此总结,只是为了方便记忆和以后阅读,如果有什么失礼的地方,请大家及时指正。

大神们:(推荐阅读原文)

  1. CSDN博客:小九
  2. CSDN博客:梵尘yst
  3. 简书:曲年

好了,抄录正式开始

每个Objective-C对象都有一个隐藏的数据结构,这个数据结构是Objective-C对象的第一个成员变量,它就是isa指针。
这个isa到底是什么呢?官方介绍是这样的:

Every object is connected to the run-time system through itsisa instance variable, inherited from the NSObject class.isa identifies the object's class; it points to a structurethat's compiled from the class definition. Through isa, anobject can find whatever information it needs at run timesuch asits place in the inheritance hierarchy, the size and structure ofits instance variables, and the location of the methodimplementations it can perform in response to messages.
可见,一个对象(Object)的isa指向了这个对象的类(Class),而这个对象的类(Class)的isa指向了metaclass。这样我们就可以找到静态方法和变量了。
Objective-C的运行时是动态的,它能让你在运行时为类添加方法或者去除方法以及使用反射。这在其它语言是不多见的。

类的实例对象的 isa 指向它的类;类的 isa 指向该类的 metaclass;
类的 super_class 指向其父类,如果该类为根类则值为 NULL;

metaclass 的 isa 指向根 metaclass,如果该 metaclass 是根 metaclass则指向自身;
metaclass 的 super_class 指向父 metaclass,如果该 metaclass 是根 metaclass则指向该 metaclass 对应的类;
Object-C 为每个类的定义生成两个 objc_class ,一个普通的 class,另一个即metaclass。我们可以在运行期创建这两个 objc_class 数据结构,然后使用 objc_addClass将 class注册到运行时系统中,以此实现动态地创建一个新的类。
在NSObject.h里面:

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

再点开 Class 的定义:

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

}

这一些定义对于懂的人自然懂,不会的人根本看不懂。建议看完下面的例子,再点开参考里面的链接仔细看一遍。
现在我们知道的是,对于我们新建的一个类,都会有一个隐藏的属性isa,可以通过它进行一些访问。


我们现在新建一个类Parent,继承于NSObject, 里面有成员方法-(void)selectorP,类方法+(void)ClassSelectorP。

再新建一个类Child,继承于Parent,里面有成员方法-(void)selectorC, 类方法+(void)ClassSelectorC。

现在我们新建一个实例Child* child = [Chlid new];

  1. 当我们调用[child class] 的时候,child就会通过isa指针去找到Child的class。
  1. 当我们调用[child superclass]的时候,child 通过isa找到Child的class,再通过super_class,找到Parent的class。
    在这里,再普及objc_class 的两种类型:
    class     实例对象(child、Child)的isa指向的结构体;
    metaclass  class的isa指向的一个结构体;
  2. 接着,调用[child SelectorC],child通过isa找到Child的class,在class(注意看上面 struct objc_class 的定义)的方法列表里面找到SelectorC;
  3. 再试着调用[child SelectorP],child通过isa找到Child的class,发现class里面并没有这个方法,通过class里面的super_class找到Parent的class,在里面的方法列表找到了SelectorP;
  4. 再是类方法[Child ClassSelectorC],Child(请注意,大写)通过isa找到Child的class,通过class的isa找到Child的metaclass,在metaclass的方法列表里面找到了ClassSelectorC;
  5. 再试着调用[Child ClassSelectorP],Child通过isa找到Child的class,通过class的isa找到Child的metaclass,发现metaclass里面并没有这个方法,通过metaclass里面的super_class找到Parent的metaclass,在里面的方法列表找到了ClassSelectorP;
- (void)viewDidLoad {
    [super viewDidLoad];
    Class clazz = [self class];
    Class clarr = [AroundMapController class];
    Class metalclazz = objc_getMetaClass("AroundMapController");
    if (class_respondsToSelector(metalclazz, @selector(viewWillAppear:))) {
        NSLog(@"ok");
    }
    if (clarr == clazz) {
        NSLog(@"2 ok");
    }
}

这个是验证runtime的机制,可以把这段代码复制到自己的controller.m里面,运行一下。记得把AroundMapController改成自己的controller的名字,还有引入头文件<objc/runtime.h>

这是几个例子基本上已经涵盖了大多数调用的情况。
细心的朋友可能已经发现,上面的例子中,child通过isa找到的类对象,其实就是Child 通过isa找到的class。(如果能理解这一点,基本上也算对objectC的isa机制也算入门)

最后为了理解class和metaclass的作用,大家可以换位思考一下,如果我们作为runtime的设计者,当开发者新建出来一个实例对象child的时候,我们应该存储child的属性和方法,同时又该如何响应其方法调用,最后还要记录与Parent之间的继承关系。
这时候,再来看看,这种图。可以加深对isa机制的理解:

图片.png

参考
http://www.cocoachina.com/iOS/20141018/9960.html
http://blog.csdn.NET/jasonblog/article/details/7246822
http://blog.csdn.Net/totogo2010/article/details/8081253

一.isa指针
要认识什么是isa指针,我们得先明确一点:

在Objective-C中,任何类的定义都是对象。类和类的实例(对象)没有任何本质上的区别。任何对象都有isa指针。

那么什么是类呢?在xcode中用快捷键Shift+Cmd+O 打开文件objc.h 能看到类的定义:

图片.png

可以看出:
Class 是一个 objc_class 结构类型的指针, id是一个 objc_object 结构类型的指针.

我们再来看看 objc_class 的定义:

图片.png

稍微解释一下各个参数的意思:

  1. isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。
  1. super_class:父类,如果该类已经是最顶层的根类,那么它为NULL。
  2. version:类的版本信息,默认为0
  3. info:供运行期使用的一些位标识。
  4. instance_size:该类的实例变量大小
  5. ivars:成员变量的数组

再来看看各个类实例变量的继承关系:

  1. 每一个对象本质上都是一个类的实例。其中类定义了成员变量和成员方法的列表。对象通过对象的isa指针指向类。
  1. 每一个类本质上都是一个对象,类其实是元类(meteClass)的实例。元类定义了类方法的列表。类通过类的isa指针指向元类。
  2. 所有的元类最终继承一个根元类,根元类isa指针指向本身,形成一个封闭的内循环。

二.runtime 机制

  1. runtime:指一个程序在运行(或者在被执行)的状态。也就是说,当你打开一个程序使它在电脑上运行的时候,那个程序就是处于运行时刻。在一些编程语言中,把某些可以重用的程序或者实例打包或者重建成为“运行库"。这些实例可以在它们运行的时候被连接或者被任何程序调用。
  1. objective-c中runtime:是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API。 在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码。

runtime的应用:

  1. 动态创建一个类(比如KVO的底层实现)
  1. 动态地为某个类添加属性\方法, 修改属性值\方法
  2. 遍历一个类的所有成员变量(属性)\所有方法
  3. 实质上,以上的是通过相关方法来获取对象或者类的isa指针来实现的。

相关函数

  1. 增加
  1. 增加函数:class_addMethod
  2. 增加实例变量:class_addIvar
  3. 增加属性:@dynamic标签,或者class_addMethod,因为属性其实就是由getter和setter函数组成
  4. 增加Protocol:class_addProtocol
  5. 获取
  6. 获取函数列表及每个函数的信息(函数指针、函数名等等):class_getClassMethod method_getName ...
  7. 获取属性列表及每个属性的信息:class_copyPropertyList property_getName
  8. 获取类本身的信息,如类名等:class_getName class_getInstanceSize
  9. 获取变量列表及变量信息:class_copyIvarList
  10. 替换
  11. 将实例替换成另一个类:object_setClass
  12. 替换类方法的定义:class_replaceMethod
  13. 其他常用方法:
  14. 交换两个方法的实现: method_exchangeImplementations.
  15. 设置一个方法的实现:method_setImplementation.

三、对meta-class的补充说明(抄录)
比较简单的一篇英文,重点是讲解meta-class。翻译下,加深理解。
原文标题:What is a meta-class in Objective-C?
原文地址:http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html

接下来将会探讨一个在Objective-C中相对陌生的概念 -- meta-class。OC中的每一个类都会有一个与之相关联的meta class,但是你却几乎永远也不会直接使用到,它们始终笼罩着一层神秘的面纱。笔者将以运行时动态创建一个class为引,通过剖析创建的class pair来弄明白到底meta-class是什么以及更深入的了解它对于OC中对象、类的意义。

  1. 在运行时创建类
    以下代码演示运行时创建一个NSError的子类,同时添加一个实例方法给它:
Class newClass =  
    objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);  
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");  
objc_registerClassPair(newClass);  

  1. 函数ReportFunction就是添加的实例方法的具体实现
void ReportFunction(id self, SEL _cmd)  
{  
    NSLog(@"This object is %p.",self);  
    NSLog(@"Class is %@, and super is %@.",[self class],[self superclass]);  
    Class currentClass = [self class];  
    for( int i = 1; i < 5; ++i )  
    {  
        NSLog(@"Following the isa pointer %d times gives %p",i,currentClass);  
        currentClass = object_getClass(currentClass);  
    }  
    NSLog(@"NSObject's class is %p", [NSObject class]);  
    NSLog(@"NSObject's meta class is %p",object_getClass([NSObject class]));  
}  

看起来一切都很简单,运行时创建类只需要三步:

  1. 为"class pair"分配空间(使用objc_allocateClassPair).
  1. 为创建的类添加方法和成员(上例使用class_addMethod添加了一个方法)。
  2. 注册你创建的这个类,使其可用(使用objc_registerClassPair)。

估计读者马上就要问:什么是“class pair"? objc_allocateClassPair只返回一个值:Class。那么pair的另一半在哪里呢?
是的,估计你已经猜到了这个另一半就是meta-class,也就是这篇短文的标题,但是要解释清楚它是什么,为什么需要它,还需要交代下OC的对象与类的相关背景。

一个数据结构何以成为一个对象?

每个对象都会有一个它所属的类。这是面向对象的基本概念,但是在OC中,这对所有数据结构有效。任何数据结构,只要在恰当的位置具有一个指针指向一个class,那么,它都可以被认为是一个对象。
在OC中,一个对象所属于哪个类,是由它的isa指针指向的。这个isa指针指向这个对象所属的class。
实际上,OC中对象的定义是如下的样子:

typedef struct objc_object {  
      Class isa;  
}*id;  

这个定义表明:任何以一个指向Class的指针作为首个成员的数据结构都可以被认为是一个objc_object.
最重要的特性就是,你可以向OC中的任何对象发送消息,如下这样:

【@”stringValue" writeToFile:@"/file.txt atomically:YES encoding: NSUTF8StringEncoding error:NULL];  

运行原理就是,当你向一个OC对象发送消息时(上文的@“stringValue”),运行时库会根据对象的isa指针找到这个对象所属的类(上文为例,会找到NSCFString类).这个类会包含一个所有实例方法的列表及一个指向superclass的指针以便可以找到父类的实例方法。运行时库会在类的方法列表以及父类(们)的方法列表中寻找符合这个selector(上文为例,这个selector是"writeToFile:atomically:encoding:error")的方法。找到后即运行这个方法。关键点就是类要定义这个你发送给对象的消息。

什么是meta-class?

typedef struct objc_class *Class;  
struct objc_class{  
     Class isa;  
     Class super_class;  
    /*followed by runtime specific details...*/  
};  

  1. 为了可以调用类方法,这个类的isa指针必须指向一个包含这些类方法的类结构体。
    这样就引出了meta-class的概念:meta-class是一个类对象的类。
    简单解释下:
  1. 当你向一个对象发送消息时,runtime会在这个对象所属的那个类的方法列表中查找。
  2. 当你向一个类发送消息时,runtime会在这个类的meta-class的方法列表中查找。
  3. meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。

meta-class的类又是什么呢?
完美的闭环

  1. meta-class,就像Class一样,也是一个对象。你依旧可以向它发送消息调用函数,自然的,meta-class也会有一个isa指针指向其所属类。所有的meta-class使用基类的meta-class作为他们的所属类。具体而言,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己所属的类。
  1. 根据这个规则,所有的meta-class使用基类的meta-class作为它们的类,而基类的meta-class也是属于它自己,也就是说基类的meta-class的isa指针指向它自己。(译:完美的闭环)

类和meta-class的继承

  1. 就像一个类使用super_class指针指向自己的父类一样,meta-class的super_class会指向类的super_class的meta-class。一直追溯到基类的meta-class,它的super_class会指向基类自身。(译:万物归根)
  1. 这样一来,整个继承体系中的实例、类和meta-class都派生自继承体系中的基类。对于NSObject继承体系来说,NSObject的实例方法对体系中所有的实例、类和meta-class都是有效的;NSObject的类方法对于体系中所有的类和meta-class都是有效的。

实验证明:

为了证实以上的论述,让我们查看下开篇代码中ReportFunction的输出。这个函数的目的就是沿着isa指针进行打印。
为了运行ErportFunction,我们需要创建一个实例,并调用report方法。

id instanceOfNewClass = [[newClass alloc]initWithDomain:@"some Domain" code:0 userInfo:nil];  
[instanceOfNewClass performSelector:@"report)];  
[instanceOfNewClass release];  

因为我们并没有对report方法进行声明,所以我们使用performSelector进行调用,这样避免编译器警告。
然后ReportFunction函数会沿着isa进行检索,来告诉我们class,meta-class以及meta-class的class是什么样的情况:

【注:ReportFunction使用object_getClass来获取isa指针指向的类,因为isa指针是一个受保护成员,你不能直接访问其他对象的isa指针。ReportFunction没有使用class方法是因为在一个类对象上调用这个方法是无法获得meta-class的,它只是返回这个类而已。(所以[NSString class]只是返回NSString类,而不是NSString的meta-class]

以下是程序的输出:

This object is 0x10010c810.  
Class is RuntimeErrorSubclass, and super is NSError.  
Followingthe isa pointer 1times gives 0x10010c600  
Followingthe isa pointer 2times gives 0x10010c630  
Followingthe isa pointer 3times gives 0x7fff71038480  
Followingthe isa pointer 4times gives 0x7fff71038480  
NSObject's class is 0x7fff710384a8  
NSObject's meta class is 0x7fff71038480  

观察通过isa获得的地址:
对象的地址是 0x10010c810.
类的地址是 0x10010c600.
类的meta-class地址是 0x10010c630.
类的meta-class的类地址是 0x7fff71038480.(即NSOjbect的meta-class)
NSObject的meta-class的类地址是它自身。
这些地址的值并不重要,重要的是它们说明了文中讨论的从类到meta-class到NSObject的meta-class的整个流程。

结论:
meta-class是类对象的类,每个类都有自己单独的meta-class。所有的类对象并不会属于同一个meta-class。
meta-class要保证类对象具有继承体系中基类的所有实例和类方法,以及继承体系中的所有中间类方法。对于所有NSObject继承体系下的类,NSObject的实例方法和协议方法对他们和他们meta-class的对象都要有效。
所有的meta-class使用基类的meta-class作为自己的基类,对于顶层基类的meta-class也是一样,只是它指向自己而已。

再次说明:
这篇文章纯属抄录,感谢各位大神的总结,如果本文有什么不对的地方,请及时指正。

作者:LiYaoPeng
链接:https://www.jianshu.com/p/45fe90253519
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

推荐阅读更多精彩内容

  • 首先说明,这篇文章几乎都是抄录的别人的博客,简书文章,在此总结,只是为了方便记忆和以后阅读,如果有什么失礼的地方,...
    LiYaoPeng阅读 4,711评论 1 14
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,625评论 0 9
  • Objective-C语言是一门动态语言,他将很多静态语言在编译和链接时期做的事情放到了运行时来处理。这种动态语言...
    tigger丨阅读 1,319评论 0 8
  • 原文出处:南峰子的技术博客 Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了...
    _烩面_阅读 1,192评论 1 5
  • 我坐在树荫下看着远处身材修长年轻的男孩路过,心里极度想要冲过去抓住他。 我想要这幅身体,用以将我这幅已然干涸枯瘦的...
    杜三quan阅读 200评论 2 1