oc runtime源码 版本 objc4-723
先来一段大家都知道的知识
NSObject类在程序运行会有多个实例对象,一个类对象,一个元类对象。
其中所有实例对象中的isa指针指向类对象,类对象中的isa指针指向元类对象。NSObject 的元类对象指向自己,其他元类对象指向 NSObject 的元类对象
OC 对象都是 C 语言的结构体,所有的对象都包含一个类型为 isa_t 的 isa 指针.
上面是结论,咱证明一下,新建Xcode 工程后 修改 mai.m 文件
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
@interface Person : NSObject{
NSString * _name;
int age;
}
@end
@implementation Person
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
NSObject * obj = [Person new];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
创建了一个新类Person,父类为 NSObject,然后在命令行执行如下命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main-arm64.cpp
然后就会在生成的 main-arm64.cpp 文件中找到如下结构体
struct NSObject_IMPL {
__unsafe_unretained Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *__strong _name;
int age;
};
猜测 Person_IMPL 应该是 Persion Implementation 的简写,Implementation 在计算机语义中是实现的意思,Person_IMPL 就是 Persion 底层实现的意思。
从这可以证明 OC 对象都是 C 语言的结构体
。
在 OC 中所有的类都是继承自NSObject,来看一下 NSObject 的结构
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
typedef struct objc_class *Class;
objc_class 结构体的源码 ,下面的代码就是从 oc runtime 源码搜到的
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 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();
...
}
从上面代码可以看到,NSObject 类有一个类型为 Class 的成员变量isa , Class 是结构体 objc_class 的别名,结构体 objc_class 继承自 结构体objc_object, 结构体 objc_object 中第一个成员变量就是 isa_t 类型的 isa,还是私有变量。
到这里就证明了 所有的对象都包含一个类型为 isa_t 的 isa 指针
这句话。
下面来看一下 isa_t 到底是个啥
typedef unsigned long uintptr_t;
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)
};
};
上面的代码我去掉了 __x86_64__
iMac、 __ARM_ARCH_7K__
iWatch 相关代码,只保留了与__arm64__
iPhone 有关的代码。
union结构是一种特殊的类,union 里的成员内存是共享的,以size最大的结构作为自己的大小。
union可以用来测试CPU是大端模式还是小端模式
void checkCPU()
{
union MyUnion{
int i;
char c;
}test;
test.i = 1;
if (test.c == 1)
printf("little endian \n");
else
printf("big endian \n");
}
int main(int argc, char * argv[]) {
checkCPU();
}
union(联合体) isa_t 中有构造函数isa_t(uintptr_t value)和 isa_t(), 成员变量 Class 类型的 cls,unsigned long 类型的 bits以及一个结构体,cls、bits、结构体 共享一块长8字节64位的内存,结构体使用位域技术存储更多的信息。
isa 指针在 arm64 架构之前就是一普通指针,存储着Class、Meta-Class 对象的内存地址,在 arm64 之后,才变成现在这个样子的。
解释一下 结构体中 变量是啥意思
变量 | 位(bit) | |
---|---|---|
nonpointer | 1 | 是否开启指针优化 |
has_assoc | 1 | 是否设置过关联对象,如果没有释放更快 |
has_cxx_dtor | 1 | 表示对象是否有C++的析构函数(.cxx_destruct),如果没有释放更快 |
shiftcls | 33 | 类指针,存储着 Class、Meta-Class 对象的内存地址 |
magic | 6 | 固定值1a,用于调试时分辨对象是否未完成初始化 |
weakly_referenced | 1 | 是否有被弱引用指向过,如果没有释放更快 |
deallocating | 1 | 对象是否正在释放 |
has_sidetable_rc | 1 | 该对象的引用计数值是否过大而导致无法存储在extra_rc 中 |
extra_rc | 19 | 存储引用计数值减一后的结果 |
isa 初始化
当调用 [NSObject alloc] 创建一个新的实例的时候,会调用到 initInstanceIsa 函数
define ISA_MAGIC_VALUE 0x000001a000000001ULL
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
if (!nonpointer) {
isa.cls = cls;
} else {
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
}
}
hasCxxDtor 值是从 类对象的isa中取出的。上面的代码可以精简为
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
bits
使用 ISA_MAGIC_VALUE 设置了 isa_t 联合体的 nonpointer 和 magic,nonpointer为1,magic为1a ,其他变量为零。
nonpointer为1 代表 isa_t 开启了指针优化,isa不再是一个内存指针,里面包含了类信息、对象的引用计数信息等。
nonpointer为0 代表 没有开启指针优化,当访问isa时,直接返回 cls 变量
has_cxx_dtor
hasCxxDtor 值是从 类对象的isa中来,那 类对象的 hasCxxDtor 值是从哪里来的呢.
// class has .cxx_construct/destruct implementations
#define RO_HAS_CXX_STRUCTORS (1<<2)
static Class realizeClass(Class cls){
const class_ro_t *ro;
...
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
}
...
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
...
}
realizeClass 函数会给类创建 class_rw_t * 数据, class_rw_t 结构里面包含了类的函数数组,属性数组,协议数组等,这些数组是可读写的,数组里也包含分类里的内容.
class_ro_t *ro 是只读的,里面存储的函数、属性等是不包含分类里的.
类对象的 hasCxxDtor 值来自 ro 的 flags,
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;
}
};
下面来看一下获取类的方法
// Look for a __DATA or __DATA_CONST or __DATA_DIRTY section
// with the given name that stores an array of T.
template <typename T>
T* getDataSection(const headerType *mhdr, const char *sectname,
size_t *outBytes, size_t *outCount)
{
unsigned long byteCount = 0;
T* data = (T*)getsectiondata(mhdr, "__DATA", sectname, &byteCount);
if (!data) {
data = (T*)getsectiondata(mhdr, "__DATA_CONST", sectname, &byteCount);
}
if (!data) {
data = (T*)getsectiondata(mhdr, "__DATA_DIRTY", sectname, &byteCount);
}
if (outBytes) *outBytes = byteCount;
if (outCount) *outCount = byteCount / sizeof(T);
return data;
}
#define GETSECT(name, type, sectname) \
type *name(const headerType *mhdr, size_t *outCount) { \
return getDataSection<type>(mhdr, sectname, nil, outCount); \
} \
type *name(const header_info *hi, size_t *outCount) { \
return getDataSection<type>(hi->mhdr(), sectname, nil, outCount); \
}
GETSECT(_getObjc2ClassList, classref_t, "__objc_classlist"); //获取当前前注册的所有类
GETSECT(_getObjc2NonlazyClassList, classref_t, "__objc_nlclslist");//获取所有非懒加载的类(实现了+load)
_getObjc2ClassList 获取所有类, 通过 getDataSection 函数看,Class cls 信息是从 Mach-O 中的 __DATA 数据段获取的,也就是说 ro 中的 flags 应该是编译期写在Mach-O文件中的.
shiftcls
shiftcls存储的是类指针, 将位移(shift)后的 cls 存入 shiftcls(类指针按照 8 字节对齐,所以最后三位一定是 0,所以可以右移三位来节省位的使用)。
has_assoc
has_assoc是否设置过关联对象,如果没有释放更快
runtime提供給我们关联对象的方法
//关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//获取关联的对象
id objc_getAssociatedObject(id object, const void *key)
//移除关联的对象
void objc_removeAssociatedObjects(id object)
变量说明:
id object:被关联的对象(如xiaoming)
const void *key:关联的key,要求唯一
id value:关联的对象(如dog)
objc_AssociationPolicy policy:内存管理的策略
objc_AssociationPolicy policy的enum值有:
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
现在看一下关联对象函数
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
...
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
...
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
...
}
}
...
}
inline void
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
newisa.has_assoc = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
看到了吧, 如果有没有在 associations 找到对象,那就调用对象object 的 setHasAssociatedObjects() 函数设置has_assoc 变量为 true.
“如果没有释放更快” 这是因为在释放对象的时候,如果没有关联过,会少执行很多代码
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
看上面的代码,如果对象没有弱引用,没有关联对象,没有c++析构函数,引用计数没有曾经溢出过 ,那就直接释放对象,如果这些标记为 true,那还要执行下面的代码
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
weakly_referenced
是否有被弱引用指向过,如果没有释放更快
先接着看释放更快这一点,接着上面的代码clearDeallocating()
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
}
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
weak_clear_no_lock 函数就是对象被 weak 引用过要执行的函数,将所有引用设置为 nil.
slowpath 和 fastpath 宏的定义如下
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
__builtin_expect起的是优化性能的作用,与 cpu 读取指令有关
__builtin_expect((x),1) 表示 x 的值为真的可能性更大;
__builtin_expect((x),0) 表示 x 的值为假的可能性更大。
再说下 weakly_referenced 是在哪里设置的,当某个对象的 weak 属性调用 setter 时,会调用 objc_storeWeak(id *location, id newObj) 函数,然后调用 setWeaklyReferenced_nolock()函数
id objc_storeWeak(id *location, id newObj)
{
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object *)newObj);
}
static id storeWeak(id *location, objc_object *newObj){
...
// Assign new value, if any.
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
...
}
inline void
objc_object::setWeaklyReferenced_nolock()
{
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
sidetable_setWeaklyReferenced_nolock();
return;
}
if (newisa.weakly_referenced) {
ClearExclusive(&isa.bits);
return;
}
newisa.weakly_referenced = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
deallocating
对象是否正在释放,该变量就是对象释放的一个标记,防止释放内存的时候其他地方强引用.
是在rootRelease函数设置的
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow){
...
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
...
}
如果是重复释放,就会崩溃.
has_sidetable_rc
在 64 位环境下,优化的 isa 指针并不是就一定会存储引用计数,毕竟用 19bit (iOS 系统)保存引用计数不一定够。需要注意的是这 19 位保存的是引用计数的值减一。has_sidetable_rc 的值如果为 1,那么引用计数会存储在一个叫 SideTable 的类的属性中
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
rootRetainCount 方法对引用计数存储逻辑进行了判断,如果是 TaggedPointer ,就直接返回本身.TaggedPointer在最后会简单说.
如果has_sidetable_rc 为0 ,就直接返回 extra_rc+1 .
如果has_sidetable_rc 为1 ,就从SideTables 获取当前实例对象的 SideTable 对象,refcnts 属性就是存储引用计数的散列表,以自身为 key 找到 value ,然后右移2位返回.
为啥右移呢,通过 宏 看到,第一个 bit 表示该对象是否有过 weak 对象,如果没有,在析构释放内存时可以更快;第二个 bit 表示该对象是否正在析构。从第三个 bit 开始才是存储引用计数数值的地方.
// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))
#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)
size_t
objc_object::sidetable_getExtraRC_nolock()
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) return 0;
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
最后说一下
Tagged Pointer
苹果不允许访问 isa 指针,与 Tagged Pointer 有关系.因为 Tagged Pointer 的情况下,isa 不在是指向一块内存,而是直接表示对象的值,64位其中高4位和低4位是保留位,最高位63位,是标记位,第60、61、62位是类型位.
从源码可以看到,_OBJC_TAG_MASK 就是标记位.
#if TARGET_OS_OSX && __x86_64__
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
inline bool
objc_object::isTaggedPointer()
{
return _objc_isTaggedPointer(this);
}
举个例子
NSString *a = @"a";
NSString *b = [[a mutableCopy] copy];
NSLog(@"%p %p %@", a, b, object_getClass(b));
//0x10a2822a8 0xa000000000000611 NSTaggedPointerString
通过lldb 打印
(lldb) p @1
(__NSCFNumber *) $3 = 0xb000000000000012 (int)1
(lldb) p @2
(__NSCFNumber *) $4 = 0xb000000000000022 (int)2
(lldb) p @0xfffffffffffff
(__NSCFNumber *) $1 = 0xb0fffffffffffff3
Type | bit(60~62) | int |
---|---|---|
NSString | 010 | 2 |
NSNumber | 011 | 3 |
NSIndexPath | 100 | 4 |
NSManagedObjectID | 101 | 5 |
NSDate | 110 | 6 |