二、Objective-C对象模型

1、简介

主要介绍OC对象模型的实现细节,以及OC对象模型对isa swizzling和method swizzling的支持。

2、isa指针

OC是一门面向对象的编程语言,每一个对象都是一个类的实例,在OC语言的内部,每一个对象都有一个名为isa的指针,指向该对象的
每一个类描述了一系列它的实例的特点,包括成员变量的列表、成员函数的列表等。
每一个对象都可以接受消息,而对象能够接受的消息列表保存在它所对应的类中。
按照面向对象语言的设计原则,所有事物都应该是对象,在OC语言中,每一个类实际上也是一个对象,每一个类也有一个isa指针,每一个类也可以接收消息:

[NSObject alloc]

NSObject其实就是Class对象:
二、Objective-C对象模型_第1张图片
NSObject

Class其实只是一个结构体的指针:
Class

objc_class是一个包含isa指针结构体:
二、Objective-C对象模型_第2张图片
objc_class
上图中除了isa外还有其他成员变量,但那是为了兼容非2.0版本的OC的遗留逻辑,大家可以忽略!

因为类也是一个对象,所以它也必须是另一个类的实例,这个类就是元类(metaclass)。元类保存了类方法的列表,当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有,则该元类会向它的父类查找,以此类推,一直找到继承链的头!元类也是一个对象,为了设计上的完整,元类的isa指针都会指向一个根元类(root metaclass),跟元类本身的isa指针指向自己,这样就形成了一个闭环!所有子类的元类都会继承父类的元类,即类对象和元类对象有着同样的继承关系:
二、Objective-C对象模型_第3张图片
image.png
我们从图中可以看出:
  1. NSObject的类中定义了实例方法,例如-(id)init方法和-(void)dealloc方法
  2. NSObject的元类中定义了类方法,例如+(id)alloc方法、+(void)load方法和+(void)initialize方法
  3. NSObject的元类继承自NSObject类,所有NSObject类是所有类的根,因此NSObject中定义的实例方法可以被所有对象调用,例如-(id)init方法和-(void)dealloc方法
  4. 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];  //创建对象

在程序中插入断点,断点处利用调试器输出对象的结构:

二、Objective-C对象模型_第4张图片
image.png

因为对象在内存中的排布可以看成一个结构体,该结构体的大小并不能动态变化,所以无法在运行时动态地给对象增加成员变量

对象的方法定义都保存在类的可变区域中,OC2.0并未在头文件中将实现暴露出来,但在OC1.0中,我们可以看到方法的定义列表是一个名为methodLists的指针的指针,通过修改该指针指向的指针的值,就可以动态地为某一个类增加成员方法,这也是category的原理,同时也说明了为什么category不能增加成员变量!!!通过associatedObject关联对象可以变相地给对象增加成员变量,但由于实现机制不一样,所以并不是真正改变了对象的内存结构!
二、Objective-C对象模型_第5张图片
methodLists

因为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打印如下:


二、Objective-C对象模型_第6张图片
程序log

要点如下:

  1. #import
  2. objc_allocateClassPair方法创建新的类
  3. class_addMethod方法来给类增加新的方法
object_setIvar
class_getInstanceVariable
object_getIvar
  1. class_addIvar方法来给类增加新的成员变量
  2. objc_registerClassPair方法来注册新的类
  3. object_getClass方法来获得对象的isa指针所指向的对象

4.2 系统相关API及应用

应用一、isa swizzling
二、Objective-C对象模型_第7张图片
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交换两个方法的实现
    二、Objective-C对象模型_第8张图片
    exchange

    其实内部实现是调用了两次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);
}

你可能感兴趣的:(二、Objective-C对象模型)