引言:这篇文章旨在从runtime源码中分析出 引用计数 值本身的保存位置,适合对底层原理有兴趣的朋友,或者面试造火箭的同学(比如百度的面试官非常喜欢问底层原理:好,我知道你说了深浅复制的区别一大堆,如果我让你自己实现一个copy,你能实现吗?如果我让你实现引用计数的功能,你有思路吗?)。因而本文并 不适用于 专注业务层快速开发的同学,因为这里将贴有大量的源码。没有耐心的同学可以先收藏暂时回避一下,日后造火箭造飞机的时候再来。

核心问题

iOS开发者都知道OC里面的内存管理是通过对象的引用计数来管理的,或手动MRC,或自动ARC,有些操作可以让引用计数加1,有些可以减1,一旦一个对象的引用计数为0,就回收内存了。

可是,你仅仅知道这里就行了吗?指望你能造火箭造飞机的面试官可不这么想了,比如问你一句,一个对象的 引用计数本身 保存在哪里??不关注底层的面试者,这时候可能会懵逼。

研究方式

这篇文章不同于其它文章通过 clang编译 一个类文件以查看它的实现原理(笔者曾用clang编译分析Block的原理,传送门),而是直接通过下载runtime的源码来查看分析。

依据版本

苹果开源了runtime的代码,查看的方式既可以通过 在线网页版 预览,也可以 下载归档文件 到本地查看。本篇文件讨论的版本是 objc4-723。

目录

    1. 类与对象
    • 1.1 对象 -- Object
    • 1.2 对象 -- NSObject
    • 1.3 对象 -- objc_object
    • 1.4 类 -- objc_class
    • 1.5 NSObject,objc_object,objc_class 三者的关系
    1. 手动引用对引用计数的影响 -- retain操作
    • 2.1 两种对象:NSObject与Object的引用增加
    • 2.2 归根结底 -- NSObject对象的rootRetain()
    1. isa与Tagged Pointer
    • 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 引用计数的存储形式 -- 散列表
    1. 散列表
  • 4.1 增加引用计数 -- sidetable_retain()
  • 4.2 增加引用计数 -- sidetable_tryRetain()
  • 4.3 获取散列表 -- SideTable()
    1. 设置变量导致的引用计数变化 -- objc_retain操作
    • 5.1 情况1
    • 5.2 情况2
    • 5.3 objc_storeStrong导致的retain
    1. 新建对象(分配内存与初始化)导致的引用计数变化 -- alloc 和 init 操作
    • 6.1 分配内存 -- alloc
    • 6.2 初始化 -- init
    1. 获取引用计数
    1. 结论
    1. 拓展阅读

1. 类与对象

下载完工程,打开查看

module.modulemap 头文件描述文件

module ObjectiveC [system] [extern_c] {umbrella "."export *module * { export *}module NSObject {requires objcheader "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, !cplusplusheader "List.h"export *}module Object {requires objcheader "Object.h"export *}module Protocol {requires objcheader "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 对象 -- Object

Object.mm Object

#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_UNAVAILABLE
OBJC_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 对象 -- NSObject

NSObject.h NSObject

#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_CLASS
OBJC_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_INITIALIZERNS_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.h objc_object

struct objc_object {
private:isa_t isa;public:// ISA() assumes this is NOT a tagged pointer objectClass ISA();// getIsa() allows this to be a tagged pointer objectClass 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 objectsvoid 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 methodsid retain();void release();id autorelease();// Implementations of retain/release methodsid rootRetain();bool rootRelease();id rootAutorelease();bool rootTryRetain();bool rootReleaseShouldDealloc();uintptr_t rootRetainCount();// Implementation of dealloc methodsbool rootIsDeallocating();void clearDeallocating();void rootDealloc();private:void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);// Slow paths for inline controlid rootAutorelease2();bool overrelease_error();#if SUPPORT_NONPOINTER_ISA// Unified retain count manipulation for nonpointer isaid 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 isavoid 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 countbool 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 DEBUGbool sidetable_present();
#endif
};
复制代码

1.4 类 -- objc_class

关键信息

