[关闭]
@qidiandasheng 2022-08-07T13:39:48.000000Z 字数 10287 阅读 761

iOS底层原理:创建和释放源码分析(😁)

iOS实战


创建alloc&new&retain

alloc调用过程

我们下载源码编译查看(objc4-781),alloc调用顺序如下,而 new 相当于调用 alloc 后再调用 init

  1. //第一步:
  2. + (id)alloc {
  3. return _objc_rootAlloc(self);
  4. }
  5. //第二步:
  6. id
  7. _objc_rootAlloc(Class cls)
  8. {
  9. return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
  10. }
  11. //第三步:
  12. static ALWAYS_INLINE id
  13. callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
  14. {
  15. #if __OBJC2__
  16. if (slowpath(checkNil && !cls)) return nil;
  17. if (fastpath(!cls->ISA()->hasCustomAWZ())) {
  18. return _objc_rootAllocWithZone(cls, nil);
  19. }
  20. #endif
  21. // No shortcuts available.
  22. if (allocWithZone) {
  23. return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
  24. }
  25. return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
  26. }
  27. //第四步:
  28. id
  29. _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
  30. {
  31. // allocWithZone under __OBJC2__ ignores the zone parameter
  32. return _class_createInstanceFromZone(cls, 0, nil,
  33. OBJECT_CONSTRUCT_CALL_BADALLOC);
  34. }

第五步:

  1. static ALWAYS_INLINE id
  2. _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
  3. int construct_flags = OBJECT_CONSTRUCT_NONE,
  4. bool cxxConstruct = true,
  5. size_t *outAllocatedSize = nil)
  6. {
  7. ASSERT(cls->isRealized());
  8. // Read class's info bits all at once for performance
  9. bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
  10. bool hasCxxDtor = cls->hasCxxDtor();
  11. bool fast = cls->canAllocNonpointer();
  12. size_t size;
  13. //要开辟多少内存
  14. size = cls->instanceSize(extraBytes);
  15. if (outAllocatedSize) *outAllocatedSize = size;
  16. id obj;
  17. if (zone) {
  18. obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
  19. } else {
  20. //怎么去申请内存
  21. obj = (id)calloc(1, size);
  22. }
  23. if (slowpath(!obj)) {
  24. if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
  25. return _objc_callBadAllocHandler(cls);
  26. }
  27. return nil;
  28. }
  29. // 3:将当前的类和指针地址绑定在一起
  30. if (!zone && fast) {
  31. obj->initInstanceIsa(cls, hasCxxDtor);
  32. } else {
  33. // Use raw pointer isa on the assumption that they might be
  34. // doing something weird with the zone or RR.
  35. obj->initIsa(cls);
  36. }
  37. if (fastpath(!hasCxxCtor)) {
  38. return obj;
  39. }
  40. construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
  41. return object_cxxConstructFromClass(obj, cls, construct_flags);
  42. }

最重要的就是第五步:

  1. inline void
  2. objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
  3. {
  4. ASSERT(!cls->instancesRequireRawIsa());
  5. ASSERT(hasCxxDtor == cls->hasCxxDtor());
  6. initIsa(cls, true, hasCxxDtor);
  7. }
  8. objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
  9. {
  10. ASSERT(!isTaggedPointer());
  11. if (!nonpointer) {
  12. isa = isa_t((uintptr_t)cls);
  13. } else {
  14. ASSERT(!DisableNonpointerIsa);
  15. ASSERT(!cls->instancesRequireRawIsa());
  16. isa_t newisa(0);
  17. #if SUPPORT_INDEXED_ISA
  18. ASSERT(cls->classArrayIndex() > 0);
  19. newisa.bits = ISA_INDEX_MAGIC_VALUE;
  20. newisa.has_cxx_dtor = hasCxxDtor;
  21. newisa.indexcls = (uintptr_t)cls->classArrayIndex();
  22. #else
  23. newisa.bits = ISA_MAGIC_VALUE;
  24. newisa.has_cxx_dtor = hasCxxDtor;
  25. newisa.shiftcls = (uintptr_t)cls >> 3;
  26. #endif
  27. isa = newisa;
  28. }
  29. }

retain过程

