二、Objective-C对象模型

2018-09-28  本文已影响4人  那样风采

1、简介

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

2、isa指针

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

[NSObject alloc]
NSObject其实就是Class对象: NSObject
Class其实只是一个结构体的指针: Class
objc_class是一个包含isa指针结构体: objc_class 上图中除了isa外还有其他成员变量,但那是为了兼容非2.0版本的OC的遗留逻辑,大家可以忽略!
因为类也是一个对象,所以它也必须是另一个类的实例,这个类就是元类(metaclass)。元类保存了类方法的列表,当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有,则该元类会向它的父类查找,以此类推,一直找到继承链的头!元类也是一个对象,为了设计上的完整,元类的isa指针都会指向一个根元类(root metaclass),跟元类本身的isa指针指向自己,这样就形成了一个闭环!所有子类的元类都会继承父类的元类,即类对象和元类对象有着同样的继承关系: 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];  //创建对象

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

image.png
因为对象在内存中的排布可以看成一个结构体,该结构体的大小并不能动态变化,所以无法在运行时动态地给对象增加成员变量! 对象的方法定义都保存在类的可变区域中,OC2.0并未在头文件中将实现暴露出来,但在OC1.0中,我们可以看到方法的定义列表是一个名为methodLists的指针的指针,通过修改该指针指向的指针的值,就可以动态地为某一个类增加成员方法,这也是category的原理,同时也说明了为什么category不能增加成员变量!!!通过associatedObject关联对象可以变相地给对象增加成员变量,但由于实现机制不一样,所以并不是真正改变了对象的内存结构! 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打印如下:


程序log

要点如下:

  1. #import <objc/runtime.h>
  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
isa-swizzling

其实KVO的实现可能是:

然后我们在分析一下, 在真正调用的setAge:的情况下, 根据消息机制我们知道它先通过isa找到对应对象的类,也就是现在NSKVONotifying_Person,然后再去找setAge:,由于NSKVONotifying_Person这个对象重写了这个方法,那么就会直接取当前的实现,也就是带有willChangeValueForKey:以及didChangeValueForKey:,那么自然就实现了对KVO的实现了。
参考:
isa-swizzling
整理了一下关于KVO的姿势

应用二、Method Swizzling

OC提供了以下API来动态替换类方法或实例方法的实现:

void methodSwizzInstance(Class class, SEL originalSelector, SEL swizzledSelector)
{
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    method_exchangeImplementations(originalMethod, swizzledMethod);
}
上一篇 下一篇

猜你喜欢

热点阅读