iOS ISA指针

https://opensource.apple.com/tarballs/objc4/

图解

WechatIMG2174.png

对象的isa指针指向哪里?

实例对象的isa指针指向类对象,类对象isa指针指向元类对象;如果有继承关系,所有子类以及基类的元类对象的isa指针都指向基类的元类对象。

假设//Person类里有对象方法和类方法
@implementation Person
- (void)personMethod {}
+ (void)personClassMethod {}
@end

------开始讨论类对象的isa指向调用-----

//Person *p1 = [[Person alloc] init];
//[p1 personMethod];

instance的isa指向class
当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用

//[Person personClassMethod];
class的isa指向meta-class

当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用

对象的superclass指针指向哪里?

如果有继承关系,子类类对象的superclass指向父类类对象,父类类对象的superclass指向基类类对象,基类类对象的superclass指向nil;子类元类对象的superclass指向父类元类对象,父类元类对象的superclass指向基类元类对象,基类元类对象的superclass指向基类类对象(特殊点)。

------开始讨论类对象的superclass指向调用-----

比如继承关系如下
/* Person */
@interface Person : NSObject 
{
@public
    int _age;
}
@property (nonatomic, assign) int height;
- (void)personMethod;
+ (void)personClassMethod;
@end

@implementation Person
- (void)personMethod {}
+ (void)personClassMethod {}
@end

/* Student */
@interface Student : Person 
{
@public
    int _no;
}
@property (nonatomic, assign) int score;
- (void)studentMethod;
+ (void)studentClassMethod;
@end

@implementation Student
- (void)studentMethod {}
+ (void)studentClassMethod {}
@end

上述是继承关系,如果我的调用代码如下,调用执行逻辑见备注

Student *stu = [[Student alloc] init];
[stu studentMethod]; //stu实例对象的isa指针会指向Student类对象,从而调用Student对象方法。
[stu personMethod];  //stu实例对象的isa指针会指向Student类对象,Student类对象的superclass指针指向Person类对象,从而调用Person对象方法。
[stu init];  //stu实例对象的isa指针会指向Student类对象,Student类对象的superclass指针指向Person类对象,Person类对象的superclass指针指向NSObject类对象,从而调用NSObject对象方法。(因为init这个方法存在NSobject里。)

------结束论证类对象的superclass-----

------开始论证元类对象的superclass-----

比如继承关系如下
/* Person */
@interface Person : NSObject 
{
@public
    int _age;
}
@property (nonatomic, assign) int height;
- (void)personMethod;
+ (void)personClassMethod;
@end

@implementation Person
- (void)personMethod {}
+ (void)personClassMethod {}
@end

/* Student */
@interface Student : Person 
{
@public
    int _no;
}
@property (nonatomic, assign) int score;
- (void)studentMethod;
+ (void)studentClassMethod;
@end

@implementation Student
- (void)studentMethod {}
+ (void)studentClassMethod {}
@end

上述是继承关系,如果我的调用代码如下,调用执行逻辑见备注

Student *stu = [[Student alloc] init];
[Student studentClassMethod]; //Student类对象的isa指针会指向Student元类对象,从而调用Student元类类方法。
[Student personClassMethod];  //Student类对象的isa指针会指向Student元类对象,Student元类对象的superclass指针指向Person元类对象,从而调用Person元类类方法。
[Student load];  //Student类对象的isa指针会指向Student元类对象,Student元类对象的superclass指针指向Person元类对象,Person元类对象的superclass指针指向NSObject元类对象,从而调用NSObject元类类方法。

------结束论证元类对象的superclass-----

如何事实论证isa指针指向问题呢

----先论证实例对象ISA指向类对象-----

//先创建实例对象
NSObject *object = [[NSObject alloc] init];
//创建类对象
Class objectClass = [NSObject class];
//然后断点运行,通过日志栏左侧可以看到object里有isa指针,在打印栏输入以下代码,可以打印出object的指针值,存的也就是objectClass的地址,然而实际输入打印objectClass地址时发现并不太相同,是因为从64bit开始,isa需要进行一次位运算,才能计算出真实地址  p/x是按16位输出
p/x (long)object->isa