我们知道通过retain会使引用计数加一,那我们接下去看一看retain的调用过程:

  1. //第一步:
  2. - (id)retain {
  3. return _objc_rootRetain(self);
  4. }
  5. //第二步:
  6. objc_object::rootRetain()
  7. {
  8. return rootRetain(false, false);
  9. }
  10. objc_object::rootRetain(bool tryRetain, bool handleOverflow)
  11. {
  12. if (isTaggedPointer()) return (id)this;
  13. bool sideTableLocked = false;
  14. bool transcribeToSideTable = false;
  15. isa_t oldisa;
  16. isa_t newisa;
  17. do {
  18. transcribeToSideTable = false;
  19. oldisa = LoadExclusive(&isa.bits);
  20. newisa = oldisa;
  21. .
  22. .
  23. .
  24. uintptr_t carry;
  25. // extra_rc加1
  26. newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
  27. // carry为1表示溢出
  28. if (slowpath(carry)) {
  29. // newisa.extra_rc++ overflowed
  30. if (!handleOverflow) {
  31. ClearExclusive(&isa.bits);
  32. return rootRetain_overflow(tryRetain);
  33. }
  34. if (!tryRetain && !sideTableLocked) sidetable_lock();
  35. sideTableLocked = true;
  36. //转存到SideTable位True
  37. transcribeToSideTable = true;
  38. //更新值为RC_HALF,RC_HALF=(1ULL<<7)=0b10000000
  39. newisa.extra_rc = RC_HALF;
  40. //isa中存储sidetable中有引用计数
  41. newisa.has_sidetable_rc = true;
  42. }
  43. } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
  44. if (slowpath(transcribeToSideTable)) {
  45. //保存一半的引用计数到sidetable
  46. sidetable_addExtraRC_nolock(RC_HALF);
  47. }
  48. if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
  49. return (id)this;
  50. }

我们可以看到newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);其实就是把isa公用体里extra_rc的值加一。当extra_rc值为255时,如果再加1,则carry为1,表示溢出。(extra_rc在arm64中占8位,最大值可表示255)

代码测试extra_rc的值

我们这里把代码改为MRC环境下,手动进行retain。

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. // insert code here...
  4. NSObject *objc1 = [[NSObject alloc] init];
  5. for (int i=0; i<512; i++) {
  6. [objc1 retain];
  7. }
  8. }
  9. return 0;
  10. }

在循环处设置i==255的断点,同时在newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);处加上断点,这时候extra_rc无法存储就会溢出,然后设置isa的值newisa.extra_rc = RC_HALF(128);newisa.has_sidetable_rc = true;

截屏2020-11-02 上午9.53.04.png-207.9kB

然后再进入i==256,这时候在extra_rc减半的基础上再加1,extra_rc==129

截屏2020-11-02 上午9.59.22.png-174kB

所以这里每次溢出的时候就会存储一半的引用计数到sidetable中。

retain引用计数

我们使用如下代码,并且在objc_object::rootRetain()处加上断点:

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. //1.创建对象,引用计数为1
  4. NSObject *objc1 = [[NSObject alloc] init];
  5. //2.引用计数加1
  6. NSObject *objc2 = objc1;
  7. printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(objc1)));
  8. NSLog(@"Hello, World! %@ - %@",objc1,objc2);
  9. }
  10. return 0;
  11. }
  1. //输出
  2. retain count = 2

我们发现在第一步alloc、init时并没有调用objc_object::rootRetain()方法,在第二步把objc2指向objc1时才调用了objc_object::rootRetain()使引用计数加1。但我们看到输出时引用计数值为2,这是为什么呢?

我们看一下获取引用计数的调用方法即可,CFGetRetainCount最后调用的是objc-object.h源码中的objc_object::rootRetainCount()方法:

  1. inline uintptr_t
  2. objc_object::rootRetainCount()
  3. {
  4. if (isTaggedPointer()) return (uintptr_t)this;
  5. sidetable_lock();
  6. isa_t bits = LoadExclusive(&isa.bits);
  7. ClearExclusive(&isa.bits);
  8. if (bits.nonpointer) {
  9. //引用计数值为extra_rc+1
  10. uintptr_t rc = 1 + bits.extra_rc;
  11. //如果之前retain时extra_rc溢出,
  12. //则从sidetable中取溢出的引用计数和rc的值相加即为完整的引用计数
  13. if (bits.has_sidetable_rc) {
  14. rc += sidetable_getExtraRC_nolock();
  15. }
  16. sidetable_unlock();
  17. return rc;
  18. }
  19. sidetable_unlock();
  20. return sidetable_retainCount();
  21. }