  • isa: 继承于objc_object
  • superclass: 指向自己父类的指针
  • cache: 方法缓存
  • bits: 它是一个class_data_bits_t类型的指针。作为本类的实例方法链表。

注意区别

这里的bitsclass_data_bits_t类型的,上一节objc_object的isa_t类型数据中也有一个uintptr_t类型的bits,但是这是两种结构。

由此可见,objc_class 继承于 objc_object, 所以也是包含一个isa的类。在OC里,不只是对象的实例包含一个isa,这个对象的类本身也有这么一个isa,类本身也是一个对象。

objc-runtime-new.h objc_class

struct objc_class : objc_object {// Class ISA;Class superclass;cache_t cache;             // formerly cache pointer and vtableclass_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flagsclass_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 overlapvoid 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
#elsebool instancesHaveAssociatedObjects() {// this may be an unrealized future class in the CF-bridged caseassert(isFuture()  ||  isRealized());return data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS;}void setInstancesHaveAssociatedObjects() {// this may be an unrealized future class in the CF-bridged caseassert(isFuture()  ||  isRealized());setInfo(RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS);}
#endifbool 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 metaclassClass 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 hereassert(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_class

NSObject有一个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的retain

NSObject.mm retain

+ (id)retain {return (id)self;
}// Replaced by ObjectAlloc
- (id)retain {return ((id)self)->rootRetain();
}
复制代码
② Object的retain

Object.mm retain

+(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_objectrootRetain()

2.2 归根结底 -- NSObject对象的rootRetain()

objc4/objc4-723/runtime/objc-object.h objc_object::rootRetain()

ALWAYS_INLINE id
objc_object::rootRetain()
{return rootRetain(false, false);
}
复制代码

objc4/objc4-723/runtime/objc-object.h objc_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 overridesif (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++ overflowedif (!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 Pointer

3.1 NSObject的唯一成员变量 -- isa

NSObject.h NSObject的isa

OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ROOT_CLASS
OBJC_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.h Class

typedef struct objc_class *Class;
typedef struct objc_object *id;
复制代码

其中,objc_object类内部结构:

其中,私有的成员数据isa为isa_t类型的联合体:

objc-private.h isa_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 0x000001a000000001ULLstruct {uintptr_t nonpointer        : 1;uintptr_t has_assoc         : 1;uintptr_t has_cxx_dtor      : 1;uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000uintptr_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 0x001d800000000001ULLstruct {uintptr_t nonpointer        : 1;uintptr_t has_assoc         : 1;uintptr_t has_cxx_dtor      : 1;uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000uintptr_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 0x001C0001struct {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会是如何初始化的,其实,这是一种与构造函数并列的初始化办法 -- 初始化列表。关于初始化列表的定义,截取百度百科的一段话:

所以,再回过来看bitsbitsisa_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_INLINE
uintptr_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_rc bits.nonpointer bits.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_ONERC_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.h objc_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.h SUPPORT_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.mm objc_object::sidetable_retain()

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISAassert(!isa.nonpointer);
#endifSideTable& 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.mm objc_object::sidetable_tryRetain()

bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISAassert(!isa.nonpointer);
#endifSideTable& 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.mm SideTable

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以及HaveOldHaveNew的定义为:

// 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.h DenseMap/DenseMapBase

DenseMapBase

DenseMap

5. 设置变量导致的引用计数变化 -- objc_retain操作

5.1 情况1 -- strong

runtime.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 void
object_setIvarWithStrongDefault(id _Nullable obj, Ivar _Nonnull ivar,id _Nullable value) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0);
复制代码

objc-class.mm object_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.mm objc_storeStrong

void
objc_storeStrong(id *location, id obj)
{id prev = *location;if (obj == prev) {return;}objc_retain(obj);*location = obj;objc_release(prev);
}
复制代码

5.2 情况2 -- weak

objc-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.mm object_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.mm objc_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*/
id
objc_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.mm objc_storeStrong(id *location, id obj)

void
objc_storeStrong(id *location, id obj)
{id prev = *location;if (obj == prev) {return;}objc_retain(obj);*location = obj;objc_release(prev);
}
复制代码

NSObject.mm objc_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)))
id
objc_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.h objc_object::retain()

// Equivalent to calling [this retain], with shortcuts if there is no override
inline id
objc_object::retain()
{assert(!isTaggedPointer());if (fastpath(!ISA()->hasCustomRR())) {return rootRetain();}return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
复制代码

objc-object.h objc_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 id
callAlloc(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 summaryif (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_ISAassert(cls->classArrayIndex() > 0);newisa.bits = ISA_INDEX_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUEnewisa.has_cxx_dtor = hasCxxDtor;newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#elsenewisa.bits = ISA_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUEnewisa.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 instantiationisa = 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 performancebool 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_ISAassert(cls->classArrayIndex() > 0);newisa.bits = ISA_INDEX_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUEnewisa.has_cxx_dtor = hasCxxDtor;newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#elsenewisa.bits = ISA_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUEnewisa.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 instantiationisa = 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.mm retainCount

- (NSUInteger)retainCount {return ((id)self)->rootRetainCount();
}
复制代码

objc-object.h objc_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中)。

  • 情况0 -- TaggedPointer

直接返回isa值本身。

  • 情况1 -- 非TaggedPointer,且开启了指针优化,且存储在extra_rc

objc-os.h LoadExclusive(uintptr_t *src)

static ALWAYS_INLINE
uintptr_t
LoadExclusive(uintptr_t *src)
{return *src;
}
复制代码
  • 情况2 -- 非TaggedPointer,且没有开启指针优化,且存储在散列表中

NSObject.mm objc_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 来判断是否引用计数值有可能溢出。

  • 情况3 -- 默认值

NSObject.mm objc_object::sidetable_retainCount()

uintptr_t
objc_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 toorefcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;}table.unlock();return refcnt_result;
}
复制代码

8. 结论

  1. 如果有些对象支持使用 TaggedPointer
  • 苹果会直接将对象的指针值作为引用计数返回。
  1. 如果另外一些对象不支持使用 TaggedPointer
  • 如果当前设备是 64 位环境并且使用 Objective-C 2.0,那么会使用对象的 isa 指针一部分空间bits.extra_rc)来存储它的引用计数;
  • 否则 Runtime 会使用一张 散列表SideTables())来管理引用计数。

9. 拓展阅读

weak表

https://www.jianshu.com/p/13c4fb1cedea

苹果iOS系统源码思考:对象的引用计数存储在哪里?--从runtime源码得到的启示...相关推荐

