研讨援用计数的贯彻,援用计数原理

作者: 编程应用  发布:2019-09-14

M福特ExplorerMurano 即为 “manual retain-release”,人为地插入 retain, release 等语句进行内部存储器管理。

全部内部存款和储蓄器管理模型皆以环绕对象具备权(object ownership)职业的:借使某些对象一贯被别的对象所全数,那么它就能够设有,反之则以。遵从以下准则以确认保障对指标拥有权处理的没有错:

引用计数如何存款和储蓄

  • 和煦生成的目的,本人独具(使用 allow/new/copy/mutableCopy 开头的主意生成并负有对象);

     id obj = [[NSObject alloc] init];
    
  • 非友好生成的目标,自身也能有所(发送 -retain 音讯持有对象);

     NSMutableArray *array = [NSMutableArray array]; [array retain];
    
  • 不再需求和煦有着的指标时,应该交出自身的目的全体权(发送 -release 音信放出对象全数权,或然发送 -autorelease 新闻延迟释放);

     id obj = [[NSObject alloc] init]; [obj release];
    
  • 敬敏不谢自由自个儿不有所的对象的全部权;

稍稍对象倘诺扶助选取TaggedPointer,苹果会直接将其指针值作为引用计数再次来到;要是当前设备是 62位蒙受何况使用 Objective-C 2.0,那么“一些”对象会使用其 isa 指针的一有个别空间来存储它的援引计数;不然 Runtime 会使用一张散列表来管理援引计数。

换做援用计数来驾驭,通过 +alloc/-init 等方式生成八个对象,那对指标被您所具有,它的援用计数(retain count)是 1。对它发送 -retain 新闻,援引计数加一,发送 -release 音信则减一,当其引用计数为 0 时,对象所占的内部存款和储蓄器被系统回收。

实际还会有一种状态会改造援用计数的存储计策,那便是是或不是利用垃圾回收(用UseGC属性判别),但这种早就弃用的事物就不用管了,而且开始化垃圾回收机制的 void gc_init(BOOL wantsGC) 方法一向被传播 NO。

上边 objc-runtime 的代码来源于 RetVal 的 Github。谢谢作者的修补。

TaggedPointer

援引计数的囤积

要驾驭引用计数是何等存储与操作,除了通晓与计数相关的数据结构之外,还要了然 isa 指针的囤积优化(non-pointer isa)和 tagged pointer 这两项手艺,这么些知识在下文中对 -retainCount 等达成的知晓有赞助:

isa 指针平时用来针对对象所属的类,可是在 64 位的遭受下,isa 还是能够积攒一些附加的消息,毕竟 62个比特仅仅存款和储蓄叁个类的地址的确有个别浪费。那么,先瞄一下 isabits 的各样指针变量(以x86_64阳台的为例)

 // 变量意义来源于:http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html // 其意义可能已经有些改变,这里列出来仅供参考。 struct { uintptr_t indexed : 1; // 0 表示纯粹的 isa 指针,1 表示 non-pointer isa uintptr_t has_assoc : 1; // 是否有 associated object,没有的话 dealloc 会更快 uintptr_t has_cxx_dtor : 1; // 是否有 C++/ARC 的析构函数,没有的话 dealloc 会更快 uintptr_t shiftcls : 44; // 指向类的指针 uintptr_t magic : 6; // 0x02 用于在调试时区分未初始化的垃圾数据和已经初始化的对象 uintptr_t weakly_referenced : 1; // 是否被 weak 变量引用过,没有的话 dealloc 会更快 uintptr_t deallocating : 1; // 是否正在 deallocating uintptr_t has_sidetable_rc : 1; // 引用计数值是否太大,以至于无法存在 isa 中,需要 SideTable 辅助存储 uintptr_t extra_rc : 8; /* 额外的引用计数值。对象实例化时的本身的引用计数值为 1,而该值为 0。 向该对象发送 retain 消息后,extra_rc 增加 1。当 extra_rc 太大时,则需要 SideTable 辅助计数。*/ #define RC_ONE (1ULL<<56) // bits + RC_ONE 等于 extra_rc + 1 #define RC_HALF (1ULL<<7) };

