美文网首页
iOS进阶-底层探索

iOS进阶-底层探索

作者: leo_guo | 来源:发表于2018-06-05 15:40 被阅读3次

Objective-C对象模型

isa指针

Objective-C是一门面向对象的编程语言,每一个对象都是一个实例。在其内部,每一个对象都有一个名为isa的指针,指向该对象的类。 NSObject其实就是一个包含isa指针的结构体。查看NSObject的class:

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

再点runtime的头文件看看runtime的class,也是一个包含isa指针的结构体:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

其中除了isa外还有其他成员变量,但那时为了兼容非objective-c 2.0版的遗留逻辑,现在可以忽视。
因为类也是一个对象,所以它也必须是另一个类的实例,这个类就是元类。元类保存了类方法的列表,当一个类方法被调用时,元类首先查找它本身是否有该类方法的实现,如果没有,则该元类回向它的父类查找该方法,这样可以一直找到继承链的头。
元类也是一个对象,那么元类的isa指针又指向哪里呢?为了设计上的完整,所有的元类isa指针都会指向一个根元类。根元类本身的isa指向自己,这样就形成了一个闭环。上面提到,一个对象能够接收消息列表是保存在它所对应的类中的。在实际编程中,我们几乎不会遇到向元类发送消息的情况,那它的isa指针在实际上很少用到。不过这么设计保证了面向对象概念在OC语言中的完整,即语言中所有的事物都是对象,都有isa指针。

类的成员变量

如果把类的实例看成一个C语言的结构体,上面说的isa指针就是这个结构体的第一个成员变量,而类的其它成员变量依次排列在结构体中。排列顺序如下图所示:


1528185836680.jpg

对象模型的应用

动态创建对象

我们可以使用OC语言提供的tuntime函数,动态的创业一个新的类,并且通过相关的方法来获得isa指针的值,从而了解对象的内部结构
我们先来看动态创建类的代码:

#import "ViewController.h"
#import <objc/runtime.h>
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //创建一个CustomView的类,它是UIView的子类
    Class newClass = objc_allocateClassPair([UIView class], "CustomView", 0);
    //为该类增加一个名为report的方法
    class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
    //注册该类
    objc_registerClassPair(newClass);
    
    //创建一个CustomView的类的实例
    id instanceOfNewClass = [[newClass alloc]init];
    //调用report方法
    [instanceOfNewClass performSelector:@selector(report)];
}
//实现report方法
void ReportFunction(id self,SEL _cmd){
    NSLog(@"This object is %p",self);
    NSLog(@"Class is %@,and Super is %@.",[self class],[self superclass]);
    
    Class currentClass = [self class];
    for (int i=0; i<5; i++) {
        NSLog(@"following the isa pointer %d times 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]));
}

最终我们得到的结果如下:

2018-06-05 17:06:08.193400+0800 ThreadStuty01[882:131115] This object is 0x7ffd7ac07630
2018-06-05 17:06:08.193588+0800 ThreadStuty01[882:131115] Class is CustomView,and Super is UIView.
2018-06-05 17:06:08.193676+0800 ThreadStuty01[882:131115] following the isa pointer 0 times gives 0x6000002582a0
2018-06-05 17:06:08.193738+0800 ThreadStuty01[882:131115] following the isa pointer 1 times gives 0x600000257fa0
2018-06-05 17:06:08.193800+0800 ThreadStuty01[882:131115] following the isa pointer 2 times gives 0x1060c8e58
2018-06-05 17:06:08.194008+0800 ThreadStuty01[882:131115] following the isa pointer 3 times gives 0x1060c8e58
2018-06-05 17:06:08.194194+0800 ThreadStuty01[882:131115] following the isa pointer 4 times gives 0x1060c8e58
2018-06-05 17:06:08.194382+0800 ThreadStuty01[882:131115] NSObject's class is 0x1060c8ea8
2018-06-05 17:06:08.194601+0800 ThreadStuty01[882:131115] NSObject's meta class is 0x1060c8e58

从结果可以清晰的看到:
1.从customView对象开始,在我们连续读取了四次isa指针所指向的对象后,isa指针所指向的地址变成了0x1060c8e58,也就是我们说的NSObject元类的地址。之后我们第4次取isa指针所指向的对象时,地址仍是0x1060c8e58,这说明NSObject元类的isa指针确实是指向它自己的。
2.作为对比,我们在代码最后获取了NSObject类的isa地址,我们看到都是0x1060c8e58,这说明所有的元类对象的isa指针都是指向NSObject元类的。

系统相关API及其应用

isa swizzling的应用

系统提供的KVO的实现,就利用了动态的修改isa指针值的技术。

Method swizzling API的说明

OC提供一下API来动态替换类方法或实例方法的实现
class_replaceMethod 替换类方法的定义,当需要替换的方法有可能不存在时,考虑使用该方法,如果不存在,会调用class_addMethod来为该类增加一个新方法。
method_exchangeImplementations 交换两个方法的实现
method_setImplementation 设置一个方法的实现方式。

实际应用如下,例如为viewWillAppear:设置为view的drawRect实现方法

 Class class = object_getClass((id)self);
    SEL will = @selector(viewWillAppear:);
    SEL draw = @selector(drawRect:);

    Method oldMethod =class_getClassMethod(class, will);
    Method newMethod = class_getInstanceMethod([UIView class], draw);
    //替换方法
    class_replaceMethod(class, will, method_getImplementation(draw), method_getTypeEncoding(draw));
    //交换方法
    method_exchangeImplementations(oldMethod, newMethod);
    //设置方法实现
    method_setImplementation(oldMethod, method_getImplementation(newMethod));
    //添加方法
    class_addMethod(class, will, method_getImplementation(draw), method_getTypeEncoding(draw));

以上方法在一些第三方开源库中都有大量使用,大家有空可以去看下,使其能熟练应用到自己的开发工作中。

相关文章

网友评论

      本文标题:iOS进阶-底层探索

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