截屏2020-11-01 下午7.52.17.png-558.6kB

我们发现extra_rc值确实为1,只是这里永远会有一个extra_rc+1的操作,也就是说你创建对象(alloc)之后其实引用计数就是为1的,没必要再调用retain了,直接在获取引用计数的地方加一个常数1即可。

也就是说retainCount有三部分组成:

释放dealloc&release

dealloc调用过程

调用步骤:

  1. //第一步:
  2. - (void)dealloc {
  3. _objc_rootDealloc(self);
  4. }
  5. void
  6. _objc_rootDealloc(id obj)
  7. {
  8. ASSERT(obj);
  9. obj->rootDealloc();
  10. }
  11. //第二步:
  12. inline void
  13. objc_object::rootDealloc()
  14. {
  15. if (isTaggedPointer()) return; // fixme necessary?
  16. if (fastpath(isa.nonpointer && // 是否是优化过的isa
  17. !isa.weakly_referenced && // 不包含或者不曾经包含weak指针
  18. !isa.has_assoc && // 没有关联对象
  19. !isa.has_cxx_dtor && // 没有c++析构方法
  20. !isa.has_sidetable_rc)) // 引用计数没有超出上限的时候可以快速释放,rootRetain(bool tryRetain, bool handleOverflow) 中设置为true
  21. {
  22. assert(!sidetable_present());
  23. free(this);
  24. }
  25. else {
  26. object_dispose((id)this);
  27. }
  28. }

我们从第二步可以看到满足一定条件下,对象指针会直接free释放掉,实际很多情况下都会走object_dispose((id)this)函数。这一步主要就是释放成员变量,移除关联对象,设置弱引用指针为nil:

  1. //第三步:
  2. id
  3. object_dispose(id obj)
  4. {
  5. if (!obj) return nil;
  6. objc_destructInstance(obj);
  7. /// 释放内存
  8. free(obj);
  9. return nil;
  10. }
  11. void *objc_destructInstance(id obj)
  12. {
  13. if (obj) {
  14. // Read all of the flags at once for performance.
  15. bool cxx = obj->hasCxxDtor();
  16. bool assoc = obj->hasAssociatedObjects();
  17. // 对象拥有成员变量时编译器会自动插入.cxx_desctruct方法用于自动释放。
  18. if (cxx) object_cxxDestruct(obj);
  19. // 移除关联对象
  20. if (assoc) _object_remove_assocations(obj);
  21. // weak->nil
  22. obj->clearDeallocating();
  23. }
  24. return obj;
  25. }
  1. //释放成员变量,沿着继承链逐层向上搜寻SEL_cxx_destruct这个selector,找到函数实现(void (*)(id)(函数指针)并执行。
  2. void object_cxxDestruct(id obj)
  3. {
  4. if (!obj) return;
  5. if (obj->isTaggedPointer()) return;
  6. object_cxxDestructFromClass(obj, obj->ISA());
  7. }
  8. static void object_cxxDestructFromClass(id obj, Class cls)
  9. {
  10. void (*dtor)(id);
  11. // Call cls's dtor first, then superclasses's dtors.
  12. for ( ; cls; cls = cls->superclass) {
  13. if (!cls->hasCxxDtor()) return;
  14. dtor = (void(*)(id))
  15. lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
  16. if (dtor != (void(*)(id))_objc_msgForward_impcache) {
  17. if (PrintCxxCtors) {
  18. _objc_inform("CXX: calling C++ destructors for class %s",
  19. cls->nameForLogging());
  20. }
  21. (*dtor)(obj);
  22. }
  23. }
  24. }
  1. // weak->nil
  2. inline void
  3. objc_object::clearDeallocating()
  4. {
  5. if (slowpath(!isa.nonpointer)) {
  6. // Slow path for raw pointer isa.
  7. sidetable_clearDeallocating();
  8. }
  9. else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
  10. // Slow path for non-pointer isa with weak refs and/or side table data.
  11. clearDeallocating_slow();
  12. }
  13. assert(!sidetable_present());
  14. }
  15. NEVER_INLINE void
  16. objc_object::clearDeallocating_slow()
  17. {
  18. ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
  19. // 根据对象地址从SideTables中取出SideTable
  20. SideTable& table = SideTables()[this];
  21. table.lock();
  22. if (isa.weakly_referenced) {
  23. // 从SideTable中取出weak_table,根据weak_table获取弱引用数组,然后一个个置为nil
  24. weak_clear_no_lock(&table.weak_table, (id)this);
  25. }
  26. if (isa.has_sidetable_rc) {
  27. table.refcnts.erase(this);
  28. }
  29. table.unlock();
  30. }