同样的,tagged pointer 也是 陆21位境遇下一种采用指针优化存储工夫,用来存款和储蓄一些小指标(实际上只是栈上的一段数据,可能算不上是三个Objective-C 对象),裁减 malloc/free 在堆上的费用。在 objc_internal.h 中能看到以下的类别协理 tagged pointer:

 OBJC_TAG_NSAtom = 0, OBJC_TAG_1 = 1, OBJC_TAG_NSString = 2, OBJC_TAG_NSNumber = 3, OBJC_TAG_NSIndexPath = 4, OBJC_TAG_NSManagedObjectID = 5, OBJC_TAG_NSDate = 6, OBJC_TAG_7 = 7

对此三个 tagged pointer,其内部存款和储蓄器布局如下:

MSB 60 bit 3 bit 1 bit LSB
< payload tag index,即上面所列出来的类型 1 表示 tagged pointer 对象,0 表示普通对象 >

您能够写这么一段代码去印证目的是或不是为 tagged pointer 对象,以及检查它的连串:

 NSNumber *obj = @1; uintptr_t ptr = 0xF; uintptr_t result = ((uintptr_t)obj & ptr); NSLog(@"obj's pointer: %p", obj); NSLog(@"isTaggedPointer: %lu", result & 0x1); NSLog(@"TaggedPointerType: %lu", (result >> 1 & 0x7));

有人会试 NSString *obj = @"Hello!";,想看看它是或不是 tagged pointer。答案是或不是认的。str 指向的是 TEXT 段的叁个常量指针,合理的实验艺术是 NSString *obj = [NSString stringWithFormat:@"Hello!"];