  1. 黑客30秒攻破苹果iOS系统夺得大赛冠军

    本文讲的是 :  黑客30秒攻破苹果iOS系统夺得大赛冠军  , [IT168 评论]神不知鬼不觉,一部装有最新iOS7系统的iPhone5手机中的照片账号密码等被王琦轻而易举地盗取了.在今年11月初 ...

  2. 苹果iOS系统下的推送机制及实现

    苹果iOS系统下的推送机制及实现 浏览:785次  出处信息 width="336" height="280" frameborder="0" ...

  3. 添加流程_艾多美手机商城苹果ios系统的主屏幕添加流程

    嗨,大家好,我是你们的老朋友莎莎老师,最近有很多朋友都在问莎莎老师,苹果手机的APP商城什么时候可以下载安装,因为目前苹果版的艾多美手机商城只有网页版,还没有出苹果ios系统版本的APP软件程序,所以 ...

  4. 安卓手机真的不行了,搞不定卡顿问题,只能抄袭苹果iOS系统,然而各怀鬼胎的它们终究画虎不成反类犬...

    近期安卓手机品牌开始推送新功能,更新了这个新功能后就有了类似苹果iOS系统的墓碑机制,这可以在一定程度上解决安卓手机的卡顿问题,如此做恰恰说明了国产手机不如苹果,难怪消费者越来越认可iPhone了. ...

  5. android与ios系统优缺点,安卓系统与苹果iOS系统的差别,谁更好用?为什么系统会卡顿?...

    在现在的智能手机圈,主要有两类操纵系统,一类是谷歌的安卓系统,目前最新的版本为Android P系统,该系统为开源系统,简单讲就是业态共享,比如谷歌手机的原生系统.华为的EMUI定制系统.小米的MIU ...