20191023223411723.png-35.6kB

release过程

  1. //第一步:
  2. - (oneway void)release {
  3. _objc_rootRelease(self);
  4. }
  5. //第二步:
  6. _objc_rootRelease(id obj)
  7. {
  8. ASSERT(obj);
  9. obj->rootRelease();
  10. }
  11. //第三步:
  12. objc_object::rootRelease()
  13. {
  14. return rootRelease(true, false);
  15. }

最终调用的是如下方法:

  1. ALWAYS_INLINE bool
  2. objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
  3. {
  4. if (isTaggedPointer()) return false;
  5. bool sideTableLocked = false;
  6. isa_t oldisa;
  7. isa_t newisa;
  8. retry:
  9. do {
  10. oldisa = LoadExclusive(&isa.bits);
  11. newisa = oldisa;
  12. .
  13. .
  14. .
  15. uintptr_t carry;
  16. // extra_rc中的值减1
  17. newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
  18. // 如果减1后小于0,则carry为1,执行下溢操作
  19. if (slowpath(carry)) {
  20. goto underflow;
  21. }
  22. } while (slowpath(!StoreReleaseExclusive(&isa.bits,
  23. oldisa.bits, newisa.bits)));
  24. if (slowpath(sideTableLocked)) sidetable_unlock();
  25. return false;
  26. underflow:
  27. newisa = oldisa;
  28. // 如果sidetable中有保存引用计数,则从SideTable 借位
  29. if (slowpath(newisa.has_sidetable_rc)) {
  30. if (!handleUnderflow) {
  31. ClearExclusive(&isa.bits);
  32. return rootRelease_underflow(performDealloc);
  33. }
  34. size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
  35. if (borrowed > 0) {
  36. newisa.extra_rc = borrowed - 1;
  37. bool stored = StoreReleaseExclusive(&isa.bits,
  38. oldisa.bits, newisa.bits);
  39. if (!stored) {
  40. isa_t oldisa2 = LoadExclusive(&isa.bits);
  41. isa_t newisa2 = oldisa2;
  42. if (newisa2.nonpointer) {
  43. uintptr_t overflow;
  44. newisa2.bits =
  45. addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
  46. if (!overflow) {
  47. stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
  48. newisa2.bits);
  49. }
  50. }
  51. }
  52. if (!stored) {
  53. sidetable_addExtraRC_nolock(borrowed);
  54. goto retry;
  55. }
  56. sidetable_unlock();
  57. return false;
  58. }
  59. else {
  60. // Side table 也是空值,则进入下面的dealloc的操作
  61. }
  62. }
  63. // 上面引用计数真的减少到零之后,则执行dealloc的操作
  64. if (slowpath(newisa.deallocating)) {
  65. ClearExclusive(&isa.bits);
  66. if (sideTableLocked) sidetable_unlock();
  67. return overrelease_error();
  68. // does not actually return
  69. }
  70. newisa.deallocating = true;
  71. if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
  72. if (slowpath(sideTableLocked)) sidetable_unlock();
  73. __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
  74. if (performDealloc) {
  75. ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
  76. }
  77. return true;
  78. }

参考

Objective-C 引用计数原理
iOS 引用计数 retainCount、retain、release 源码分析+注释+实验
ARC下,Dealloc还需要注意什么?
黑箱中的 retain 和 release

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注