上边的座谈中,大家引出了一个 SideTable 那样的东西。当一个对象的援用计数比非常的大时(extra_rc 赶过所能表示的限定),需求它帮忙记录对象的引用计数。此时实际上的计数值:retainCount = 1 + extra_rc + sideTable.refcnts[obj] 中的值。在 NSObject.mm 中的它,看起来大约是如此的:

 typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap; struct SideTable { spinlock_t slock; // 自旋锁,保证对 sideTable 操作的原子性 RefcountMap refcnts; // 存储引用计数的哈希表 weak_table_t weak_table; // weak 表,这个放到 ARC 再讨论 ... }

SideTable 将自旋锁、援用计数表和贰个 weak 表封装到了同步。当须要基于指标读取 SideTable 时,会从八个名称为 SideTableBuf 的静态数组中找到呼应的 SideTable:

 // 出于某些原因以下面这种方式分配 4096 个字节,即为 64 个 sideTable 的大小 alignas(sizeof(StripedMap<SideTable>)) static uint8_t SideTableBuf[sizeof(StripedMap<SideTable>)]; // StripedMap 重载了 [] 运算符,具体实现可以查看源码,这里不再赘叙 SideTable& table = SideTables()[this];

你能够领略 SideTableBuf 有 64 个格子,各类格子里面都有个 SideTable。各种对象指针能够经过测算映射到里面包车型大巴多个格子中,然后再从格子中读取 refcnts 去找到本身的附加的引用计数。

值得注意的是积累援引计数的哈希表 RefcountMap refcnts,键是将指标指针包裹了一层的 DisguisedPtr,值是对象额外的引用计数值再左移两位,所以大家读取这些值的时候要再右移两位。

剖断当前指标是不是在应用 TaggedPointer 是看标记位是不是为 1 :

援用计数的操作

上边扯完了援用计数相关的数据结构,那么接下去深入分析 -retainCount,-retain,-release 在 objc-runtime 源码中的达成。有两点供给小心的:

  1. objc-object.h 文件中对此这么些方法背后函数的贯彻有两套,通过标准编写翻译的宏 SUPPORT_NONPOINTER_ISA 区分,笔者首先次看的时候就搞蒙了;
  2. 这几个方法方面皆有 // Replaced by ObjectAlloc 那样的一行注释,应该是说那么些措施被 Core Foundation 的兑现给替换了,所以下边包车型客车剖释大概与实际的逻辑不符。

下边包车型大巴分析以 SUPPORT_NONPOINTER_ISA 为确实代码为例子。

-retainCount 的兑现最后到达下面那几个函数上:

inline uintptr_t objc_object::rootRetainCount(){ assert; if (isTaggedPointer return (uintptr_t)this; sidetable_lock(); isa_t bits = LoadExclusive(&isa.bits); if (bits.indexed) { 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();}

在调用 objc_object::rootRetainCount 时,借使当前目标使用的是 tagged pointer,那么直接重临本身的指针值。因为考究存在于栈上的变量的引用计数大约从不什么样意义,它的生命周期由栈来管理。接着,假诺目的使用了 non-pointer isa,而且没有选用 SideTable 帮忙计数,那么再次回到对象实例化后的计数值 1 丰裕额外被 retain 的次数 extra_rc(objc_object::sidetable_getExtraRC_nolock 这么些函数完成就不贴了,同上边包车型客车大都)。

对于使用纯粹的 isa 指针的指标,会调到上面那么些函数,从 SideTable 中得到计数表,通过 this 指针获得迭代器并访问引用计数值:

uintptr_tobjc_object::sidetable_retainCount(){ SideTable& table = SideTables()[this]; size_t refcnt_result = 1; table.lock(); RefcountMap::iterator it = table.refcnts.find; 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;}

通晓位置得到引用计数的函数完毕之后,对于 retain 和 release 的落到实处就轻便通晓了。但由于 id objc_object::rootRetain(bool, bool)bool objc_object::rootRelease(bool, bool) 的贯彻都比较长,贴在此间有凑字数的困惑,何况动用了比较多 goto 和递归,阅读起来也不太实惠。

因此上边仅对一些最重要的逻辑举行分析:

  • id objc_object::rootRetain(bool, bool) 中,如果目的是 tagged pointer object,那么直接回到该对象;对于普通的对象,若是其 isa 指针不用于优化存储,那么通过 goto unindexed; 跳到 unindexed 标签所标志的代码块,对 SideTable 的计数表进行操作;不然步入 do...while() 循环里面,通过上边包车型的士代码对 bits.extra 操作:

     newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
    

假如溢出,对象启用 SideTable 帮助计数,extra_rc 的值为最大值的八分之四,而将另五成拷贝到对应的 SideTable 中的计数表中。

 // 每次溢出,transcribeToSideTable 为真 if (transcribeToSideTable) { sidetable_addExtraRC_nolock;}
  • bool objc_object::rootRelease(bool, bool) 中,对于 tagged pointer object 照旧尚未任何操作,直接重返。对于 goto unindexed; 跳转的那一块代码,调用 sidetable_release() 函数操作计数表。而在

     newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
    

之后,如果 extra_rc 出现下溢,那么要跳转到 underflow 那一块代码举办操作,从指标的协助计数表中把原本加到里面包车型客车数“要”回来:

// Try to remove some retain counts from the side table. size_t borrowed = sidetable_subExtraRC_nolock;

倘若“要”回来的数字高于零,那么将设置 extra_rc 并返回:

// Side table retain count decreased.// Try to add them to the inline count.newisa.extra_rc = borrowed - 1; // redo the original decrement too

要不间接往下推行,向目的发送 -dealloc 消息:

if (performDealloc) { (objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);}

Advanced Memory Management Programming Guide

Objective-C 援引计数原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#if SUPPORT_MSB_TAGGED_POINTERS
#   define TAG_MASK (1ULL<<63)
#else
#   define TAG_MASK 1
 
inline bool 
objc_object::isTaggedPointer() 
{
#if SUPPORT_TAGGED_POINTERS
    return ((uintptr_t)this & TAG_MASK);
#else
    return false;
#endif
}

id 其实正是 objc_object * 的简写(typedef struct objc_object *id;),它的 isTaggedPointer() 方法平日会在操作引用计数时用到,因为那决定了蕴藏援用计数的战术。

isa 指针(NONPOINTER_ISA)

用 64 bit 存款和储蓄八个内部存款和储蓄器地址明显是种浪费,毕竟非常少有那么大内存的配备。于是能够优化存款和储蓄方案,用部分附加空间存款和储蓄别的剧情。isa 指针第壹位为 1 即表示使用优化的 isa 指针,这里列出不一致架构下的 陆拾人意况中 isa 指针结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
     
#if SUPPORT_NONPOINTER_ISA
# if __arm64__
#   define ISA_MASK        0x00000001fffffff8ULL
#   define ISA_MAGIC_MASK  0x000003fe00000001ULL
#   define ISA_MAGIC_VALUE 0x000001a400000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 30; // MACH_VM_MAX_ADDRESS 0x1a0000000
        uintptr_t magic             : 9;
        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  0x0000000000000001ULL
#   define ISA_MAGIC_VALUE 0x0000000000000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 14;
#       define RC_ONE   (1ULL<<50)
#       define RC_HALF  (1ULL<<13)
    };
     
# else
    // Available bits in isa field are architecture-specific.
#   error unknown architecture
# endif
 
// SUPPORT_NONPOINTER_ISA
#endif
};

SUPPORT_NONPOINTER_ISA 用于标识是还是不是支持优化的 isa 指针,其字面含义意思是 isa 的故事情节不再是类的指针了,而是蕴含了更加的多消息,例如引用计数,析构状态,被其他weak 变量援用景况。剖断方式也是依附设备档期的顺序:

1
2
3
4
5
6
// Define SUPPORT_NONPOINTER_ISA=1 to enable extra data in the isa field.
#if !__LP64__  ||  TARGET_OS_WIN32  ||  TARGET_IPHONE_SIMULATOR  ||  __x86_64__
#   define SUPPORT_NONPOINTER_ISA 0
#else
#   define SUPPORT_NONPOINTER_ISA 1
#endif

综合看来近日唯有 arm64 架构的装置辅助,下边列出了 isa 指针中变量对应的意思:

图片 1

在 64 位景况下,优化的 isa 指针并非就必将会储存援引计数,毕竟用 19bit (iOS 系统)保存援用计数不料定够。需求小心的是那 十七位保存的是援用计数的值减一。has_sidetable_rc 的值假诺为 1,那么引用计数会积攒在一个叫 SideTable 的类的性质中,前面会详细讲。

散列表

散列表来存款和储蓄援引计数具体是用 DenseMap 类来兑现,那些类中蕴藏众多映射实例到其引述计数的键值对,并扶助用 DenseMapIterator 迭代器神速找出遍历这么些键值对。接着说键值对的格式:键的类别为 DisguisedPtr<objc_object>,DisguisedPtr 类是对 objc_object * 指针及其一些操作举行的卷入,指标正是为着让它给人看起来不会有内部存储器败露的标准(真是心机裱),其剧情能够精通为对象的内部存款和储蓄器地址;值的等级次序为 __darwin_size_t,在 darwin 内核一般同样 unsigned long。其实这里保留的值也是极其援引计数减一。使用散列表保存引用计数的规划很好,尽管出现故障产生对象的内部存款和储蓄器块损坏,只要引用计数表未有被毁损,还能够追溯找到内部存款和储蓄器块的职位。

在此以前说援用计数表是个散列表,这里差十分少说下散列的法门。有个特意管理键的 DenseMapInfo 结构体,它针对 DisguisedPtr 做了些优化相称键值速度的措施:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct DenseMapInfo<disguisedptr> {
  static inline DisguisedPtr getEmptyKey() {
    return DisguisedPtr((T*)(uintptr_t)-1);
  }
  static inline DisguisedPtr getTombstoneKey() {
    return DisguisedPtr((T*)(uintptr_t)-2);
  }
  static unsigned getHashValue(const T *PtrVal) {
      return ptr_hash((uintptr_t)PtrVal);
  }
  static bool isEqual(const DisguisedPtr &LHS, const DisguisedPtr &RHS) {
      return LHS == RHS; 
  }
};</disguisedptr>

当然这里的哈希算法会依据是还是不是为 陆十三位平台来进展优化,算法具体细节就不追究了,小编总感觉苹果在此间的 hardcode 是无论写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#if __LP64__
static inline uint32_t ptr_hash(uint64_t key)
{
    key ^= key >> 4;
    key *= 0x8a970be7488fda55;
    key ^= __builtin_bswap64(key);
    return (uint32_t)key;
}
#else
static inline uint32_t ptr_hash(uint32_t key)
{
    key ^= key >> 4;
    key *= 0x5052acdb;
    key ^= __builtin_bswap32(key);
    return key;
}
#endif

再介绍下 SideTable 那么些类,它用来处理援引计数表和 weak 表,并运用 spinlock_lock 自旋锁来堤防操作表结构时只怕的竞态条件。它用一个 64*128 大小的 uint8_t 静态数组作为 buffer 来保存全部的 SideTable 实例。并提供四个国有属性:

1
2
3
spinlock_t slock;//保证原子操作的自选锁
RefcountMap refcnts;//保存引用计数的散列表
weak_table_t weak_table;//保存 weak 引用的全局散列表

还提供了三个厂子方法,用于依据指标的地址在 buffer 中找出对应的 SideTable 实例:

1
static SideTable *tableForPointer(const void *p)

weak 表的职能是在对象进行 dealloc 的时候将享有指向该指标的 weak 指针的值设为 nil,幸免悬空指针。那是 weak 表的组织:

1
2
3
4
5
6
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

苹果选择一个大局的 weak 表来保存全数的 weak 援用。并将目的作为键,weak_entry_t 作为值。weak_entry_t 中保留了全体指向该对象的 weak 指针。

获得引用计数

在非 ARC 意况能够选择 retainCount 方法赢得有个别对象的引用计数,其会调用 objc_object 的 rootRetainCount() 方法:

1
2
3
- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}

