1、简介
主要介绍OC对象模型的实现细节,以及OC对象模型对isa swizzling和method swizzling的支持。
2、isa指针
OC是一门面向对象的编程语言,每一个对象都是一个类的实例,在OC语言的内部,每一个对象都有一个名为isa的指针,指向该对象的类。
每一个类描述了一系列它的实例的特点,包括成员变量的列表、成员函数的列表等。
每一个对象都可以接受消息,而对象能够接受的消息列表保存在它所对应的类中。
按照面向对象语言的设计原则,所有事物都应该是对象,在OC语言中,每一个类实际上也是一个对象,每一个类也有一个isa指针,每一个类也可以接收消息:
[NSObject alloc]
NSObject其实就是Class对象:Class其实只是一个结构体的指针:
objc_class是一个包含isa指针结构体:
因为类也是一个对象,所以它也必须是另一个类的实例,这个类就是元类(metaclass)。元类保存了类方法的列表,当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有,则该元类会向它的父类查找,以此类推,一直找到继承链的头!元类也是一个对象,为了设计上的完整,元类的isa指针都会指向一个根元类(root metaclass),跟元类本身的isa指针指向自己,这样就形成了一个闭环!所有子类的元类都会继承父类的元类,即类对象和元类对象有着同样的继承关系:
- NSObject的类中定义了实例方法,例如-(id)init方法和-(void)dealloc方法
- NSObject的元类中定义了类方法,例如+(id)alloc方法、+(void)load方法和+(void)initialize方法
- NSObject的元类继承自NSObject类,所有NSObject类是所有类的根,因此NSObject中定义的实例方法可以被所有对象调用,例如-(id)init方法和-(void)dealloc方法
- NSObject的元类的isa指向自己
3、类的成员变量
把类的实例看成一个C语言的结构体,成员变量排列顺序如下:
isa指针 |
---|
NSObject的成员变量 |
NSObject子类的成员变量 |
NSObject子类的子类的成员变量 |
..... |
父类的成员变量 |
类本身的成员变量 |
验证程序:
@interface car : NSObject {
int _carNO;
}
@end
@implementation car
@end
@interface SUV : car {
int _SUVNO;
}
@end
@implementation SUV
@end
SUV *suv = [[SUV alloc] init]; //创建对象
在程序中插入断点,断点处利用调试器输出对象的结构:
因为对象在内存中的排布可以看成一个结构体,该结构体的大小并不能动态变化,所以无法在运行时动态地给对象增加成员变量! 对象的方法定义都保存在类的可变区域中,OC2.0并未在头文件中将实现暴露出来,但在OC1.0中,我们可以看到方法的定义列表是一个名为methodLists的指针的指针,通过修改该指针指向的指针的值,就可以动态地为某一个类增加成员方法,这也是category的原理,同时也说明了为什么category不能增加成员变量!!!通过associatedObject关联对象可以变相地给对象增加成员变量,但由于实现机制不一样,所以并不是真正改变了对象的内存结构!
因为isa本身也只是一个指针,我们也可以在运行时动态地修改isa指针的值,打到替换对象整个行为的目的!
4、对象模型的应用
4.1 动态创建对象
- (void)viewDidLoad {
[super viewDidLoad];
//创建一个名为XZCustomView的类,它是UIView的子类
Class newClass = objc_allocateClassPair([UIView class], "XZCustomView", 0);
//为该类增加一个名为report的方法
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
//为该类增加一个名为name的成员变量,虽然无法在运行时为已有对象增加成员变量,但是在使用运行时方法创建对象时,可以为对象增加成员变量
//class_addProperty增加属性
class_addIvar(newClass, "_name", sizeof(NSString *), log(sizeof(NSString *)), "I");
//注册该类
objc_registerClassPair(newClass);
id instanceOfNewClass = [[newClass alloc] init];
object_setIvar(instanceOfNewClass, class_getInstanceVariable(newClass, "_name"), @"Lance");
[instanceOfNewClass performSelector:@selector(report)];
}
void ReportFunction(id self, SEL _cmd){
NSLog(@"This object is %p", self);
NSLog(@"This object's name is %@", object_getIvar(self, class_getInstanceVariable([self class], "_name")));
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 thimes 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]));
}
运行测试程序,log打印如下:
要点如下:
#import <objc/runtime.h>
-
objc_allocateClassPair
方法创建新的类 -
class_addMethod
方法来给类增加新的方法
object_setIvar
class_getInstanceVariable
object_getIvar
-
class_addIvar
方法来给类增加新的成员变量 -
objc_registerClassPair
方法来注册新的类 -
object_getClass
方法来获得对象的isa指针所指向的对象
4.2 系统相关API及应用
应用一、isa swizzling
其实KVO的实现可能是:
- 添加Observer
通过runtime偷偷实现了一个子类,并且以NSKVONotifying_+类名来命名,将之前那个对象的isa指针指向了这个子类。
重写了观察的对象setter方法,并且在重写的中添加了willChangeValueForKey:
以及didChangeValueForKey:
- 移除Observer
只是简单的将其的isa指向原来的类对象中
然后我们在分析一下, 在真正调用的setAge:的情况下, 根据消息机制我们知道它先通过isa找到对应对象的类,也就是现在NSKVONotifying_Person,然后再去找setAge:,由于NSKVONotifying_Person这个对象重写了这个方法,那么就会直接取当前的实现,也就是带有willChangeValueForKey:
以及didChangeValueForKey:
,那么自然就实现了对KVO的实现了。
参考:
isa-swizzling
整理了一下关于KVO的姿势
应用二、Method Swizzling
OC提供了以下API来动态替换类方法或实例方法的实现:
- class_replaceMethod替换类方法的定义
当类中没有想替换的原方法时,该方法会调用class_addMethod来为该类增加一个新方法,这也是该API需要传入types参数的原因!!
当需要替换的方法有可能不存在时,可以考虑该方法! -
method_exchangeImplementations交换两个方法的实现
其实内部实现是调用了两次method_setImplementation!当需要交换两个方法的实现时使用!
- method_setImplementation设置一个方法的实现
最简单的用法,当仅仅需要为一个方法设置其实现方式时使用!
void methodSwizzInstance(Class class, SEL originalSelector, SEL swizzledSelector)
{
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}