位运算规则如下 (源码可以查看,源码库里搜索objc_object { ,点击objc-private.h文件,点击isa_t,可以找到ISA_MASK定义)
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL    //真机64位处理器需要arm64架构
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL    //模拟器64位处理器测试需要x86_64架构
# endif

//通过isa值和ISA_MASK位运算后可以看到值和objectClass地址相同
p/x (long)object->isa & 0x00007ffffffffff8
上面进行地址的&运算后,可以看到object的isa指向地址和objectClass的地址相同,所以证明了实例对象的isa指向了类对象

----现在论证类对象isa指向元类对象-----

//因为类的isa不像实例对象的isa直接暴露出来。我们在点击Class objectClass = [NSObject class];这句代码的Class可以看到被定义为这句话typedef struct objc_class *Class;这是一个结构体。再点击objc_class看到确实是一个isa指针的结构体 如右struct objc_class {
//                                 Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
//                              }

/* 我们看到了类对象里的结构,但是因为类的isa不像实例对象的isa直接暴露出来,所以我们为了让isa“暴露”出来,我们定义一个跟系统Class结构一样的结构体,然后通过桥接,将objectClass转成我们自己的结构体,于是乎可以打印出类对象的isa指针。 */
//我们自定一个跟系统同样功能的objc_class结构体
struct mj_objc_object {
    Class isa;
};
//创建类对象,所以下面这句话可以强转
Class objectClass = [NSObject class];
//创建元类对象
Class mateClass = object_getClass(obClass1);
//桥接(__bridge),然后强转(struct mj_objc_class *),通过这样强转后,就可以看到左侧日志栏,已经出现的objectClass2的isa。
struct mj_objc_class *objectClass2 = (__ bridge struct mj_objc_class *)objectClass;
//通过isa值和ISA_MASK位运算后可以看到值和mateClass地址相同
p/x (long)objectClass2->isa & 0x00007ffffffffff8
上面进行地址的&运算后,可以看到objectClass2的isa指向地址和mateClass的地址相同,所以证明了类对象的isa指向了元类对象

----论证类对象内部结构以及superclass -----

在xcode里直接点击Class可以查看结构是typedef struct objc_class *Class结构体,然后点击objc_class这个结构体,定义如下

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;

但是,这个OBJC2_UNAVAILABLE代表已经在oc2.0(2006 年,苹果发布了全新的 Objective-C 2.0)被舍弃了,那么objc_class最新的长什么样子呢?

打开源码,搜索objc_class然后在objc-runtime-new.h里可以看到objc_class定义如下

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() {
        return bits.data();
    }
    
    //还有很多代码,为了便于查看,删除了
}

可以看到最新的objc_class里居然没有isa,其实是有的,因为继承于objc_object,点击objc_object查看,可以看到这个结构体定义为

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    
    //还有很多代码,为了便于查看,删除了
}

objc_object里有isa,objc_class继承于objc_object,所以也是有isa的,只不过不会显式看到。

一步步查看源码,可以合并为下面的层次结构

    struct objc_class  {
        Class ISA;
        Class superclass;
        cache_t cache;             // 方法缓存
        class_data_bits_t bits;    // 用于获取类的具体信息

        class_rw_t *data() {
            return bits.data();
        }
        
        //还有很多代码,为了便于查看,删除了
    }
而bits里有(点击class_data_bits_t)
    struct class_data_bits_t {
        class_rw_t* data() {
            return (class_rw_t *)(bits & FAST_DATA_MASK);
        }
        //还有很多代码,为了便于查看,删除了
    }
而data()里有(点击class_rw_t)
    struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;  //方法列表
    property_array_t properties;  //属性列表
    protocol_array_t protocols;   //协议列表

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
        
    }