在 ARC 时期除了利用 Core Foundation 库的 CFGetRetainCount() 方法,也足以使用 Runtime 的 _objc_rootRetainCount(id obj) 方法来获得援用计数,此时亟待引进头文件。那个函数也是调用 objc_object 的 rootRetainCount() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
inline uintptr_t 
objc_object::rootRetainCount()
{
    assert(!UseGC);
    if (isTaggedPointer()) return (uintptr_t)this;
     
    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    if (bits.indexed) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }
    sidetable_unlock();
    return sidetable_retainCount();
}

rootRetainCount() 方法对援用计数存款和储蓄逻辑进行了判别,因为 TaggedPointer 前边已经说过了,能够一贯拿走引用计数;64 位遭遇优化的 isa 指针后面也说过了,所以这里的基本点是在 TaggedPointer 不恐怕利用时调用的 sidetable_retainCount() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable *table = SideTable::tableForPointer(this);
     
    size_t refcnt_result = 1;
     
    spinlock_lock(&table->slock);
    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;
    }
    spinlock_unlock(&table->slock);
    return refcnt_result;
}

sidetable_retainCount() 方法的逻辑正是先从 SideTable 的静态方法获取当前实例对应的 SideTable 对象,其 refcnts 属性正是事先说的囤积引用计数的散列表,这里将其种类简写为 RefcountMap:

1
typedef objc::DenseMap<disguisedptr,size_t,true> RefcountMap;</disguisedptr,size_t,true>

接下来在引用计数表中用迭代器查找当前实例对应的键值对,获取引用计数值,并在此基础上 +1 并将结果重临。这也正是干吗事先说援用计数表存款和储蓄的值为实际引用计数减一。

急需留神的是干吗这里把键值对的值做了向右移位操作(it->second >> SIDE_TABLE_RC_SHIFT):

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifdef __LP64__
#   define WORD_BITS 64
#else
#   define WORD_BITS 32
#endif
 
// 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)RefcountMap

可以看出值的首先个 bit 表示该目的是或不是有过 weak 对象,若无,在析构释放内部存款和储蓄器时可以越来越快;第一个 bit 表示该指标是或不是正在析构。从第八个 bit 先河才是积存援引计数数值的地方。所以这边要做向右移两位的操作,而对援引计数的 +1 和 -1 能够动用 SIDE_TABLE_RC_ONE,还足以用 SIDE_TABLE_RC_PINNED 来判别是还是不是引用计数值有比异常的大几率溢出。

本来不可见统统信任这一个 _objc_rootRetainCount(id obj) 函数,对于已放出的对象以及不精确的对象地址,有的时候也回到 “1”。它所重回的引用计数只是有些给定时期点上的值,该方式未有思量到系统稍后会把机动释放吃池清空,因而不会将再三再四的放出操作从再次来到值里减去。clang 会尽恐怕把 NSString 完成成单例对象,其援用计数会异常的大。假如使用了 TaggedPointer,NSNumber 的剧情有相当的大希望就不再放置堆中,而是一贯写在平阔的60位栈指针值里。其看起来和实在的 NSNumber 对象一样,只是利用 TaggedPointer 优化了下,但其援用计数只怕不确切。

