objc_object
Objective-C 所有对象都是 C 语言结构体objc_object
,这个结构体中包含一个isa
成员变量,不是一个普通的指针,是一个isa_t
结构体。
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();
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
}
isa_t
因为iPhone5s之后的设备是在arm64架构下,所以截取arm64架构下的代码,这个结构体是苹果经过优化的
armv64: iPhoneX, iPhone 5s-8, iPad Air — iPad Pro
armv7 : iPhone3Gs-5c, iPad WIFI(4th gen)
armv6 : iPhone — iPhone3G
the above if for real devices
i386 : 32-bit simulator
x86_64 : 64-bit simulator
i386是针对intel通用微处理器32位处理器
x86_64是针对x86架构的64位处理器
模拟器32位处理器测试需要i386架构
模拟器64位处理器测试需要x86_64架构
真机32位处理器需要armv7,或者armv7s架构
真机64位处理器需要arm64架构
为何要进行优化呢,进行了怎样的优化,看看Friday Q&A 2013-09-27: ARM64 and You怎么说的
尽管指针为64位,但在实际使用中,这些位数并不是都用上了。例如X86-64的Mac OS X系统仅使用了其中的47位。而ARM64上占用得更少,目前只有33位。只要未被系统全部占用,这些指针就能用于存储数据。这是Objective-C Runtime演进史上最重要的变化之一。
Objective-C对象是连续的内存块,这个内存块中第一个指针大小的部分称为ISA。一般来说,ISA是一个指向该对象所属类的指针。不过这么大的空间仅作为指针有点儿浪费,尤其是在64位CPU上。运行iOS的ARM64目前仅使用了一个指针的33位,而其余31位则另作他用。另外,类 指针还需要对齐,这就释放了另外3位,于是ISA指针中共有34位可另作他用。苹果的ARM64 Runtime正是利用了这一点使性能有了大幅提升。
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
}
Hamster Emporium: [objc explain]: Non-pointer isa - Sealie Software一文对这些bits做了如下解释
bits | 作用 |
---|---|
nonpointer | 0表示普通isa,1表示non-pointe isa |
has_assoc | 对象具有或曾经具有关联的引用。没有关联引用的对象可以更快地析构。 |
has_cxx_dtor | 对象有一个C ++或ARC析构函数。 没有析构函数的对象可以更快地解除分配。 |
shiftcls | 类指针的非零位 |
magic | 等于0xd2,调试器使用它来判断对象是否完成了初始化 |
weakly_referenced | 对象有或者曾经有过ARC的weak对象,如果没有,析构更快 |
deallocating | 对象正在析构 |
has_sidetable_rc | 对象的应用计数太大,无法内敛存储 |
extra_rc | 这个数值加1为对象的引用计数.(例如,如果extra_rc为5,则对象的实际引用计数为6.) |
initIsa
在objc_object
的代码中我们可以看到有好几初始化方法,最后都会走到下面这个方法,我只是截取了部分我们需要的
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
}
}
有一个地方不好理解
newisa.shiftcls = (uintptr_t)cls >> 3
这里我们可以看到是将类地址右移三位得到有效的33位shiftcls
位,Friday Q&A 2013-09-27: ARM64 and You和从 NSObject 的初始化了解 isa 解释:
使用整个指针大小的内存来存储 isa 指针有些浪费,尤其在 64 位的 CPU 上。在 ARM64 运行的 iOS 只使用了 33 位作为指针(与结构体中的 33 位无关,Mac OS 上为 47 位),而剩下的 31 位用于其它目的。类的指针也同样根据字节对齐了,每一个类指针的地址都能够被 8 整除,也就是使最后 3 bits 为 0,为 isa 留下 34 位用于性能的优化。
绝大多数机器的架构都是 byte-addressable 的,但是对象的内存地址必须对齐到字节的倍数,这样可以提高代码运行的性能,在 iPhone5s 中虚拟地址为 33 位,所以用于对齐的最后三位比特为 000,我们只会用其中的 30 位来表示对象的地址
objc_object::ISA()
objc-object.h文件中
inline Class
objc_object::ISA()
{
return (Class)(isa.bits & ISA_MASK);
}
define ISA_MASK 0x0000000ffffffff8ULL
ffffffff8 = 1111 1111 1111 1111 1111 1111 1111 1111 1000
共36位,isa.bits和&
运算后就拿到33位shiftcls
,也就是类指针。
objc_class
Objective-C 中类都是 C 语言的类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
继承自objc_object
,说明在Objective-C 中类也是一个对象。我们先来看看objc_class
的结构:
-
isa:因为
objc_class
继承自objc_object
,所以它包含一个isa
成员变量,指向(MetaClass)元类 - superclass:指向当前类的父类
-
cache:用来缓存指针和
vtable
(virtual table虚函数表)。Runtime运行时系统库实现了一种自定义的虚函数表分派机制。这个表是专门用来提高性能和灵活性的。用来存储IMP类型的数组 - bits:class_rw_t 指针加上 rr/alloc 标志,用来存储类的属性,方法,协议等信息
class_rw_t
class_data_bits_t
的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;
}
- methods:方法列表
- properties:属性列表
- protocols:协议列表
class_ro_t
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;
}
};
- instanceSize:instance对象占用的内存空间
- name:类名
- baseMethodList:类的原始方法列表
- baseProtocols:类的原始属性列表
- ivars:成员变量列表
我们看到class_rw_t
和class_ro_t
都有方法列表等,深入解析 ObjC 中方法的结构对此作出的解释:
在分析方法在内存中的位置时,笔者最开始一直在尝试寻找只读结构体 class_ro_t 中的 baseMethods 第一次设置的位置(了解类的方法是如何被加载的)。尝试从 methodizeClass 方法一直向上找,直到 _obj_init 方法也没有找到设置只读区域的 baseMethods 的方法。
而且在 runtime 初始化之后,realizeClass 之前,从 class_data_bits_t 结构体中获取的 class_rw_t 一直都是错误的,这个问题在最开始非常让我困惑,直到后来在 realizeClass 中发现原来在这时并不是 class_rw_t 结构体,而是class_ro_t,才明白错误的原因。
后来突然想到类的一些方法、属性和协议实在编译期决定的(baseMethods 等成员以及类在内存中的位置都是编译期决定的),才感觉到豁然开朗。
类在内存中的位置是在编译期间决定的,在之后修改代码,也不会改变内存中的位置。
类的方法、属性以及协议在编译期间存放到了“错误”的位置,直到 realizeClass 执行之后,才放到了 class_rw_t 指向的只读区域 class_ro_t,这样我们即可以在运行时为 class_rw_t 添加方法,也不会影响类的只读结构。
在 class_ro_t 中的属性在运行期间就不能改变了,再添加方法时,会修改 class_rw_t 中的 methods 列表,而不是 class_ro_t 中的 baseMethods,对于方法的添加会在之后的文章中分析。
class and metaclass
在前面的源码中可以看到,对象通过isa
找到类,同时类也是一种对象,类隐式的拥有isa
,那这个isa
指向哪里呢,元类。
static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)
{
runtimeLock.assertWriting();
class_ro_t *cls_ro_w, *meta_ro_w;
cls->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
meta->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
cls_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
meta_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
cls->data()->ro = cls_ro_w;
meta->data()->ro = meta_ro_w;
// Set basic info
cls->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
meta->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
cls->data()->version = 0;
meta->data()->version = 7;
cls_ro_w->flags = 0;
meta_ro_w->flags = RO_META;
if (!superclass) {
cls_ro_w->flags |= RO_ROOT;
meta_ro_w->flags |= RO_ROOT;
}
if (superclass) {
cls_ro_w->instanceStart = superclass->unalignedInstanceSize();
meta_ro_w->instanceStart = superclass->ISA()->unalignedInstanceSize();
cls->setInstanceSize(cls_ro_w->instanceStart);
meta->setInstanceSize(meta_ro_w->instanceStart);
} else {
cls_ro_w->instanceStart = 0;
meta_ro_w->instanceStart = (uint32_t)sizeof(objc_class);
cls->setInstanceSize((uint32_t)sizeof(id)); // just an isa
meta->setInstanceSize(meta_ro_w->instanceStart);
}
cls_ro_w->name = strdupIfMutable(name);
meta_ro_w->name = strdupIfMutable(name);
cls_ro_w->ivarLayout = &UnsetLayout;
cls_ro_w->weakIvarLayout = &UnsetLayout;
meta->chooseClassArrayIndex();
cls->chooseClassArrayIndex();
// Connect to superclasses and metaclasses
cls->initClassIsa(meta);
if (superclass) {
meta->initClassIsa(superclass->ISA()->ISA());
cls->superclass = superclass;
meta->superclass = superclass->ISA();
addSubclass(superclass, cls);
addSubclass(superclass->ISA(), meta);
} else {
meta->initClassIsa(meta);
cls->superclass = Nil;
meta->superclass = cls;
addRootClass(cls);
addSubclass(cls, meta);
}
cls->cache.initializeToEmpty();
meta->cache.initializeToEmpty();
}
上图实线是 superclass 指针,虚线是isa指针
- 对象调用实例方法时,是在对应类对象及其继承链上找方法。类对象调用类方法时,是在其元类及继承链上找方法
- 所以元类的isa指针都指向根元类(NSObject),根元类的isa指针指向自己
- 所有的类方法都储存在元类当中
- NSObject 的超类为 nil
关于元类Classes and metaclasses 和What is a meta-class in Objective-C?解释的很详细
下面是What is a meta-class in Objective-C?的测试代码
Class newClass =
objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
objc_registerClassPair(newClass);
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 = 1; 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]));
}
id instanceOfNewClass =
[[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
[instanceOfNewClass performSelector:@selector(report)];
[instanceOfNewClass release];
打印结果:
This object is 0x10010c810.
Class is RuntimeErrorSubclass, and super is NSError.
Following the isa pointer 1 times gives 0x10010c600
Following the isa pointer 2 times gives 0x10010c630
Following the isa pointer 3 times gives 0x7fff71038480
Following the isa pointer 4 times gives 0x7fff71038480
NSObject's class is 0x7fff710384a8
NSObject's meta class is 0x7fff71038480
观察isa到达过的地址的值:
- 对象的地址是 0x10010c810
- 类的地址是 0x10010c600
- 元类的地址是 0x10010c630
- 根元类(NSObject的元类)的地址是 0x7fff71038480
- NSObject元类的类是它本身
元类是 Class 对象的类。每个类(Class)都有自己独一无二的元类(每个类都有自己第一无二的方法列表)。这意味着所有的类对象都不同。
元类总是会确保类对象和基类的所有实例和类方法。对于从NSObject继承下来的类,这意味着所有的NSObject实例和protocol方法在所有的类(和meta-class)中都可以使用。
所有的meta-class使用基类的meta-class作为自己的基类,对于顶层基类的meta-class也是一样,只是它指向自己而已。
从 NSObject 的初始化了解 isa
Hamster Emporium: [objc explain]: Non-pointer isa - Sealie Software
Testing if an arbitrary pointer is a valid Objective-C object
从 NSObject 的初始化了解 isa
Pro Multithreading and Memory Management for iOS and OS X: with ARC, Grand ...
iOS framework file was built for x86_64 which is not the architecture being linked (arm64), linker command failed with exit code 1
Friday Q&A 2013-09-27: ARM64 and You
深入解析 ObjC 中方法的结构
What is a meta-class in Objective-C?
Classes and metaclasses
用 isa 承载对象的类信息