而class_ro_t里
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;  //instance对象占用的内存空间(不用质疑,就是alloc生成的那个实例对象所占的内存大小)
    #ifdef __LP64__
        uint32_t reserved;
    #endif

        const uint8_t * ivarLayout;
        
        const char * name;   //类名
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;  //成员变量列表

        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    }

下面用xcode来验证上面结构

新建一个.h(MJClassInfo.h)文件,定义如下(模仿系统的结构体)
#import 

#ifndef MJClassInfo_h
#define MJClassInfo_h

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

struct cache_t {
    bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct method_list_t : entsize_list_tt {
    method_t first;
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};

struct property_t {
    const char *name;
    const char *attributes;
};

struct property_list_t : entsize_list_tt {
    property_t first;
};

struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance对象占用的内存空间
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;  // 类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;  // 成员变量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表  (注意,这里为了便于查看,转成了一维数组,源码里定义的是个二维数组哦)
    property_list_t *properties;    // 属性列表   (注意,这里为了便于查看,转成了一维数组,源码里定义的是个二维数组哦)
    const protocol_list_t * protocols;  // 协议列表   (注意,这里为了便于查看,转成了一维数组,源码里定义的是个二维数组哦)
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/* OC对象 */
struct mj_objc_object {
    void *isa;
};

/* 类对象 */
struct mj_objc_class : mj_objc_object { 
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    mj_objc_class* metaClass() {  //针对性新增,方便获取元类。原理就是拿到类对象isa然后位运算就能拿到元类对象的地址
        return (mj_objc_class *)((long long)isa & ISA_MASK);
    }
};

#endif /* MJClassInfo_h */

    
然后调用,因为MJClassInfo.h有一些C++语法,所以为了调用,需要将main.m的文件改为main.mm,然后这个文件就兼容了C++语法, 此时书写结构体时就不用再加struct了。
NSObject *object = [[NSObject alloc] init];
Person *person = [[Person alloc] init];
Student *student = [[Student alloc] init];

mj_objc_class *objectClass = (__bridge mj_objc_class *)[object class];
mj_objc_class *personClass = (__bridge mj_objc_class *)[person class];
mj_objc_class *studentClass = (__bridge mj_objc_class *)[student class];

//获取元类。因为在上面代码里,新增了一个方法直接做了isa & ISA_MASK操作。
mj_objc_class *objectMetaClass = objectClass->metaClass();
mj_objc_class *personMetaClass = personClass->metaClass();
mj_objc_class *studentMetaClass = studentClass->metaClass();

class_rw_t *objectClassData = objectClass->data();
class_rw_t *personClassData = personClass->data();
class_rw_t *studentClassData = studentClass->data();

class_rw_t *objectMetaClassData = objectMetaClass->data();
class_rw_t *personMetaClassData = personMetaClass->data();
class_rw_t *studentMetaClassData = studentMetaClass->data();
// 0x00007ffffffffff8
NSLog(@"%p %p %p %p %p %p",  objectClassData, personClassData, studentClassData,
      objectMetaClassData, personMetaClassData, studentMetaClassData);
然后打断点,可以看到左边打印框里的信息或者看输出来看里面存的数据

由此,通过源码以及代码都验证了类对象内部结构
论证superclass,可以通过上述断点查看左侧结构里的superclass,通过打印superclass地址以及父类地址可以验证superclass指向父类,以及元类的superclass指向问题。

有一道题是,子类继承NSObject,NSObject写了一个分类,子类和NSObject分类都在.h里声明了test类方法,但是NSObject分类里面实现的是test对象方法,子类里面不实现任何方法。结果通过子类调用test类方法时,打印的是NSObject分类里面实现的test对象方法。由此论证了基类元类对象的superclass指向基类类对象。
原因是调用类方法时底层是objc_msgSend([NSObject class],@selector(test)) ,其中objc_msgSend调用的时候是不关心调用的是类方法还是对象方法,只会根据对象去找,元类对象找不到,根据基类元类对象的superclass指向基类类对象,所以成了调用类对象的对象方法了

你可能感兴趣的:(iOS ISA指针)