修改引用计数

  • retain 和 release

在非 ARC 情况下得以采取 retain 和 release 方法对援用计数进行加一减一操作,它们各自调用了 _objc_rootRetain(id obj) 和 _objc_rootRelease(id obj) 函数,可是后两个在 ARC 遭遇下也可选拔。最后那四个函数又会调用 objc_object 的底下多少个议程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
inline id 
objc_object::rootRetain()
{
    assert(!UseGC);
    if (isTaggedPointer()) return (id)this;
    return sidetable_retain();
}
 
inline bool 
objc_object::rootRelease()
{
    assert(!UseGC);
    if (isTaggedPointer()) return false;
    return sidetable_release(true);
}

如此这般的兑现跟获取援用计数类似,先是看是还是不是帮忙TaggedPointer(究竟数据存在栈指针并非堆中,栈的治本当然就是半自动的),不然去操作 SideTable 中的 refcnts 属性,那与收获引用计数战略类似。sidetable_retain() 将 引用计数加一后回到对象,sidetable_release() 重临是还是不是要执行 dealloc 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
bool 
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.indexed);
#endif
    SideTable *table = SideTable::tableForPointer(this);
     
    bool do_dealloc = false;
     
    if (spinlock_trylock(&table->slock)) {
        RefcountMap::iterator it = table->refcnts.find(this);
        if (it == table->refcnts.end()) {
            do_dealloc = true;
            table->refcnts[this] = SIDE_TABLE_DEALLOCATING;
        else if (it->second < SIDE_TABLE_DEALLOCATING) {
            // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
            do_dealloc = true;
            it->second |= SIDE_TABLE_DEALLOCATING;
        else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
            it->second -= SIDE_TABLE_RC_ONE;
        }
        spinlock_unlock(&table->slock);
        if (do_dealloc  &&  performDealloc) {
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
        }
        return do_dealloc;
    }
     
    return sidetable_release_slow(table, performDealloc);
}