  6. 苹果系统和安卓系统的区别_安卓手机刷苹果ios系统,会发生什么

    如今安卓手机的硬件,直接逼近台式电脑,加入彪悍的安卓手机硬件搭配上目前顶级的苹果iOS系统,会不会走上智能手机的巅峰时刻?但是,安卓手机刷苹果iOS系统有可能吗?即使能刷,安卓手机和iOS会产生什么影 ...

  7. android ios 都会有好处吗,苹果IOS系统更新有什么好处?有必要每次都更新吗

    苹果IOS系统更新有什么好处?有必要每次都更新吗无论是苹果用户还是安卓用户,手机系统升级都是大家必须面对的一个话题.不过对于系统升级,用户的态度却并不一致.有些人每次都会第一时间更新,另一些人则非常讨 ...

  8. 信道检测手机软件 ios_微瑞“云检测”软件推出苹果iOS系统版啦!

    使用苹果iOS系统的客户再也不用愁没法下载使用微瑞"云检测"软件了,现在我们推出了iOS系统版本的"云检测"软件.兼容设备:iPhone.iPad.iPod t ...

  9. 华为鸿蒙系统与苹果IOS系统,华为鸿蒙与苹果iOS的两极之路

    原标题:华为鸿蒙与苹果iOS的两极之路 近段时间,关于华为自研操作系统鸿蒙的消息越来越多,而且此前余承东自信表示,鸿蒙最快今年底,最晚明年初就会推出市场,且近期在全球范围传出华为鸿蒙的版权注册等消息. ...

  10. 华为鸿蒙系统VS苹果iOS系统

    华为鸿蒙系统会超过苹果iOS系统吗?我们先来做一下相关对比: 性能: --鸿蒙系统是基于微内核的全场景分布式系统,具有分布架构,天生流畅,内核安全和生态共享的优势.微内核也使得系统整体功耗会变得很低. ...

最新文章

  1. 程序员真香!IT 业 2020 年平均工资最高
  2. linux nfs 配置_NFS服务器
  3. 利用Inotify和Rsync将webproject文件自己主动同步到多台应用server
  4. 中文课程!台大李宏毅机器学习公开课2019版上线
  5. 数据恢复错误卡住 linux,如何处理ORA-00376错误的恢复问题
  6. 点击显示底框颜色,默认显示第一个。
  7. ibatis.net:第六天,QueryForList
  8. Ndarry 拉伸为一个list
  9. Airflow 中文文档翻译和改进活动 | ApacheCN
  10. django 1.8 官方文档翻译: 2-1-4 Model 类参考
  11. testlink(以及服务器)问题定位思路
  12. 游戏UI设计(2.1)--窗口之父CXWnd的封装
  13. 2021-2025年中国电子台秤行业市场供需与战略研究报告
  14. 好用的局域网共享工具有哪些?win10系统如何设置?
  15. API接口文档范文-API接口文档示例
  16. maven环境变量配置?
  17. Jsp中getParameter、getParameterValues、getParameterNames和getParameterMap用法详解
  18. 计算机操作系统版本号怎么查看,Windows系统版本怎么看?2种查看windows版本的方法介绍...
  19. ioncube php encode,ioncube加密与解密 php代码
  20. adb启动程序命令:adb shell am start

热门文章

  1. Netty in action—ChannelHandler和ChannelPipeline
  2. C++Primer中文版(第5版)(顶级畅销书重磅升级 全面采用最新 C++ 11标准)
  3. Media Queries移动设备样式
  4. 2.5 HDFS体系架构
  5. iis中间件_.NET Core技术研究中间件的由来和使用
  6. oracle数字类型是什么格式,oracle字段类型NUMBER(38,3),括号中两个数字分别表示什么?...
  7. map中key值带‘-’转json_h5模型转savedModel + tf_serving部署采坑记录
  8. linux下架子网卡,Linux下新手装网卡指南
  9. 《Flutter 从0到1构建大前端应用》读后感—第4章【事件处理】
  10. Flutter进阶第5篇: 使用WebView组件flutter_inappbrowser加载远程web页面渲染新闻详情数据