OC 对象的分类
Objective-C 中的对象,简称OC对象,主要可以分主3种:
-
instance 对象(实例对象)
-
定义:instance 对象就是通过类 alloc 出来的对象,每次调用 alloc 都会产生新的 instance 对象;
NSObject *object1 = [[NSObject alloc] init]; NSObject *object2 = [[NSObject alloc] init];
- object1、object2 是 NSObject 的 instance 对象(实例对象)
- 它们是不同的两个对象,分别占据着两块不同的内存
-
instance 对象在内存中存储的信息包括:isa 指针、其他成员变量
-
-
class 对象(类对象)
示例代码:
#import "ViewController.h" #import <objc/runtime.h> @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSObject *object1 = [[NSObject alloc] init]; NSObject *object2 = [[NSObject alloc] init]; //实例对象的内存地址值输出 NSLog(@"%p %p",object1,object2); /** 输出结果如下: 0x600000f440a0 0x600000f440f0 */ /** 一个类的类对象是惟一的; */ Class objectClass1 = [object1 class]; Class objectClass2 = [object2 class]; Class objectClass3 = [NSObject class]; Class objectClass4 = object_getClass(object1); Class objectClass5 = object_getClass(object2); //类对象的内存地址值输出 NSLog(@"%p",objectClass1); NSLog(@"%p",objectClass2); NSLog(@"%p",objectClass3); NSLog(@"%p",objectClass4); NSLog(@"%p",objectClass5); /** 输出结果如下: 0x1b8c94148 0x1b8c94148 0x1b8c94148 0x1b8c94148 0x1b8c94148 */ }
输出结果如下:
0x600000f440a0 0x600000f440f0
0x1b8c94148
0x1b8c94148
0x1b8c94148
0x1b8c94148
0x1b8c94148小结:
- object1、object2 是 NSObject 的 instance 对象(实例对象),它们是不同的两个对象,分别占据着两块不同的内存
- objectClass1 ~ objectClass5 都是 NSObject 的 class对象(类对象),它们是同一个对象。每个类在内存中有且只有一个类对象
class 对象在内存中存储的信息主要包括
- isa 指针
- superclass 指针
- 类的属性信息(@property)
- 类的对象方法信息(instance method)
- 类的协议信息(protocol)
- 类的成员变量信息(ivar)
-
meta-class 对象(元类对象)
示例代码:
#import "ViewController.h" #import <objc/runtime.h> @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"===============实例对象输出=================="); NSObject *object1 = [[NSObject alloc] init]; NSObject *object2 = [[NSObject alloc] init]; //基类的实例对象的内存地址值输出 NSLog(@"%p %p",object1,object2); /** 输出结果如下: 0x600003e103b0 0x600003e10380 */ NSLog(@"================类对象输出================="); /** 一个类的类对象是惟一的; 类对象调用 class 方法返回的一直是 class 对象 */ Class objectClass1 = [object1 class]; Class objectClass2 = [object2 class]; Class objectClass3 = [NSObject class]; Class objectClass4 = object_getClass(object1); Class objectClass5 = object_getClass(object2); //基类的类对象的内存地址值输出 NSLog(@"%p",objectClass1); NSLog(@"%p",objectClass2); NSLog(@"%p",objectClass3); NSLog(@"%p",objectClass4); NSLog(@"%p",objectClass5); /** 输出结果如下: 0x1b8c94148 0x1b8c94148 0x1b8c94148 0x1b8c94148 0x1b8c94148 */ NSLog(@"===============元类对象输出================="); Class objectMetaClass = object_getClass(objectClass1); //基类的元类对象的地址输出 NSLog(@"%p",objectMetaClass); /** 输出结果如下: 0x1b8c940f8 */ NSLog(@"============================================"); /** 基类的元类对象调用 class 方法,返回的仍然是它本身 */ NSLog(@"%p",[objectMetaClass class]); /** 输出结果如下: 0x1b8c940f8 */ NSLog(@"============查看是否是元类对象==============="); /** 查看是否是元类对象 */ BOOL result = class_isMetaClass([NSObject class]); if(result) { NSLog(@"[NSObject class] 返回的结果是元类对象"); }else { NSLog(@"[NSObject class] 返回的结果不是元类对象"); } /** 输出结果: [NSObject class] 返回的结果不是元类对象 */ } @end
输出结果如下:
===============实例对象输出==================
0x600003e103b0 0x600003e10380
================类对象输出=================
0x1b8c94148
0x1b8c94148
0x1b8c94148
0x1b8c94148
0x1b8c94148
===============元类对象输出=================
0x1b8c940f8
============================================
0x1b8c940f8
============查看是否是元类对象===============
[NSObject class] 返回的结果不是元类对象
- objectMetaClass 是 NSObject 的 meta-class对象(元类对象)
- 每个类在内存中有且只有一个 meta-class 对象
- meta-class 对象和 class 对象的内存结构是一样的(都是 Class 指针类型),但是用途不一样,元类对象在内存中存储的信息主要包括:
- isa 指针;
- superclass 指针;
- 类的类方法信息(class method)
小结:
-
实例对象:
- 通过 alloc 出来的对象是实例对象;
- 实例对象存储了 isa 指针以及其他的成员变量。
-
类对象:
- 调用实例对象或者类对象的 class 方法 或者执行 runtime 的 object_getClass 方法并将实例对象作为参数传入,返回的结果都是类对象;
- 类对象在内存中只有一份,即是惟一的。类对象无论调用多少次 class 方法,最终返回的依然还是类对象;
- 类对象主要存储 isa 指针、superclass 指针、属性信息、对象方法信息、协议信息、成员变量信息(如变量的类型、变量的名字)等“只需定义一份”的信息,类似于 Java 中“类”的概念,相当于定义了一个“模子”。只是这个“模子”不包含类方法,oc 将类方法存储到了元类对象当中。
-
元类对象:
- 执行 runtime 的 object_getClass 方法并将类对象作为参数传入,返回的结果是元类对象;
- 元类对象调用 class 方法,返回的是基类的元类对象。
- 元类对象主要存储 isa 指针、superclass 指针,以及类方法信息。class_isMetaClass 可用于判断传入的对象是否是元类对象。
-
面试题:
(1)对象的 isa 指向哪里?
- 实例对象的 isa 指定类对象;类对象的 isa 指针指向元类对象;元类对象的 isa 指针指向 NSObject 基类的 元类对象。
相关源码:
(2)OC 的类信息存放在哪里?
-
OC 的实例对象存储了成员变量信息(成员变量的值);
-
OC 的类对象存储了类信息中的属性信息、对象方法信息、协议信息、成员变量信息(如成员变量的类型、成员变量的变量名等)等;
-
元类对象存储了类信息中的类方法信息等;
-
runtime 的相关源码在 obj4 中查看;
-
objc_getClass 和 object_getClass 方法的区别:
源码对比如下:
(1)object_getClass 的源码:
/*********************************************************************** * object_getClass. * Locking: None. If you add locking, tell gdb (rdar://7516456). **********************************************************************/ Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; }
(2)objc_getClass 的源码:
/*********************************************************************** * objc_getClass. Return the id of the named class. **********************************************************************/ Class objc_getClass(const char *aClassName) { if (!aClassName) return Nil; // NO unconnected, YES class handler return look_up_class(aClassName, NO, YES); }
通过源码可以看出,objc_getClass 是将类名作为参数,参数是一个字符串,通过字符串返回对应的类对象;
object_getClass 是将对象作为参数:
-
传入的如果是实例对象,则返回类对象;
-
传入的如果是类对象,则返回元类对象;
-
传入的如果是元类对象,则返回基类的元类对象;
-
-
OC 的消息机制
如:
Person *person = [[Person alloc] init]; [person personInstanceMethod]; //最后一行代码,底层实现可转为:objc_msgSend(person,@selector(personInstanceMethod));
-
方法的调用问题
- 实例对象的 isa 指向 类对象:当调用对象方法时,通过 instance 的 isa 找到 class,最后找到对象方法的实现进行调用。
- 类对向的 isa 指向元类对象:当调用类方法时,通过 class 的 isa 找到 meta-class,最后找到类方法的实现进行调用。
-
superClass 指针
- 作用:如果是类对象的 superClass 指针,则指向父类的类对象;如果是元类对象的 superClass 指针,则指向父类的元类对象。
- 类对象的superClass 指针的方法调用流程:
- 假设子类实例对象调用父类的实例方法(eg: [stu personInstanceMethod]),则调用流程为:
- 第一步:先通过实例对象的 isa 指针找到子类的类对象(因为实例方法存在该子类的类对象中),在该子类的类对象的对象方法列表中找要调用的方法;能找到则直接调用,找不到则进入第二步;
- 第二步:如果在子类类对象的实例方法列表中找不到要调用的方法,此时通过子类类对象的 superClass 指针去找该子类的父类类对象,进入第三步;
- 第三步:找到子类的父类类对象后,会看父类类对象的实例方法列表中是否存在要调用的方法,如果有则直接调用,如果没有,则继续通过该父类类对象的 superClass 指针去找该父类的父类类对象,一层层往上找,直到找到 NSObject,如果所有的父类类对象的实例方法列表中都不存在该方法的实现,则代码直接闪退,会报 unrecognized selector 的经典错误;
- 场景举例:当 Student 的 instance 对象要调用 Person 的实例方法时,会先通过 isa 找到 Student 的 class,然后通过 superclass 找到 Person 的 class,最后找到实例方法进行调用。
- 假设子类实例对象调用父类的实例方法(eg: [stu personInstanceMethod]),则调用流程为:
- 元类对象的 superClass 指针的方法调用流程:
- 假设子类对象调用父类的类方法(eg: [Student personClassMethod]),则调用流程与类对象的 superClass 指针类似,如下:
- 第一步:先通过子类类对象的 isa 找到该子类的元类对象(因为类方法是存在该子类的元类对象中),看该子类的元类对象的类方法列表中是否存在要调用的方法,如果有则直接调用,如果没有则进入第二步;
- 第二步:如果在子类元类对象的类方法列表中找不到要调用的类方法,此时通过子类的元类对象的 superClass 指针去找该子类的父类的元类对象,进入第三步;
- 第三步:找到子类的父类元类对象后,会看父类元类对象的类方法列表中是否存在要调用的方法,如果有则直接调用,如果没有,则继续通过该父类的 superClass 指针去找该父类的父类类对象,一层层往上找,直到找到 NSObject。如果所有父类元类对象的类方法列表中都不存在该方法的实现,则代码直接闪退,会报 unrecognized selector 的经典错误;
- 场景举例:当 Student 的 class 要调用 Person 的类方法时,会先通过 isa 找到 Student 的 meta-class,然后通过 superclass 找到 Person 的 meta-class,最后找到类方法的实现进行调用。
- 假设子类对象调用父类的类方法(eg: [Student personClassMethod]),则调用流程与类对象的 superClass 指针类似,如下:
-
小结:类对象的 superClass 指针指向父类的类对象;元类对象的 superClass 指针指向父类的元类对象;
附苹果官方图:
对象的isa 指针和 superClass 指针.png