阅览这里通晓干什么在仓储引用计数时总是真正的引用计数值减一了吧。因为 release 本来是要将援引计数减一,所以存款和储蓄引用计数时先预留了个“一”,在减一事先先看看存款和储蓄的援引计数值是不是为 0 (it->second < SIDE_TABLE_DEALLOCATING),假使是,那就将对象标志为“正在析构”(it->second |= SIDE_TABLE_DEALLOCATING),并发送 dealloc 新闻,重返YES;否则就将援引计数减一(it->second -= SIDE_TABLE_RC_ONE)。那样做防止了负数的爆发。

除此而外,Core Foundation 库中也提供了增减引用计数的办法。比如在采用Toll-Free Bridge 转变时采纳的 CFBridgingRetain 和 CFBridgingRelease 方法,其本质是使用 __bridge_retained 和 __bridge_transfer 告诉编写翻译器此处须要哪些修改援用计数:

1
2
3
4
5
6
7
NS_INLINE CF_RETURNS_RETAINED CFTypeRef __nullable CFBridgingRetain(id __nullable X) {
    return (__bridge_retained CFTypeRef)X;
}
 
NS_INLINE id __nullable CFBridgingRelease(CFTypeRef CF_CONSUMED __nullable X) {
    return (__bridge_transfer id)X;
}

除此以外 Objective-C 相当多落成是靠 Core Foundation Runtime 来促成, Objective-C Runtime 源码中某个地点鲜明表明:”// Replaced by CF“,那正是意味说那块任务被 Core Foundation 库接管了。当然 Core Foundation 有一部分是开源的。还应该有一部分 Objective-C Runtime 函数的完成被诸如 ObjectAlloc 和 NSZombie 这样的内部存储器管理工科具所代表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
 
// Replaced by CF (throws an NSException)
+ (id)init {
    return (id)self;
}
 
// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}
alloc, new, copy, mutableCopy

基于编写翻译器的约定,那以那八个单词开头的法子都会使援引计数加一。而 new 也就是调用 alloc 后再调用 init:

1
2
3
4
5
6
7
8
9
10
11
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/true/*allocWithZone*/);
}
+ (id)alloc {
    return _objc_rootAlloc(self);
}
+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

能够看到 alloc 和 new 最终都会调用 callAlloc,暗中同意使用 Objective-C 2.0 且忽视垃圾回收和 NSZone,那么继续的调用顺序依次是为:

1
2
3
class_createInstance()
_class_createInstanceFromZone()
calloc()

calloc() 函数相比较于 malloc() 函数的长处是它将分配的内部存款和储蓄器区域早先化为0,也正是 malloc() 后再用 memset() 方法最早化一次。

copy 和 mutableCopy 都以依照 NSCopying 和 NSMutableCopying 方法约定,分别调用种种温馨完毕的 copyWithZone: 和 mutableCopyWithZone: 方法。这一个方式无论达成方式是深拷贝依然浅拷贝,都会追加援用计数。(某个类的宗旨是懒拷贝,只扩充援引计数但并不真的正片,等对象内容发生变化时再拷贝一份出来,举例NSArray)。

在 retain 方法加符号断点会开掘 alloc, new, copy, mutableCopy 这个方法都会通过 Core Foundation 的 CFBasicHashAddValue() 函数来调用 retain 方法。其实 CF 有个修改和查看引用计数的入口函数 __CFDoExternRefOperation,在 CFRuntime.c 文件中完毕。

  • autorelease

本想贴上一批 Runtime 中关于机关释放池的源码然后说上一大堆,然后发现了太阳星君的那篇黑幕背后的Autorelease把自身想说的都说了,把本人不明白的也说了,简直太屌了。

实质上通过看源码能够领悟大多细节,没事点进去各类宏定义往往会拿走欢欣:哇,原本是这么回事,XX 正是 XX 之类。。。

Reference

本文由今晚开什么码发布于编程应用,转载请注明出处:研讨援用计数的贯彻,援用计数原理

关键词:

上一篇:相互兼容,IOS开发各种加解密
下一篇:没有了