在上一篇文章中我们浅谈了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和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包含的是属性列表、成员变量列表、实例方法列表、协议列表等。
而类方法是存储在元类对象中的。
目前、一个对象在内存中的布局是不是已经很明白了。
下面这张图是不是也明白了?
isa的指向:实例对象的isa->类对象、类对象的isa->元类对象、元类对象的isa->基类的元类对象
如果您看明白了,那不妨尝试一下这道面试题吧。
定义一个Person的类继承自NSObject
@interface Person : NSObject
+ (void)test;
@end
@implementation Person
@end
在创建一下NSObject的分类
#import <Foundation/Foundation.h>
@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】