美文网首页
二、Objective-C对象模型

二、Objective-C对象模型

作者: 那样风采 | 来源:发表于2018-09-28 17:09 被阅读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的实现可能是:

    • 添加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交换两个方法的实现 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对象模型

          本文链接:https://www.haomeiwen.com/subject/rioanftx.html