浅谈Objective-C对象二(深入理解isa指针)

在上一篇文章中我们浅谈了Objective-C对象在内存中的基本布局,在文章中的末尾部分我留下了两个疑问,什么是isa?oc中的实例对象方法,类方法,以及协议,属性的名称的都分别存储在哪里?
在开始之前,首先要明白一个概念那就是对象,对象分为以下三种:

  • 实例对象(instance)
  • 类对象(class)
  • 元类对象(meta-class)

instance

实例对象就是通过某个类,调用该类的类方法alloc出来的,每次调用alloc方法都会生成一个全新的instance对象。当然使用alloc+init和使用new是一样的。

@interface Person : NSObject
{
    @public
    int _age;
    int _weight;
}
@end
@implementation Person @end
#define TLog(arg1,arg2) NSLog(@"\n{\narg1:%p\narg2:%p\n}",arg1,arg2)
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *instance1 = [[Person alloc] init];
        instance1->_age = 12;
        Person *instance2 = [[Person alloc] init];
        instance2->_age = 15;
        TLog(instance1,instance2);
    }
    return 0;
}

内存布局

instance1.png

instance2.png

通过两张图片可以清晰的看到instance1和instance2的内存地址是不同的,并且各自都包含一个isa和两个成员变量的,此处、细心的同学一定发现了两个对象的isa是一毛毛样。OK,接下来就引入了类对象的概念。

class

其实、实例对象的isa指针,指向的是它的类对象,类对象在内存中的布局:

  • isa指针
  • superclass指针
  • 类的属性列表
  • 成员变量
  • 实例方法列表
  • 协议列表

证明如下:

Class class1 = instance1.class;
Class class2 = instance2.class;
TLog(class1, class2);
// 通过ruantime获取
Class rtClass1 = object_getClass(instance1);
Class rtClass2 = object_getClass(instance2);
TLog(rtClass1, rtClass2);
log:
{
  arg1:0x1000011a0
  arg2:0x1000011a0
}

通过获取到的类对象,打印其内存地址是0x1000011a0,意外的发现这和上图中isa的值(0x001D8001000011A1)并不相等啊。这其实是因为苹果在isa的值上又做了一次位运算(即0x001D8001000011A1 & ISA_MASK)。可通过查看objc4部分源码查看到。
在源码中可以看到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();
    }
  ...
 }
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; //协议列表
    ...
}

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize; 
#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;
    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

objc_class结构体中,class_rw_t这个结构体包含了类对象的基本信息。
搞明白了,实例对象的isa&ISA_MASK指向了自己的类对象,那么类对象的isa指向了谁呢?

meta-class

你猜测的没错,类对象的isa&ISA_MASK就是指向了meta-class。可通过一下代码来证明:

// 可以通过这两种方式获取元类对象
Class metaClass1 = object_getClass([Person class]);
Class metaClass2 = object_getClass(rtClass2);
TLog(metaClass1, metaClass2);
{
arg1:0x100001178
arg2:0x100001178
}

// 在控制台打印如下指令
p/x 0x001D800100001179 & 0x00007ffffffffff8
// 结果是: 0x0000000100001178

元类对象和类对象都是Class类型,所以内存布局也是一样的,但是存储的内容是不一样的。
下面我们证明如此,我们从objc4中抽离出需要的代码来做证明

# 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 {
    struct 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 {
    struct chained_property_list *next;
    uint32_t count;
    struct 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 ts_objc_object {
    void *isa;
};

/* 类对象 */
struct ts_objc_class : ts_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    ts_objc_class* metaClass() {
        return (ts_objc_class *)((long long)isa & ISA_MASK);
    }
};
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      // 实例对象
        ts_objc_object *instance = (__bridge ts_objc_object *)[[Person alloc] init];
        // 类对象
        ts_objc_class *objectClass = (__bridge ts_objc_class *)[Person class];
        class_rw_t *object_rw_data = objectClass->data();
        // 元类对象
        ts_objc_class *metaObjClass = objectClass->metaClass();
        class_rw_t *meta_rw_data = metaObjClass->data(); 
    }
    return 0;
}

通过设置断点,可以清楚的看到,object_rw_data包含的是属性列表、成员变量列表、实例方法列表、协议列表等。
而类方法是存储在元类对象中的。
目前、一个对象在内存中的布局是不是已经很明白了。
下面这张图是不是也明白了?


Snip20180319_18.png

isa的指向:实例对象的isa->类对象、类对象的isa->元类对象、元类对象的isa->基类的元类对象
如果您看明白了,那不妨尝试一下这道面试题吧。
定义一个Person的类继承自NSObject

@interface Person : NSObject
+ (void)test;
@end
@implementation Person
@end

在创建一下NSObject的分类

#import 
@interface NSObject (cc)
+ (void)test;
@end
#import "NSObject+cc.h"
@implementation NSObject (cc)
- (void)test{
    NSLog(@"[%@ cc]",self);
}
@end

猜猜下面打印的是什么

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [Person test];
        [NSObject test];
    }
    return 0;
}

答案是:
[Person test]
1.给Person这个类对象发送类方法、首先会通过Person类对象的isa指针找到Person的元类对象(存储着类方法)
2.Person元类对象并没有实现+ (void)test这个函数,然后会根据superClass指针从父类中找,即NSObject的元类对象。
3.但是NSObject的元类对象中也没有+ (void)test这个函数,所以继续根据其superClass指针,从NSObject的类对象找到了- (void)test函数,直接执行。
所以,第一个打印【Person cc】
[NSObject test]
这个相对就比较直接了
1.NSObject元类对象中没有+ (void)test这个函数,根据superClass指针从NSObject类对象中找到了- (void)test函数。
所以,第二个打印【NSObject cc】

你可能感兴趣的:(浅谈Objective-C对象二(深入理解isa指针))