引言:这篇文章旨在从runtime源码中分析出 引用计数 值本身的保存位置,适合对底层原理有兴趣的朋友,或者面试造火箭的同学(比如百度的面试官非常喜欢问底层原理:好,我知道你说了深浅复制的区别一大堆,如果我让你自己实现一个copy,你能实现吗?如果我让你实现引用计数的功能,你有思路吗?)。因而本文并 不适用于 专注业务层快速开发的同学,因为这里将贴有大量的源码。没有耐心的同学可以先收藏暂时回避一下,日后造火箭造飞机的时候再来。
核心问题iOS开发者都知道OC里面的内存管理是通过对象的引用计数来管理的,或手动MRC,或自动ARC,有些操作可以让引用计数加1,有些可以减1,一旦一个对象的引用计数为0,就回收内存了。
可是,你仅仅知道这里就行了吗?指望你能造火箭造飞机的面试官可不这么想了,比如问你一句,一个对象的 引用计数本身 保存在哪里??不关注底层的面试者,这时候可能会懵逼。
研究方式这篇文章不同于其它文章通过 clang编译 一个类文件以查看它的实现原理(笔者曾用clang编译分析Block的原理,传送门),而是直接通过下载runtime的源码来查看分析。
依据版本苹果开源了runtime的代码,查看的方式既可以通过 在线网页版 预览,也可以 下载归档文件 到本地查看。本篇文件讨论的版本是 objc4-723。
目录
- 1.1 对象 -- Object
- 1.2 对象 -- NSObject
- 1.3 对象 -- objc_object
- 1.4 类 -- objc_class
- 1.5 NSObject,objc_object,objc_class 三者的关系
- 2.1 两种对象:NSObject与Object的引用增加
- 2.2 归根结底 -- NSObject对象的rootRetain()
- 3.1 NSObject的唯一成员变量 -- isa
- 3.2 isa_t联合体里面的数据含义
- 3.3 isa_t联合体里面的宏
- 3.4 是否Tagged Pointer的判断
- 3.5 与isa类型有关的宏
- 3.6 怎么判断是否支持优化的isa指针?-- 看设备、自己设置。
- 3.7 怎么判断是否Tagged Pointer的对象?-- 看对象、自己设置
- 3.8 引用计数的存储形式 -- 散列表
- 4.1 增加引用计数 -- sidetable_retain()
- 4.2 增加引用计数 -- sidetable_tryRetain()
- 4.3 获取散列表 -- SideTable()
- 设置变量导致的引用计数变化 -- objc_retain操作
- 5.1 情况1
- 5.2 情况2
- 5.3 objc_storeStrong导致的retain
- 新建对象(分配内存与初始化)导致的引用计数变化 -- alloc 和 init 操作
- 6.1 分配内存 -- alloc
- 6.2 初始化 -- init
1. 类与对象下载完工程,打开查看
module.modulemap头文件描述文件
module ObjectiveC [system] [extern_c] { umbrella "." export * module * { export * } module NSObject { requires objc header "NSObject.h" export * }#if defined(BUILD_FOR_OSX) module List { // Uses @defs, which does not work in ObjC++ or non-ARC. requires objc, !objc_arc, !cplusplus header "List.h" export * } module Object { requires objc header "Object.h" export * } module Protocol { requires objc header "Protocol.h" export * }#endif#if !defined(BUILD_FOR_OSX) // These file are not available outside macOS. exclude header "hashtable.h" exclude header "hashtable2.h"#endif}复制代码这里的Module本质上是一个描述文件,用来描述Module中包涵的内容,每个Module中必须包涵一个umbrella头文件,这个文件用来#import所有这个Module下的文件,比如#import <UIKit/UIKit.h>这个UIKit.h就是一个umbrella文件。关于Module更多参考 这篇文章。
从#if defined(BUILD_FOR_OSX)这句逻辑判断可知, Object是针对macOS的,iOS开发暂时只关心NSObject即可。
1.1 对象 -- ObjectObject.mmObject
#include "objc-private.h"#undef id#undef Classtypedef struct objc_class *Class;typedef struct objc_object *id;#if __OBJC2____OSX_AVAILABLE(10.0) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE__WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLEOBJC_ROOT_CLASS@interface Object { Class isa; } @end@implementation Object+ (id)initialize{ return self; }+ (id)class{ return self;}-(id) retain{ return _objc_rootRetain(self);}-(void) release{ _objc_rootRelease(self);}-(id) autorelease{ return _objc_rootAutorelease(self);}+(id) retain{ return self;}+(void) release{}+(id) autorelease{ return self;}@end复制代码1.2 对象 -- NSObjectNSObject.hNSObject
#ifndef _OBJC_NSOBJECT_H_#define _OBJC_NSOBJECT_H_#if __OBJC__#include <objc/objc.h>#include <objc/NSObjCRuntime.h>@class NSString, NSMethodSignature, NSInvocation;@protocol NSObject- (BOOL)isEqual:(id)object;@property (readonly) NSUInteger hash;@property (readonly) Class superclass;- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");- (instancetype)self;- (id)performSelector:(SEL)aSelector;- (id)performSelector:(SEL)aSelector withObject:(id)object;- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;- (BOOL)isProxy;- (BOOL)isKindOfClass:(Class)aClass;- (BOOL)isMemberOfClass:(Class)aClass;- (BOOL)conformsToProtocol:(Protocol *)aProtocol;- (BOOL)respondsToSelector:(SEL)aSelector;- (instancetype)retain OBJC_ARC_UNAVAILABLE;- (oneway void)release OBJC_ARC_UNAVAILABLE;- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;@property (readonly, copy) NSString *description;@optional@property (readonly, copy) NSString *debugDescription;@endOBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)OBJC_ROOT_CLASSOBJC_EXPORT@interface NSObject <NSObject> {#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wobjc-interface-ivars" Class isa OBJC_ISA_AVAILABILITY;#pragma clang diagnostic pop}+ (void)load;+ (void)initialize;- (instancetype)init#if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER NS_DESIGNATED_INITIALIZER#endif ;+ (instancetype)new OBJC_SWIFT_UNAVAILABLE("use object initializers instead");+ (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_SWIFT_UNAVAILABLE("use object initializers instead");+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");- (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer");- (void)finalize OBJC_DEPRECATED("Objective-C garbage collection is no longer supported");- (id)copy;- (id)mutableCopy;+ (id)copyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;+ (id)mutableCopyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;+ (BOOL)instancesRespondToSelector:(SEL)aSelector;+ (BOOL)conformsToProtocol:(Protocol *)protocol;- (IMP)methodForSelector:(SEL)aSelector;+ (IMP)instanceMethodForSelector:(SEL)aSelector;- (void)doesNotRecognizeSelector:(SEL)aSelector;- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");- (BOOL)allowsWeakReference UNAVAILABLE_ATTRIBUTE;- (BOOL)retainWeakReference UNAVAILABLE_ATTRIBUTE;+ (BOOL)isSubclassOfClass:(Class)aClass;+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);+ (NSUInteger)hash;+ (Class)superclass;+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");+ (NSString *)description;+ (NSString *)debugDescription;@end#endif#endif复制代码1.3 对象 -- objc_object关键信息:
- isa: isa_t类型的指针,详情可见下面3.2节。简单的说,它是这样的一个联合体,包含了bits (是一个 uintptr_t 类型的值,作为isa初始化列表中必初始化的值,可以用来获取isa结构体)和 cls (该变量会指向对象所属的类的结构,在 64 位设备上会占用 8byte)。
objc-private.hobjc_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(); // 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); // changeIsa() should be used to change the isa of existing objects. // If this is a new object, use initIsa() for performance. Class changeIsa(Class newCls); bool hasNonpointerIsa(); bool isTaggedPointer(); bool isBasicTaggedPointer(); bool isExtTaggedPointer(); bool isClass(); // object may have associated objects? bool hasAssociatedObjects(); void setHasAssociatedObjects(); // object may be weakly referenced? bool isWeaklyReferenced(); void setWeaklyReferenced_nolock(); // object may have -.cxx_destruct implementation? bool hasCxxDtor(); // Optimized calls to retain/release methods id retain(); void release(); id autorelease(); // Implementations of retain/release methods id rootRetain(); bool rootRelease(); id rootAutorelease(); bool rootTryRetain(); bool rootReleaseShouldDealloc(); uintptr_t rootRetainCount(); // Implementation of dealloc methods bool rootIsDeallocating(); void clearDeallocating(); void rootDealloc();private: void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor); // Slow paths for inline control id rootAutorelease2(); bool overrelease_error();#if SUPPORT_NONPOINTER_ISA // Unified retain count manipulation for nonpointer isa id rootRetain(bool tryRetain, bool handleOverflow); bool rootRelease(bool performDealloc, bool handleUnderflow); id rootRetain_overflow(bool tryRetain); bool rootRelease_underflow(bool performDealloc); void clearDeallocating_slow(); // Side table retain count overflow for nonpointer isa void sidetable_lock(); void sidetable_unlock(); void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced); bool sidetable_addExtraRC_nolock(size_t delta_rc); size_t sidetable_subExtraRC_nolock(size_t delta_rc); size_t sidetable_getExtraRC_nolock();#endif // Side-table-only retain count bool sidetable_isDeallocating(); void sidetable_clearDeallocating(); bool sidetable_isWeaklyReferenced(); void sidetable_setWeaklyReferenced_nolock(); id sidetable_retain(); id sidetable_retain_slow(SideTable& table); uintptr_t sidetable_release(bool performDealloc = true); uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true); bool sidetable_tryRetain(); uintptr_t sidetable_retainCount();#if DEBUG bool sidetable_present();#endif};复制代码1.4 类 -- objc_class关键信息:
- isa: 继承于objc_object
- superclass: 指向自己父类的指针
- cache: 方法缓存
- bits: 它是一个class_data_bits_t类型的指针。作为本类的实例方法链表。
注意区别:
这里的bits是class_data_bits_t类型的,上一节objc_object的isa_t类型数据中也有一个uintptr_t类型的bits,但是这是两种结构。
由此可见,objc_class 继承于 objc_object, 所以也是包含一个isa的类。在OC里,不只是对象的实例包含一个isa,这个对象的类本身也有这么一个isa,类本身也是一个对象。
objc-runtime-new.hobjc_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(); } void setData(class_rw_t *newData) { bits.setData(newData); } void setInfo(uint32_t set) { assert(isFuture() || isRealized()); data()->setFlags(set); } void clearInfo(uint32_t clear) { assert(isFuture() || isRealized()); data()->clearFlags(clear); } // set and clear must not overlap void changeInfo(uint32_t set, uint32_t clear) { assert(isFuture() || isRealized()); assert((set & clear) == 0); data()->changeFlags(set, clear); } bool hasCustomRR() { return ! bits.hasDefaultRR(); } void setHasDefaultRR() { assert(isInitializing()); bits.setHasDefaultRR(); } void setHasCustomRR(bool inherited = false); void printCustomRR(bool inherited); bool hasCustomAWZ() { return ! bits.hasDefaultAWZ(); } void setHasDefaultAWZ() { assert(isInitializing()); bits.setHasDefaultAWZ(); } void setHasCustomAWZ(bool inherited = false); void printCustomAWZ(bool inherited); bool instancesRequireRawIsa() { return bits.instancesRequireRawIsa(); } void setInstancesRequireRawIsa(bool inherited = false); void printInstancesRequireRawIsa(bool inherited); bool canAllocNonpointer() { assert(!isFuture()); return !instancesRequireRawIsa(); } bool canAllocFast() { assert(!isFuture()); return bits.canAllocFast(); } bool hasCxxCtor() { // addSubclass() propagates this flag from the superclass. assert(isRealized()); return bits.hasCxxCtor(); } void setHasCxxCtor() { bits.setHasCxxCtor(); } bool hasCxxDtor() { // addSubclass() propagates this flag from the superclass. assert(isRealized()); return bits.hasCxxDtor(); } void setHasCxxDtor() { bits.setHasCxxDtor(); } bool isSwift() { return bits.isSwift(); } // Return YES if the class's ivars are managed by ARC, // or the class is MRC but has ARC-style weak ivars. bool hasAutomaticIvars() { return data()->ro->flags & (RO_IS_ARC | RO_HAS_WEAK_WITHOUT_ARC); } // Return YES if the class's ivars are managed by ARC. bool isARC() { return data()->ro->flags & RO_IS_ARC; }#if SUPPORT_NONPOINTER_ISA // Tracked in non-pointer isas; not tracked otherwise#else bool instancesHaveAssociatedObjects() { // this may be an unrealized future class in the CF-bridged case assert(isFuture() || isRealized()); return data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS; } void setInstancesHaveAssociatedObjects() { // this may be an unrealized future class in the CF-bridged case assert(isFuture() || isRealized()); setInfo(RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS); }#endif bool shouldGrowCache() { return true; } void setShouldGrowCache(bool) { // fixme good or bad for memory use? } bool isInitializing() { return getMeta()->data()->flags & RW_INITIALIZING; } void setInitializing() { assert(!isMetaClass()); ISA()->setInfo(RW_INITIALIZING); } bool isInitialized() { return getMeta()->data()->flags & RW_INITIALIZED; } void setInitialized(); bool isLoadable() { assert(isRealized()); return true; // any class registered for +load is definitely loadable } IMP getLoadMethod(); // Locking: To prevent concurrent realization, hold runtimeLock. bool isRealized() { return data()->flags & RW_REALIZED; } // Returns true if this is an unrealized future class. // Locking: To prevent concurrent realization, hold runtimeLock. bool isFuture() { return data()->flags & RW_FUTURE; } bool isMetaClass() { assert(this); assert(isRealized()); return data()->ro->flags & RO_META; } // NOT identical to this->ISA when this is a metaclass Class getMeta() { if (isMetaClass()) return (Class)this; else return this->ISA(); } bool isRootClass() { return superclass == nil; } bool isRootMetaclass() { return ISA() == (Class)this; } const char *mangledName() { // fixme can't assert locks here assert(this); if (isRealized() || isFuture()) { return data()->ro->name; } else { return ((const class_ro_t *)data())->name; } } const char *demangledName(bool realize = false); const char *nameForLogging(); // May be unaligned depending on class's ivars. uint32_t unalignedInstanceStart() { assert(isRealized()); return data()->ro->instanceStart; } // Class's instance start rounded up to a pointer-size boundary. // This is used for ARC layout bitmaps. uint32_t alignedInstanceStart() { return word_align(unalignedInstanceStart()); } // May be unaligned depending on class's ivars. uint32_t unalignedInstanceSize() { assert(isRealized()); return data()->ro->instanceSize; } // Class's ivar size rounded up to a pointer-size boundary. uint32_t alignedInstanceSize() { return word_align(unalignedInstanceSize()); } size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; } void setInstanceSize(uint32_t newSize) { assert(isRealized()); if (newSize != data()->ro->instanceSize) { assert(data()->flags & RW_COPIED_RO); *const_cast<uint32_t *>(&data()->ro->instanceSize) = newSize; } bits.setFastInstanceSize(newSize); } void chooseClassArrayIndex(); void setClassArrayIndex(unsigned Idx) { bits.setClassArrayIndex(Idx); } unsigned classArrayIndex() { return bits.classArrayIndex(); }};复制代码1.5 NSObject,objc_object,objc_class 三者的关系1)NSObject与objc_classNSObject有一个Class类型,名为isa成员变量
继续查看Class的本质,可以发现Class 其实就是 C 语言定义的结构体类型(struct objc_class)的指针,这个声明说明 Objective-C 的 类 实际上就是 struct objc_class。
另外,第二个定义是经常遇到的 id 类型,这里可以看出 id 类型是 C 语言定义的结构体类型(struct objc_object)的指针,我们知道我们可以用 id 来声明一个对象,所以这也说明了 Objective-C 的 对象 实际上就是 struct objc_object。
2)objc_object与objc_class继续查看objc_class的本质,可以发现objc_class是一个 继承 自objc_object的结构体。所以 Objective-C 中的 类 自身也是一个 对象,只是除了 objc_object 中定义的成员变量外,还有另外三个成员变量:superclass、cache 和 bits。
注意,这里面的 “结构体” 并非 C语言 里面的结构体,而是 C++语言 里面的结构体,而且这个概念仅限字面意思的结构体。严格来讲,其实struct关键字定义的是 类,跟class关键字定义的类除了默认访问权限的区别,没有区别。这一点,国内人写的C++书籍却很少有注意到。下面是比较权威的《C++ Primer》(第546页)一书关于这点的说明。
3)知识补课C++中的struct对C中的struct进行了扩充,它已经不再只是一个包含不同数据类型的数据结构了,它已经获取了太多的功能。下面简单列一下C++的struct跟C中的struct不一样的地方:
- struct能包含成员函数
- struct能继承
- struct能实现多态
2. 手动引用对引用计数的影响 -- retain操作2.1 两种对象:NSObject与Object的引用增加① NSObject的retainNSObject.mmretain
+ (id)retain { return (id)self;}// Replaced by ObjectAlloc- (id)retain { return ((id)self)->rootRetain();}复制代码② Object的retainObject.mmretain
+(id) retain{ return self;}-(id) retain{ return _objc_rootRetain(self);}复制代码NSObject.mm_objc_rootRetain(id obj)
id_objc_rootRetain(id obj){ assert(obj); return obj->rootRetain();}复制代码可见,无论是NSObject还是Object的 retain,归根结底,调用的都是 objc_object 的 rootRetain()。
2.2 归根结底 -- NSObject对象的rootRetain()objc4/objc4-723/runtime/objc-object.hobjc_object::rootRetain()
ALWAYS_INLINE id objc_object::rootRetain(){ return rootRetain(false, false);}复制代码objc4/objc4-723/runtime/objc-object.hobjc_object::rootRetain(bool tryRetain, bool handleOverflow)
ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow){ if (isTaggedPointer()) return (id)this; bool sideTableLocked = false; bool transcribeToSideTable = false; isa_t oldisa; isa_t newisa; do { transcribeToSideTable = false; oldisa = LoadExclusive(&isa.bits); newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain(); } // don't check newisa.fast_rr; we already called any RR overrides if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ if (slowpath(carry)) { // newisa.extra_rc++ overflowed if (!handleOverflow) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); } // Leave half of the retain counts inline and // prepare to copy the other half to the side table. if (!tryRetain && !sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(transcribeToSideTable)) { // Copy the other half of the retain counts to the side table. sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this;}复制代码其中,手动retain对引用计数的影响关键在这么一句话:
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++复制代码对isa的 extra_rc 变量进行+1,前面说到isa会存很多东西。
3. isa与Tagged Pointer3.1 NSObject的唯一成员变量 -- isaNSObject.hNSObject的isa
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)OBJC_ROOT_CLASSOBJC_EXPORT@interface NSObject <NSObject> {#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wobjc-interface-ivars" Class isa OBJC_ISA_AVAILABILITY;#pragma clang diagnostic pop}复制代码其中,Class isa继续查看Class的定义:
objc-private.hClass
typedef struct objc_class *Class;typedef struct objc_object *id;复制代码其中,objc_object类内部结构:
其中,私有的成员数据isa为isa_t类型的联合体:
objc-private.hisa_t
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits;#if SUPPORT_PACKED_ISA // extra_rc must be the MSB-most field (so it matches carry/overflow flags) // nonpointer must be the LSB (fixme or get rid of it) // shiftcls must occupy the same bits that a real class pointer would // bits + RC_ONE is equivalent to extra_rc + 1 // RC_HALF is the high bit of extra_rc (i.e. half of its range) // future expansion: // uintptr_t fast_rr : 1; // no r/r overrides // uintptr_t lock : 2; // lock for atomic property, @synch // uintptr_t extraBytes : 1; // allocated with extra bytes# 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) };# elif __x86_64__# define ISA_MASK 0x00007ffffffffff8ULL# define ISA_MAGIC_MASK 0x001f800000000001ULL# define ISA_MAGIC_VALUE 0x001d800000000001ULL struct { uintptr_t nonpointer : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000 uintptr_t magic : 6; uintptr_t weakly_referenced : 1; uintptr_t deallocating : 1; uintptr_t has_sidetable_rc : 1; uintptr_t extra_rc : 8;# define RC_ONE (1ULL<<56)# define RC_HALF (1ULL<<7) };# else# error unknown architecture for packed isa# endif// SUPPORT_PACKED_ISA#endif#if SUPPORT_INDEXED_ISA# if __ARM_ARCH_7K__ >= 2# define ISA_INDEX_IS_NPI 1# define ISA_INDEX_MASK 0x0001FFFC# define ISA_INDEX_SHIFT 2# define ISA_INDEX_BITS 15# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS)# define ISA_INDEX_MAGIC_MASK 0x001E0001# define ISA_INDEX_MAGIC_VALUE 0x001C0001 struct { uintptr_t nonpointer : 1; uintptr_t has_assoc : 1; uintptr_t indexcls : 15; uintptr_t magic : 4; uintptr_t has_cxx_dtor : 1; uintptr_t weakly_referenced : 1; uintptr_t deallocating : 1; uintptr_t has_sidetable_rc : 1; uintptr_t extra_rc : 7;# define RC_ONE (1ULL<<25)# define RC_HALF (1ULL<<6) };# else# error unknown architecture for indexed isa# endif// SUPPORT_INDEXED_ISA#endif};复制代码其中,cls 变量会指向对象所属的类的结构,在 64 位设备上会占用 8byte。
另外,bits 变量保存着isa的唯一标志(可以根据bits获取isa),是一个类型为 uintptr_t 的数据, uintptr_t的定义:
typedef unsigned long uintptr_t;复制代码知识回顾不熟悉C++的朋友可能很难看出来bits会是如何初始化的,其实,这是一种与构造函数并列的初始化办法 -- 初始化列表。关于初始化列表的定义,截取百度百科的一段话:
所以,再回过来看bits,bits以isa_t(uintptr_t value)中的value为初始化的值:
例如isa初始化的API objc_object::initIsa(Class cls)`中,有这样一句:
isa_t newisa(0);newisa.bits = ISA_INDEX_MAGIC_VALUE;//...复制代码而这个bits值可以用来获取isa(注意区分左右两边的bits分别是两个东西):
isa_t bits = LoadExclusive(&isa.bits);复制代码其中,LoadExclusive根据平台的不同,实现体并不一样,这是__arm64__平台的实现体:
#if __arm64__static ALWAYS_INLINEuintptr_t LoadExclusive(uintptr_t *src){ uintptr_t result; asm("ldxr %x0, [%x1]" : "=r" (result) : "r" (src), "m" (*src)); return result;}复制代码对这个isa (这里是左边的bits,它是个isa,而非右边的uintptr_t) 的调用,比如获取引用计数的源代码中就有几处:
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();}复制代码调用的有:bits.extra_rcbits.nonpointerbits.has_sidetable_rc
3.2 isa_t联合体里面struct的数据含义nonpointer该变量占用 1bit 内存空间,可以有两个值:0 和 1,分别代表不同的 isa_t 的类型:
- 0 表示 isa_t 没有开启指针优化,不使用 isa_t 中定义的结构体。访问 objc_object 的 isa 会直接返回 isa_t 结构中的 cls 变量,cls 变量会指向对象所属的类的结构,在 64 位设备上会占用 8byte。
- 1 表示 isa_t 开启了指针优化,不能直接访问 objc_object 的 isa 成员变量 (因为 isa 已经不是一个合法的内存指针了,而是一个 Tagged Pointer ),从其名字 nonpointer 也可获知这个 isa 已经不是一个指针了。但是 isa 中包含了类信息、对象的引用计数等信息,在 64 位设备上充分利用了内存空间。
shiftcls存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。
has_assoc该变量与对象的关联引用有关,当对象有关联引用时,释放对象时需要做额外的逻辑。关联引用就是我们通常用 objc_setAssociatedObject 方法设置给对象的,这里对于关联引用不做过多分析,如果后续有时间写关联引用实现时再深入分析关联引用有关的代码。
has_cxx_dtor表示该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
magic用于判断对象是否已经完成了初始化,在 arm64 中 0x16 是调试器判断当前对象是真的对象还是没有初始化的空间(在 x86_64 中该值为 0x3b)。
weakly_referenced标志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。
deallocating标志对象是否正在释放内存。
extra_rc表示该对象的引用计数值,实际上是引用计数值减 1,例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10,则需要使用到下面的 has_sidetable_rc。
has_sidetable_rc当对象引用计数大于 10 时,则has_sidetable_rc 的值为 1,那么引用计数会存储在一个叫 SideTable 的类的属性中,这是一个散列表。
ISA_MAGIC_MASK通过掩码方式获取 magic 值。
ISA_MASK通过掩码方式获取 isa 的类指针值。
RC_ONE 和 RC_HALF用于引用计数的相关计算。
3.3 isa_t联合体里面的宏SUPPORT_PACKED_ISA
表示平台是否支持在 isa 指针中插入除 Class 之外的信息。
- 如果支持就会将 Class 信息放入 isa_t 定义的 struct 内,并附上一些其他信息,例如上面的 nonpointer 等等;
- 如果不支持,那么不会使用 isa_t 内定义的 struct,这时 isa_t 只使用 cls(Class 指针)。
小结:在 iOS 以及 MacOSX 设备上,SUPPORT_PACKED_ISA 定义为 1。
__arm64__、__x86_64__
表示 CPU 架构,例如电脑一般是 __x86_64__ 架构,手机一般是 arm 结构,这里 64 代表是 64 位 CPU。上面只列出了 __arm64__ 架构的定义。
小结:iOS 设备上 __arm64__ 是 1。
SUPPORT_INDEXED_ISA
SUPPORT_INDEXED_ISA 表示 isa_t 中存放的 Class 信息是 Class 的地址,还是一个索引(根据该索引可在类信息表中查找该类结构地址)。可以看出,多了一个 uintptr_t indexcls : 15;。
小结:iOS 设备上 SUPPORT_INDEXED_ISA 是 0。
3.4 是否Tagged Pointer的判断objc-object.hobjc_object::isTaggedPointer()
inline bool objc_object::isTaggedPointer() { return _objc_isTaggedPointer(this);}复制代码objc-internal.h_objc_isTaggedPointer(const void * _Nullable ptr)
static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) { return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;}复制代码3.5 与isa类型有关的宏SUPPORT_NONPOINTER_ISA
用于标记是否支持优化的 isa 指针,其字面含义意思是 isa 的内容不再是类的指针了,而是包含了更多信息,比如引用计数,析构状态,被其他 weak 变量引用情况。下面看看SUPPORT_NONPOINTER_ISA及其相关宏的定义:
objc-config.hSUPPORT_TAGGED_POINTERS
// Define SUPPORT_TAGGED_POINTERS=1 to enable tagged pointer objects// Be sure to edit tagged pointer SPI in objc-internal.h as well.#if !(__OBJC2__ && __LP64__)# define SUPPORT_TAGGED_POINTERS 0#else# define SUPPORT_TAGGED_POINTERS 1#endif// Define SUPPORT_MSB_TAGGED_POINTERS to use the MSB // as the tagged pointer marker instead of the LSB.// Be sure to edit tagged pointer SPI in objc-internal.h as well.#if !SUPPORT_TAGGED_POINTERS || !TARGET_OS_IPHONE# define SUPPORT_MSB_TAGGED_POINTERS 0#else# define SUPPORT_MSB_TAGGED_POINTERS 1#endif// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa // field as an index into a class table.// Note, keep this in sync with any .s files which also define it.// Be sure to edit objc-abi.h as well.#if __ARM_ARCH_7K__ >= 2# define SUPPORT_INDEXED_ISA 1#else# define SUPPORT_INDEXED_ISA 0#endif// Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa // field as a maskable pointer with other data around it.#if (!__LP64__ || TARGET_OS_WIN32 || TARGET_OS_SIMULATOR)# define SUPPORT_PACKED_ISA 0#else# define SUPPORT_PACKED_ISA 1#endif// Define SUPPORT_NONPOINTER_ISA=1 on any platform that may store something// in the isa field that is not a raw pointer.#if !SUPPORT_INDEXED_ISA && !SUPPORT_PACKED_ISA# define SUPPORT_NONPOINTER_ISA 0#else# define SUPPORT_NONPOINTER_ISA 1#endif复制代码3.6 怎么判断是否支持优化的isa指针?-- 看设备、自己设置。- 已知iOS系统的SUPPORT_PACKED_ISA为1,SUPPORT_INDEXED_ISA为0,根据4.5节中源代码的定义可知,iOS系统的SUPPORT_NONPOINTER_ISA为1。
- 在环境变量中设置OBJC_DISABLE_NONPOINTER_ISA。
即,iOS系统 支持 优化的isa指针。
在 64 位环境下,优化的 isa 指针并不是就一定会存储引用计数,毕竟用 19bit (iOS 系统)保存引用计数不一定够。需要注意的是这 19 位保存的是引用计数的值减一。
3.7 怎么判断是否Tagged Pointer的对象?-- 看对象、自己设置- 可以启用Tagged Pointer的类对象有:NSDate、NSNumber、NSString。Tagged Pointer专门用来存储小的对象。
- 在环境变量中设置OBJC_DISABLE_TAGGED_POINTERS=YES强制不启用Tagged Pointer。
3.8 引用计数的存储形式 -- 散列表下面对sidetable_retain进行分析。
4. 散列表4.1 增加引用计数 -- sidetable_retain()第2节的增加引用假设,以及后面第8节的获取引用计数会用到下面的API:
NSObject.mmobjc_object::sidetable_retain()
idobjc_object::sidetable_retain(){#if SUPPORT_NONPOINTER_ISA assert(!isa.nonpointer);#endif SideTable& table = SideTables()[this]; table.lock(); size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { refcntStorage += SIDE_TABLE_RC_ONE; } table.unlock(); return (id)this;}复制代码4.2 增加引用计数 -- sidetable_tryRetain()NSObject.mmobjc_object::sidetable_tryRetain()
boolobjc_object::sidetable_tryRetain(){#if SUPPORT_NONPOINTER_ISA assert(!isa.nonpointer);#endif SideTable& table = SideTables()[this]; // NO SPINLOCK HERE // _objc_rootTryRetain() is called exclusively by _objc_loadWeak(), // which already acquired the lock on our behalf. // fixme can't do this efficiently with os_lock_handoff_s // if (table.slock == 0) { // _objc_fatal("Do not call -_tryRetain."); // } bool result = true; RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end()) { table.refcnts[this] = SIDE_TABLE_RC_ONE; } else if (it->second & SIDE_TABLE_DEALLOCATING) { result = false; } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { it->second += SIDE_TABLE_RC_ONE; } return result;}复制代码4.3 获取散列表 -- SideTable()NSObject.mmSideTable
struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; SideTable() { memset(&weak_table, 0, sizeof(weak_table)); } ~SideTable() { _objc_fatal("Do not delete SideTable."); } void lock() { slock.lock(); } void unlock() { slock.unlock(); } void forceReset() { slock.forceReset(); } // Address-ordered lock discipline for a pair of side tables. template<HaveOld, HaveNew> static void lockTwo(SideTable *lock1, SideTable *lock2); template<HaveOld, HaveNew> static void unlockTwo(SideTable *lock1, SideTable *lock2);};复制代码其中,RefcountMap以及HaveOld,HaveNew的定义为:
// RefcountMap disguises its pointers because we // don't want the table to act as a root for `leaks`.typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;// Template parameters.enum HaveOld { DontHaveOld = false, DoHaveOld = true };enum HaveNew { DontHaveNew = false, DoHaveNew = true };复制代码llvm-DenseMap.hDenseMap/DenseMapBase
DenseMapBase
DenseMap
5. 设置变量导致的引用计数变化 -- objc_retain操作5.1 情况1 -- strongruntime.h设置strong变量
/** * Sets the value of an instance variable in an object. * * @param obj The object containing the instance variable whose value you want to set. * @param ivar The Ivar describing the instance variable whose value you want to set. * @param value The new value for the instance variable. * * @note Instance variables with known memory management (such as ARC strong and weak) * use that memory management. Instance variables with unknown memory management * are assigned as if they were strong. * @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar * for the instance variable is already known. */OBJC_EXPORT voidobject_setIvarWithStrongDefault(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0);复制代码objc-class.mmobject_setIvarWithStrongDefault
void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value){ return _object_setIvar(obj, ivar, value, true /*strong default*/);}复制代码objc-class.mm_object_setIvar
static ALWAYS_INLINE void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong){ if (!obj || !ivar || obj->isTaggedPointer()) return; ptrdiff_t offset; objc_ivar_memory_management_t memoryManagement; _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement); if (memoryManagement == objc_ivar_memoryUnknown) { if (assumeStrong) memoryManagement = objc_ivar_memoryStrong; else memoryManagement = objc_ivar_memoryUnretained; } id *location = (id *)((char *)obj + offset); switch (memoryManagement) { case objc_ivar_memoryWeak: objc_storeWeak(location, value); break; case objc_ivar_memoryStrong: objc_storeStrong(location, value); break; case objc_ivar_memoryUnretained: *location = value; break; case objc_ivar_memoryUnknown: _objc_fatal("impossible"); }}复制代码NSObject.mmobjc_storeStrong
voidobjc_storeStrong(id *location, id obj){ id prev = *location; if (obj == prev) { return; } objc_retain(obj); *location = obj; objc_release(prev);}复制代码5.2 情况2 -- weakobjc-class.mm设置weak变量
object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);复制代码objc-class.mmobject_setIvar
void object_setIvar(id obj, Ivar ivar, id value){ return _object_setIvar(obj, ivar, value, false /*not strong default*/);}复制代码- 可见,这里同样调用了 _object_setIvar,代码情况1,是同一个API。其中,不同于objc_storeStrong,走的是objc_storeWeak,下面分析一下:
NSObject.mmobjc_storeWeak
/** * This function stores a new value into a __weak variable. It would * be used anywhere a __weak variable is the target of an assignment. * * @param location The address of the weak pointer itself * @param newObj The new object this weak ptr should now point to * * @return \e newObj */idobjc_storeWeak(id *location, id newObj){ return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object *)newObj);}复制代码上面有一个storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object *)newObj),它的代码有点长,核心的关键是更新了weak哈希表:->weak_table。读者可以从下面搜索一下这个关键词的位置。
// Update a weak variable.// If HaveOld is true, the variable has an existing value // that needs to be cleaned up. This value might be nil.// If HaveNew is true, there is a new value that needs to be // assigned into the variable. This value might be nil.// If CrashIfDeallocating is true, the process is halted if newObj is // deallocating or newObj's class does not support weak references. // If CrashIfDeallocating is false, nil is stored instead.enum CrashIfDeallocating { DontCrashIfDeallocating = false, DoCrashIfDeallocating = true};template <HaveOld haveOld, HaveNew haveNew, CrashIfDeallocating crashIfDeallocating>static id storeWeak(id *location, objc_object *newObj){ assert(haveOld || haveNew); if (!haveNew) assert(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; // Acquire locks for old and new values. // Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us. retry: if (haveOld) { oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if (haveNew) { newTable = &SideTables()[newObj]; } else { newTable = nil; } SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable); if (haveOld && *location != oldObj) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); goto retry; } // Prevent a deadlock between the weak reference machinery // and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa. if (haveNew && newObj) { Class cls = newObj->getIsa(); if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); _class_initialize(_class_getNonMetaClass(cls, (id)newObj)); // If this class is finished with +initialize then we're good. // If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself) // then we may proceed but it will appear initializing and // not yet initialized to the check above. // Instead set previouslyInitializedClass to recognize it on retry. previouslyInitializedClass = cls; goto retry; } } // Clean up old value, if any. if (haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // 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; } else { // No new value. The storage is not changed. } SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); return (id)newObj;}复制代码5.3 objc_storeStrong导致的retain上面第5.1节中有一个objc_storeStrong,这里继续分析它的原理。
NSObject.mmobjc_storeStrong(id *location, id obj)
voidobjc_storeStrong(id *location, id obj){ id prev = *location; if (obj == prev) { return; } objc_retain(obj); *location = obj; objc_release(prev);}复制代码NSObject.mmobjc_retain(id obj)
/************************************************************************ Optimized retain/release/autorelease entrypoints**********************************************************************/#if __OBJC2____attribute__((aligned(16)))id objc_retain(id obj){ if (!obj) return obj; if (obj->isTaggedPointer()) return obj; return obj->retain();}__attribute__((aligned(16)))void objc_release(id obj){ if (!obj) return; if (obj->isTaggedPointer()) return; return obj->release();}__attribute__((aligned(16)))idobjc_autorelease(id obj){ if (!obj) return obj; if (obj->isTaggedPointer()) return obj; return obj->autorelease();}// OBJC2#else// not OBJC2id objc_retain(id obj) { return [obj retain]; }void objc_release(id obj) { [obj release]; }id objc_autorelease(id obj) { return [obj autorelease]; }#endif复制代码可知:1)如果TaggedPointer,则返回本身。2)如果非TaggedPointer,则由对象的retain()返回。
objc-object.hobjc_object::retain()
// Equivalent to calling [this retain], with shortcuts if there is no overrideinline id objc_object::retain(){ assert(!isTaggedPointer()); if (fastpath(!ISA()->hasCustomRR())) { return rootRetain(); } return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);}复制代码objc-object.hobjc_object::rootRetain()
// Base retain implementation, ignoring overrides.// This does not check isa.fast_rr; if there is an RR override then // it was already called and it chose to call [super retain].//// tryRetain=true is the -_tryRetain path.// handleOverflow=false is the frameless fast path.// handleOverflow=true is the framed slow path including overflow to side table// The code is structured this way to prevent duplication.ALWAYS_INLINE id objc_object::rootRetain(){ return rootRetain(false, false);}复制代码这里的rootRetain(false, false);正是上文第2.2节中介绍的,不再赘述。
6. 新建对象(分配内存与初始化)导致的引用计数变化 -- alloc 和 init 操作首先,新建一个对象的典型写法:
NSObject *obj = [NSObject alloc] init];复制代码6.1 分配内存 -- alloc+ (id)alloc { return _objc_rootAlloc(self);}复制代码// Base class implementation of +alloc. cls is not nil.// Calls [cls allocWithZone:nil].id_objc_rootAlloc(Class cls){ return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);}复制代码// Call [cls alloc] or [cls allocWithZone:nil], with appropriate // shortcutting optimizations.static ALWAYS_INLINE idcallAlloc(Class cls, bool checkNil, bool allocWithZone=false){ if (slowpath(checkNil && !cls)) return nil;#if __OBJC2__ if (fastpath(!cls->ISA()->hasCustomAWZ())) { // No alloc/allocWithZone implementation. Go straight to the allocator. // fixme store hasCustomAWZ in the non-meta class and // add it to canAllocFast's summary if (fastpath(cls->canAllocFast())) { // No ctors, raw isa, etc. Go straight to the metal. bool dtor = cls->hasCxxDtor(); id obj = (id)calloc(1, cls->bits.fastInstanceSize()); if (slowpath(!obj)) return callBadAllocHandler(cls); obj->initInstanceIsa(cls, dtor); return obj; } else { // Has ctor or raw isa or something. Use the slower path. id obj = class_createInstance(cls, 0); if (slowpath(!obj)) return callBadAllocHandler(cls); return obj; } }#endif // No shortcuts available. if (allocWithZone) return [cls allocWithZone:nil]; return [cls alloc];}复制代码分支1 -- obj->initInstanceIsa(cls, dtor);inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor){ assert(!cls->instancesRequireRawIsa()); assert(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor);}复制代码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);#if SUPPORT_INDEXED_ISA assert(cls->classArrayIndex() > 0); newisa.bits = ISA_INDEX_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.indexcls = (uintptr_t)cls->classArrayIndex();#else 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;#endif // This write must be performed in a single store in some cases // (for example when realizing a class because other threads // may simultaneously try to use the class). // fixme use atomics here to guarantee single-store and to // guarantee memory order w.r.t. the class index table // ...but not too atomic because we don't want to hurt instantiation isa = newisa; }}复制代码上述代码中,newisa.bits = ISA_MAGIC_VALUE; 是为了对 isa 结构赋值一个初始值,通过对 isa_t 的结构分析,我们可以知道此次赋值只是对 nonpointer 和 magic 部分进行了赋值。
newisa.shiftcls = (uintptr_t)cls >> 3; 是将类的地址存储在对象的 isa 结构中。这里右移三位的主要原因是用于将 Class 指针中无用的后三位清除减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。关于类指针对齐的详细解析可参考:从 NSObject 的初始化了解 isa 。
分支2 -- id obj = class_createInstance(cls, 0);id class_createInstance(Class cls, size_t extraBytes){ return _class_createInstanceFromZone(cls, extraBytes, nil);}复制代码/************************************************************************ class_createInstance* fixme* Locking: none**********************************************************************/static __attribute__((always_inline)) id_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil){ if (!cls) return nil; assert(cls->isRealized()); // Read class's info bits all at once for performance bool hasCxxCtor = cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); size_t size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (!zone && fast) { obj = (id)calloc(1, size); if (!obj) return nil; obj->initInstanceIsa(cls, hasCxxDtor); } else { if (zone) { obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size); } else { obj = (id)calloc(1, size); } if (!obj) return nil; // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } if (cxxConstruct && hasCxxCtor) { obj = _objc_constructOrFree(obj, cls); } return obj;}复制代码其中,有个 obj->initIsa(cls);,初始化isa的操作:
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);#if SUPPORT_INDEXED_ISA assert(cls->classArrayIndex() > 0); newisa.bits = ISA_INDEX_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.indexcls = (uintptr_t)cls->classArrayIndex();#else 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;#endif // This write must be performed in a single store in some cases // (for example when realizing a class because other threads // may simultaneously try to use the class). // fixme use atomics here to guarantee single-store and to // guarantee memory order w.r.t. the class index table // ...but not too atomic because we don't want to hurt instantiation isa = newisa; }}复制代码可见,alloc的时候会初始化isa,并通过newisa(0)的初始化列表办法生成一个isa,并根据是否支持indexed isa分别设置.bits的值。
6.2 初始化 -- init- (id)init { return _objc_rootInit(self);}复制代码id_objc_rootInit(id obj){ // In practice, it will be hard to rely on this function. // Many classes do not properly chain -init calls. return obj;}复制代码7. 获取引用计数NSObject.mmretainCount
- (NSUInteger)retainCount { return ((id)self)->rootRetainCount();}复制代码objc-object.hobjc_object::rootRetainCount()
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();}复制代码可见,获取引用计数的关键在这么一句话:
uintptr_t rc = 1 + bits.extra_rc;复制代码bits.extra_rc表示引用计数减1。当然,这只针对情况1,即bits.nonpointer为1(开启了指针优化),且bits.has_sidetable_rc为0(表示不存储在散列表Side Table中,而存储在extra_rc中)。
直接返回isa值本身。
- 情况1 -- 非TaggedPointer,且开启了指针优化,且存储在extra_rc中
objc-os.hLoadExclusive(uintptr_t *src)
static ALWAYS_INLINEuintptr_t LoadExclusive(uintptr_t *src){ return *src;}复制代码- 情况2 -- 非TaggedPointer,且开启了指针优化,且存储在散列表中
NSObject.mmobjc_object::sidetable_getExtraRC_nolock()
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;}复制代码可见,其逻辑就是先从 SideTable 的静态方法获取当前实例对应的 SideTable 对象,其 refcnts 属性就是之前说的存储引用计数的散列表,这里将其类型简写为 RefcountMap:
typedef objc::DenseMap RefcountMap;复制代码然后在引用计数表中用迭代器查找当前实例对应的键值对,获取引用计数值,并在此基础上 +1 并将结果返回。这也就是为什么之前说引用计数表存储的值为实际引用计数减一。
需要注意的是为什么这里把键值对的值做了向右移位操作(it->second >> SIDE_TABLE_RC_SHIFT):
// 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)复制代码可以看出值的第一个 bit 表示该对象是否有过 weak 对象,如果没有,在析构释放内存时可以更快;第二个 bit 表示该对象是否正在析构。从第三个 bit 开始才是存储引用计数数值的地方。所以这里要做向右移两位的操作,而对引用计数的 +1 和 -1 可以使用 SIDE_TABLE_RC_ONE,还可以用 SIDE_TABLE_RC_PINNED 来判断是否引用计数值有可能溢出。
NSObject.mmobjc_object::sidetable_retainCount()
uintptr_tobjc_object::sidetable_retainCount(){ SideTable& table = SideTables()[this]; size_t refcnt_result = 1; table.lock(); RefcountMap::iterator it = table.refcnts.find(this); if (it != table.refcnts.end()) { // this is valid for SIDE_TABLE_RC_PINNED too refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT; } table.unlock(); return refcnt_result;}复制代码8. 结论- 如果有些对象支持使用 TaggedPointer:
- 如果另外一些对象不支持使用 TaggedPointer:
- 如果当前设备是 64 位环境并且使用 Objective-C 2.0,那么会使用对象的 isa 指针 的 一部分空间 (bits.extra_rc)来存储它的引用计数;
- 否则 Runtime 会使用一张 散列表 (SideTables())来管理引用计数。
作者:陈满iOS
链接:https://juejin.im/post/5b4c59a55188251ac9767872
|
|