isa指针

作者: 轰炸机上调鸡尾酒 | 来源:发表于2017-12-06 17:01 被阅读31次

    最开始让我有疑问的是在使用类方法,在控制台下仍然发现Class对应的有地址,类没有初始化,为什么会有地址?这个地址是谁的地址?
    这个问题有点复杂,刨根问底,我查阅OC中的类的实现:

    • Class是一个指向objc_class(类)结构体的指针,而id是一个指向objc_object(对象)结构体的指针。

    • objec_object(对象)中isa指针指向的类结构称为objec_class(该对象的类),其中存放着普通成员变量与对象方法 (“-”开头的方法)。

    • objec_class(类)中isa指针指向的类结构称为metaclass(该类的元类),其中存放着static类型的成员变量与static类型的方法 (“+”开头的方法)。

    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    
    
    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
     
    #if !__OBJC2__
        Class super_class                       OBJC2_UNAVAILABLE;  // 父类
        const char *name                        OBJC2_UNAVAILABLE;  // 类名
        long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
        long info                               OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识
        long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小
        struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 该类的成员变量链表
        struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表
        struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存
        struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表
    #endif
     
    } OBJC2_UNAVAILABLE;
    
    isa指针.png

    当我们调用某个对象的对象方法时,它会首先在自身isa指针指向的objc_class(类)的methodLists中查找该方法,如果找不到则会通过objc_class(类)的super_class指针找到其父类,然后从其methodLists中查找该方法,如果仍然找不到,则继续通过 super_class向上一级父类结构体中查找,直至根class;

    值得注意的是

    • 所有的metaclass(元类)中isa指针都是指向根metaclass(元类),而根metaclass(元类)中isa指针则指向自身。

    • 根metaclass(元类)中的superClass指针指向根类,因为根metaclass(元类)是通过继承根类产生的。

    • 当我们调用某个类方法时,它会首先通过自己的isa指针找到metaclass(元类),并从其methodLists中查找该类方法,如果找不到则会通过metaclass(元类)的super_class指针找到父类的metaclass(元类)结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查 找,直至根metaclass(元类);

    • 这里有个细节就是要说运行的时候编译器会将代码转化为objc_msgSend(obj, @selector (makeText)),在objc_msgSend函数中首先通过obj(对象)的isa指针找到obj(对象)对应的class(类)。在class(类)中先去cache中通过SEL(方法的编号)查找对应method(方法),若cache中未找到,再去methodLists中查找,若methodists中未找到,则去superClass中查找,若能找到,则将method(方法)加入到cache中,以方便下次查找,并通过method(方法)中的函数指针跳转到对应的函数中去执行。

    object_class结构体中有一个isa指针。一个类实例的isa指针指向的自己本身这个类对象,类对象的isa指向自己元类对象。
    元类对象(metaclass object)中存储的是关于类的信息(类的版本,名字,类方法等)。要注意的是,类对象(class object)和元类对象(metaclass object)的定义都是objc_class结构。所以很明显系统需要开辟内存存储类的信息。这个结构就是元类对象,显而易见这个地址也是元类对象的地址。

    object_getClass()就是顺着isa的指向链找到对应的类,我们一会要验证这个isa的指向链是否与上面图中是一致的,就是用这个方法。

    + (Class)class {
        return self;
    }
    - (Class)class {
        return object_getClass(self);
    }
    

    这是NSObject类里实例方法class与类方法class的实现,这里再强调一下:类方法是在meta class里的,类方法就是把自己返回,而实例方法中是返回实例isa的类,我们要验证这个isa的指向链的时候不能用这种方法。

    对于普通对象而言比如我们自定义一个Person:

    #import <objc/runtime.h>
    #import "Person.h"
    Person *obj = [Person new];
    NSLog(@"instance         :%p", obj);
    NSLog(@"class            :%p", object_getClass(obj));
    NSLog(@"---------------------------------------------");
    NSLog(@"class            :%p", [obj class]);
    

    Log输出:

    TimerDemo[1718:248402] instance         :0x7fc792530f20
    TimerDemo[1718:248402] class            :0x10ae0e178
    TimerDemo[1718:248402] ---------------------------------------------
    TimerDemo[1718:248402] class            :0x10ae0e178
    

    1.我们发现调用class方法的方式不能得到isa的指向链,但是第一次调用是正确的(class的输出都是0x10ae0e178),为什么? 我们第一次调用的class是实例方法,会返回isa的类,object_getClass调用返回的是isa指向的对象就是类对象本身,[obj class]是类方法,返回的也是本身,所以还是0x10ae0e178,以后无论怎么调用都是执行的类方法,返回的都是本身,所以,用class方法是得不到isa指向链的。
    2.从打印结果我们能看到,类也是对象,meta类也是对象,都占有一块内存,而且我们会发现类对象、meta类对象、root meta类对象的指针都是用9位16进制数表示,而实例对象是用12位16进制数表示(这里用的是64位模拟器),为什么这些类对象的指针位数少?。

    但是如果这个类是Apple自建类就有问题了?

    NSTimer *timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(test) userInfo:nil repeats:YES];
    NSLog(@"instance       :%p", timer1);
    NSLog(@"class          :%p", object_getClass(timer1));
    NSLog(@"meta class     :%p", object_getClass(object_getClass(timer1)));
    NSLog(@"[NSTimer class]:%p", [NSTimer class]);
    

    Log输出:

    TimerDemo[1745:255746] instance       :0x7fee8bc7a810
    TimerDemo[1745:255746] class          :0x10ece02c0
    TimerDemo[1745:255746] meta class     :0x10ece02e8
    TimerDemo[1745:255746] [NSTimer class]:0x10ecdfe38
    

    对于NSTimer这个类,[NSTimer class]返回应该是类对象,object_getClass(timer1)返回的也应该是类对象,为什么指针不同?
    答案非常简单,两个字:类簇,我们熟悉的类簇是NSNumber,NSArray,NSDictionary、NSString...这说明大多数的OC类都是类簇实现的(连NSTimer也不放过),也说明为什么我们Person类是正常的,因为它不是类簇实现的。

    这里还是简单的说说类簇的概念吧:一个父类有好多子类,父类在返回自身对象的时候,向外界隐藏各种细节,根据不同的需要返回的其实是不同的子类对象,这其实就是抽象类工厂的实现思路,iOS最典型的就是NSNumber。

    NSNumber *intNum = [NSNumber numberWithInt:1];
    NSNumber *boolNum = [NSNumber numberWithBool:YES];
    NSLog(@"intNum :%@", [intNum class]);
    NSLog(@"boolNum:%@", [boolNum class]);
    
    ---------------------------------------------
    
    TimerDemo[1018:35735] intNum :__NSCFNumber
    TimerDemo[1018:35735] boolNum:__NSCFBoolean
    
    

    有人可能要问,如何证明__NSCFTimer就是NSTimer的子类,如何证明类簇是真的?其实很简单:

    NSLog(@"[NSTimer class]    :%p", [NSTimer class]);
    NSLog(@"class_getSuperClass:%p", class_getSuperclass([timer1 class]));
    ----------------------------------------------------
    TimerDemo[1038:39690] [NSTimer class]    :0x109ee4e38
    TimerDemo[1038:39690] class_getSuperClass:0x109ee4e38
    

    http://www.jianshu.com/p/54c190542aa8

    相关文章

      网友评论

        本文标题:isa指针

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