iOS 内存管理底层分析(一)- 内存干系

源码 2024-9-23 02:25:10 88 0 来自 中国
干系文献:
iOS 内存管理底层分析(一)- 内存干系
iOS 内存管理底层分析(二)- AutoreleasePool底层
本文把握知识点:
1.内存的五大分区
2.内存管理方案:MRC、ARC、TaggedPointer、nonpointer_isa、SideTables、主动开释池
3.weak_table_t 弱引用表底层原理、__weak的底层原理、弱引用对象的引用计数题目
4.retain、release、dealloc 的源码分析
一、内存的五大分区


  • 堆区
    堆是向高地点扩展的数据结构;是不一连的内存地区,雷同于链表结构(便于增删,未便于查询),遵照先进先出(FIFO)原则;通常以 alloc/new/malloc 方式创建的对象。内存地点以0x6开头。
    优点:机动方便,数据顺应面广泛。
    缺点:需手动管理,速率慢、容易产生内存碎片。
  • 栈区
    栈是体系数据结构,其对应的历程或线程是唯一的;栈是向低地点扩展数据结构,是一块一连的存储地区,遵照先进后出(FILO)的原则。堆区的分配一样寻常是在运行时分配;存储局部变量、方法参数、对象的指针等。内存地点以0x7开头。
    优点:栈是由编译器主动分配并开释的,不会产生内存碎片,以是快速高效,便于查询,未便于增删。
    缺点:内存大小有限定,数据不机动主线程栈大小是1MB,子线程栈大小是512KB。
  • 全局区(静态区)
    全局区是编译时分配的内存空间,步伐运行过程中,此内存中的数据不停存在,步伐竣过后由体系开释,紧张存放:未初始化的全局变量和静态变量,即bss区(.bss);已初始化的全局变量和静态变量,即数据区(.data)。内存地点以0x1开头。
    此中,全局变量是指变量值可以在运行时被动态修改,而静态变量是static修饰的变量,包罗静态局部变量和静态全局变量。
  • 常量区
    常量区是编译时分配的内存空间,在步伐竣过后由体系开释,紧张存放 已经利用了的,且没有指向的字符串常量
  • 代码区
    代码区是编译时分配紧张用于存放步伐运行时的代码,代码会被编译成二进制存进内存的
内存五大区的验证:
内存结构

先容了内存的五大区,但其实除了内存区,另有内核区和保存区。
以4GB手机为例,如下所示,体系将此中的3GB给了五大区+保存区,剩余的1GB给内核区利用:


  • 内核区:体系用来举行内核处置惩罚操纵的地区
  • 五大区:上面已阐明
  • 保存区:预留给体系处置惩罚nil等
为什么五大区的末了内存地点是从0x00400000开始的?
紧张缘故原由是0x00000000表现nil,不能直接用nil表现一个段,以是单独给了一段内存用于处置惩罚nil等情况。
内存结构口试题
口试题:全局变量和局部变量在内存中是否有区别?如果有,是什么区别?   答案是有区别。

  • 全局变量生存在内存的全局存储区(即bss+data段),占用静态的存储单元;
  • 局部变量生存在栈中,只有在地点函数被调用时才动态的为变量分配存储单元。
二、内存管理方案


  • MRC - 手动引用计数
  • ARC - 主动引用计数
  • nonpointer_isa - 新版OC对象的isa指针优化
  • TaggedPointer - 小对象优化
  • SideTables - 散列表 (引用计数表和弱引用表)
  • autoreleasePool - 主动开释池
三、MRC 与 ARC (引用计数)

引用计数是管理对象声明周期的一种方式。当新建一个对象它的引用计数为1,当这个对象引用计数为0的时间,这个对象就会被烧毁并开释其所占用的内存空间。
MRC

在MRC期间,体系是通过对象的引用计数来判定一个是否烧毁,有以下规则:

  • 对象被创建时引用计数都为1;
  • 当对象被其他指针引用时,必要手动调用[objc retain],使对象的引用计数+1;
  • 当指针变量不再利用对象时,必要手动调用[objc release]来开释对象,使对象的引用计数-1;
  • 当一个对象的引用计数为0时,体系就会烧毁这个对象。
ARC

ARC模式是在WWDC2011和iOS5引入的主动管理机制,即主动引用计数。是编译器的一种特性。其规则与MRC划一,区别在于无需步伐员手动插入内存管理干系代码。
结论:
1.在MRC模式下,必须服从:谁创建,谁开释,谁引用,谁管理。
2.ARC模式下不必要手动retain、release、autorelease,编译器会在恰当的位置插入release和autorelease。
四、nonpointer_isa - isa指针优化

nonpointer_isa:非指针范例的isa,紧张是在创建对象时,用来优化isa指针的64位地点,详细内容在Objective-C 对象的底层探索。
我们知道在创建OC对象的时间,会初始化一个8字节的isa指针指向该OC对象的类对象。在旧版本的OC对象的isa指针紧张纪录着对象的引用计数,很显然仅仅是纪录这就利用8字节(64位)黑白常奢侈的。于是在新版本的OC对象对isa的64位举行了优化。
五、TaggedPointer - 小对象

TaggedPointer:是一个被打上标志的指针,在栈上分配8字节指针(不再必要堆去分配),该指针指向的不再是地点,而是真实值。TaggedPointer专门用来处置惩罚小对象,比方NSNumber、NSDate、小NSString等。(它是在64位iOS体系下提出来的 iPhone5s以后)
以NSString为例,运行下面代码打印:
    NSString *firstString = @"helloworld"; // __NSCFConstantString 常量区    NSString *secondString = [NSString stringWithFormat"helloworld"]; // __NSCFString 堆区    NSString *thirdString = @"hello"; // __NSCFConstantString 常量区    NSString *fourthSting = [NSString stringWithFormat"hello"]; // NSTaggedPointerString 栈指针    NSLog(@"%p %@",firstString,[firstString class]); // 0x1058d60c0 __NSCFConstantString    NSLog(@"%p %@",secondString,[secondString class]); // 0x600000b7b960 __NSCFString    NSLog(@"%p %@",thirdString,[thirdString class]); // 0x1058d60e0 __NSCFConstantString    NSLog(@"%p %@",fourthSting,[fourthSting class]); // 0xd9d08f5a3bd7e123 NSTaggedPointerString留意:此时打印NSTaggedPointerString范例指针的内容0xd9d08f5a3bd7e123其实是混淆过的。在下文探究 小对象地点分析 时会先容。
NSString的内存管理紧张分为3种:

  • __NSCFConstantString:字符串常量,是一种编译时常量,retainCount值很大,对其操纵不会引起引用计数变革,存储在字符串常量区。
  • __NSCFString:是在运行时创建的NSString子类,创建后引用计数会加1,存储在堆上。
  • NSTaggedPointerString:标签指针,是苹果在64位情况下对NSString、NSNumber等对象做的优化。
    对于NSString对象来说,当字符串是由数字、英笔墨母组合且长度 <= 9时,会主动成为NSTaggedPointerString范例,存储在常量区;当有中文 大概 其他特殊符号 或 长度 > 9时,会直接成为__NSCFString范例,存储在堆区。
1.小对象地点分析

以NSString为例:

  • 对于一样寻常的NSString对象指针,都是string值 + 指针地点,两者是分开的;
  • 对于TaggedPointer指针,是指针 + 值,都能在小对象中体现。以是TaggedPointer 既包罗指针,也包罗值。
上文中提到过NSLog(@"%p %@",fourthSting,[fourthSting class]);打印出来的取地点的内容是混淆过的。
这是由于objc4-838.1源码在类的加载过程中的_objc_init -> map_images -> _read_images -> initializeTaggedPointerObfuscator
我们可以在源码中通过objc_debug_taggedpointer_obfuscator查找taggedPointer的编码息争码:
#define OBJC_TAG_INDEX_MASK 0x7UL#define OBJC_TAG_INDEX_SHIFT 0extern uintptr_t objc_debug_taggedpointer_obfuscator;extern uint8_t objc_debug_tag60_permutations[8];uintptr_t objc_obfuscatedTagToBasicTag(uintptr_t tag) {    for (unsigned i = 0; i < 7; i++)        if (objc_debug_tag60_permutations == tag)            return i;    return 7;}uintptr_tobjc_decodeTaggedPointer(id ptr){    uintptr_t value = (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;    uintptr_t basicTag = (value >> OBJC_TAG_INDEX_SHIFT) & OBJC_TAG_INDEX_MASK;    value &= ~(OBJC_TAG_INDEX_MASK << OBJC_TAG_INDEX_SHIFT);    value |= objc_obfuscatedTagToBasicTag(basicTag) << OBJC_TAG_INDEX_SHIFT;    return value;}static inline uintptr_t objc_basicTagToObfuscatedTag(uintptr_t tag) {    return objc_debug_tag60_permutations[tag];}void *objc_encodeTaggedPointer(uintptr_t ptr){    uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);    uintptr_t basicTag = (value >> OBJC_TAG_INDEX_SHIFT) & OBJC_TAG_INDEX_MASK;    uintptr_t permutedTag = objc_basicTagToObfuscatedTag(basicTag);    value &= ~(OBJC_TAG_INDEX_MASK << OBJC_TAG_INDEX_SHIFT);    value |= permutedTag << OBJC_TAG_INDEX_SHIFT;    return (void *)value;}于是我们就可以通过调用objc_decodeTaggedPointer来还原真实指针内容:

5.png
此中最高位是标志是否是TaggedPointer,最低三位是看是什么范例,这里010的十进制是2 表现NSString,如下图:
6.png 可以通过objc4源码中的_objc_makeTaggedPointer方法的参数tag范例objc_tag_index_t进入其摆列,此中 2表现NSString,3表现NSNumber:
举例NSNumber:
8.png 2.taggedpointer口试题

- (void)touchesBeganNSSet<UITouch *> *)touches withEventUIEvent *)event {        self.queue = dispatch_queue_create("com.wj.cn", DISPATCH_QUEUE_CONCURRENT);        for (int i = 0; i<10000; i++) {        dispatch_async(self.queue, ^{            self.nameStr = [NSString stringWithFormat"WJ"]; // 栈(长度<=9)            NSLog(@"%@",self.nameStr);        });    }        for (int i = 0; i<10000; i++) {        dispatch_async(self.queue, ^{            self.nameStr = [NSString stringWithFormat"安安安安"]; // 堆(中笔墨符)            NSLog(@"%@",self.nameStr);        });    }}效果:引发瓦解,瓦解在第二个for循环里。
缘故原由:在ARC情况下,这里利用多线程有可能引发对同一块堆内存多次调用了release。
六、SideTables - 散列表

SideTables:散列表,在散列表中紧张有两个表,分别是引用计数表和弱引用表。(每次在访问SideTables时的加锁解锁操纵,会低沉效率的)
但是散列表不是一张表,而是多张表。
打开objc4-838.1源码,找到objc_retain -> retain -> rootRetain 这里在本章节的 七、retain源码分析 部分会侧重先容,有爱好可以滑下去看。紧张内容就是引用计数的存储方案。找到 sidetable_addExtraRC_nolock:
它是从多张表SideTables里获取该对象的SideTable
而SideTables其实是StripedMap范例:
13.png 多张表并不是无穷数量的,苹果计划出: SideTables在真机下8张表/模拟器下64张表 的方案,以到达运行效率和节省内存平衡目的
提问一:那我可以计划只有一张全局的表来存储全部对象的引用计数和弱引用信息吗?
答案:可以。但是每次读写对象的时间都得操纵这张全局表,而这张全局每次读写都必要频仍地加快/解锁操纵,这样会导致体系非常地慢。
提问二:那我可以计划每一个对象都有它单独的表来存储自己的引用计数和弱引用信息吗?
答案:可以。但是每创建一个对象会就会产生一张表,则会产生很多多少的表,引发大量斲丧内存的题目。
1.RefcountMap - 引用计数表

RefcountMap是用来存储引用计数的。(RefcountMap在一样寻常情况下是用不到的)。
这里的内容在本文章节 七、retain源码分析 里有分析,这里只给总结:

  • a.当isa指针不是nonpointer_isa范例的时间,该对象的引用计数就存储在SideTable里的RefcountMap;
  • b.当isa指针是nonpointer_isa范例,而且nonpointer_isa里的extra_rc存满了,会把另一半的引用计数存储到SideTable里的RefcountMap里,而extra_rc只有原来的一半。
2.weak_table_t - 弱引用表(weak底层原理)

UIViewController *oldVC = [UIViewController new];__weak typeof(oldVC) weakVC = oldVC; // 旧的UIViewController *newVC = [UIViewController new];weakVC = newVC; // 新的接下来看看weak底层逻辑吧。
利用__weak修饰的指针指向一个对象时,会走源码中的objc_initWeak。
打开objc4-838.1源码,搜索objc_initWeak函数
storeWeak函数就是处置惩罚__weak修饰的弱对象指向一个对象的处置惩罚:
// 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};// HaveOld: weak指针是否之前就指向了对象 即weakVC是否指向过oldVC// HaveNew: weak指针是否将指向的新的对象 即weakVC是否将要指向newVC// CrashIfDeallocating: 被弱引用的对象是否正在析构,如果析构则Crashtemplate <HaveOld haveOld, HaveNew haveNew,          enum CrashIfDeallocating crashIfDeallocating>// location: 弱引用指针的地点 即weakVC的地点// newObj: 将被弱引用指针指向的对象 即newVC对象static id storeWeak(id *location, objc_object *newObj){    ASSERT(haveOld  ||  haveNew);    if (!haveNew) ASSERT(newObj == nil);    Class previouslyInitializedClass = nil;    id oldObj; // 获取 weakVC之前指向的对象oldVC,若没有指向过,则为nil    SideTable *oldTable; // 获取 weakVC之前指向的对象oldVC的散列表,若没有指向过,则为nil    SideTable *newTable; // 获取 weakVC将被弱引用的对象newVC的散列表    // 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) { // 如果weakVC曾经指向过oldVC,获取 oldVC对象 和 oldVC散列表        oldObj = *location;        oldTable = &SideTables()[oldObj];    } else {        oldTable = nil;    }    if (haveNew) { // 如果weakVC将要指向newVC,获取 newVC散列表        newTable = &SideTables()[newObj];    } else {        newTable = nil;    }         SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable); // 加锁        // 清除非常情况(不消管):如果weakVC曾经指向过oldVC,而且weakVC指向的还不是oldVC    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.    //  清除非常情况(不消管):newVC的类对象还没有被初始化,就去初始化    if (haveNew  &&  newObj) {        Class cls = newObj->getIsa();        if (cls != previouslyInitializedClass  &&              !((objc_class *)cls)->isInitialized())         {            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);            class_initialize(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) { // 如果weakVC曾经指向过oldVC,则把之前的弱引用注销掉        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);    }    // Assign new value, if any.    // weakVC去注册新的弱引用,并指向newVC    if (haveNew) {        newObj = (objc_object *)            // 怎么注册?将weakVC的地点存储到SideTable里weak_table_t里去            weak_register_no_lock(&newTable->weak_table, (id)newObj, location,                                   crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);        // weak_register_no_lock returns nil if weak store should be rejected        // Set is-weakly-referenced bit in refcount table.        // 如果不是TaggedPointer或nil,将isa里的是否被弱引用weakly_referenced 置为true        if (!_objc_isTaggedPointerOrNil(newObj)) {            newObj->setWeaklyReferenced_nolock(); // 将isa的是否被弱引用置为true        }        // Do not set *location anywhere else. That would introduce a race.       // 将weakVC的地点生存newVC        *location = (id)newObj;    }    else {        // No new value. The storage is not changed.    }        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // 解锁    // This must be called without the locks held, as it can invoke    // arbitrary code. In particular, even if _setWeaklyReferenced    // is not implemented, resolveInstanceMethod: may be, and may    // call back into the weak reference machinery.    callSetWeaklyReferenced((id)newObj);    return (id)newObj;}weak底层调用:objc_initWeak -> storeWeak
storeWeak的底层逻辑:

  • 1.如果weak指针已经指向了一个A对象,则会把weak指针地点从SideTable散列表里的弱引用表weak_table_t中注销weak_unregister_no_lock;
  • 2.如果weak指针要 改变指向/指向新的 B对象,则会把weak指针地点注册进SideTable散列表里的弱引用表weak_table_t中weak_register_no_lock,而且将 B对象的类对象的isa里的weakly_referenced置为true,还会将weak指针指向B对象。
下面就来研究怎样注册和注销的。
3.相识weak指针在弱引用表weak_table_t是怎样注销和注册的(探索weak_unregister_no_lock、weak_register_no_lock)。

3.1相识weak_table_t的数据结构

struct weak_table_t {    // weak_entry_t是一个hash表,key:当前对象的地点,value: 存储弱引用指针的地点的数组    weak_entry_t *weak_entries; // (由于一个对象可以被多个弱引用去指向)    size_t    num_entries; // 个数    uintptr_t mask; // 数组的长度-1(扩容干系)    uintptr_t max_hash_displacement; // 办理hash辩说};// hash数组struct weak_entry_t {    // referent是一个动态的hash数组,存储弱引用指针的地点的数组    DisguisedPtr<objc_object> referent;    union {        // 1.当前对象被弱引用个数>4时,就会用这个struct来存储弱引用指针的地点        struct {            weak_referrer_t *referrers;            uintptr_t        out_of_line_ness : 2;            uintptr_t        num_refs : PTR_MINUS_2;            uintptr_t        mask;            uintptr_t        max_hash_displacement;        };        // 2.当前对象被弱引用个数<=4时,就会用weak_referrer_t来存储弱引用指针的地点        struct {            // out_of_line_ness field is low bits of inline_referrers[1]            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];        };    };    // out_of_line用来判定,用哪种1/2方式来存储弱引用指针的地点    bool out_of_line() {        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);    }    weak_entry_t& operator=(const weak_entry_t& other) {        memcpy(this, &other, sizeof(other));        return *this;    }        // weak_entry_t的构造函数    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)        : referent(newReferent)    {        inline_referrers[0] = newReferrer;        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {            inline_referrers = nil;        }    }};留意:SideTable在真机下有8张表,以是weak_table_t也有8张表。
weak_table_t是一个hash表,key:当前对象的地点;value:存储弱引用指针的地点的数组。为什么是数组?由于一个对象可以同时被多个弱引用去指向。
weak_table_t 数据结构雷同于:
// [key: value]{  A对象的地点: [weak1地点, weak2地点],  B对象的地点: [weak3地点, weak4地点],  ...}3.2 weak_register_no_lock将弱引用注册到对象的弱引用表weak_table_t中

weak_register_no_lock的底层逻辑:

  • a.对TaggedPointer和nil不处置惩罚;
  • b.通过被引用对象的地点去取出weak_entry_t哈希数组,如果有哈希数组,则直接将弱引用指针地点存入这个哈希数组;
  • c.如果没有哈希数组,则创建一个,再将弱引用指针地点插入到哈希数组。
3.3 weak_unregister_no_lock将弱引用从对象的弱引用表weak_table_t中注册

weak_unregister_no_lock的底层逻辑:

  • a.通过被引用对象的地点去取出weak_entry_t哈希数组,如果有哈希数组,则将弱引用指针地点从哈希数组中移除。倘若哈希数组空了,则必要去清空这个对象的弱引用表weak_table_t。
weak口试题:(弱引用对象的引用计数题目)

为什么weakVC打印引用计数是2呢?
UIViewController *oldVC = [UIViewController new];__weak typeof(oldVC) weakVC = oldVC; // 旧的UIViewController *newVC = [UIViewController new];weakVC = newVC; // 新的NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(newVC)));//1 断点这NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(weakVC)));//2 断点这打开汇编调试Debug->Debug Workflow->Always Show Disassembly
当打印强引用newVC的引用计数时,可以看到汇编会调用CFGetRetainCount,而打印弱引用weakVC的引用计数时间,则会调用objc_loadWeakRetained
打开objc4-838.1源码,找到objc_loadWeakRetained:
17.png 留意:在打印弱引用weakVC的引用计数时间,会对obj举行引用计数+1的操纵,但是由于obj是一个局部变量,出了函数域则会引用计数-1。
每次打印引用weakVC的引用计数都是2,其实是一个假象而已。
七、retain源码分析

打开objc4-838.1源码,搜索objc_retain函数
18.png rootRetain函数就是处置惩罚对象的引用计数的逻辑:
ALWAYS_INLINE idobjc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant){    if (slowpath(isTaggedPointer())) return (id)this;    bool sideTableLocked = false;    bool transcribeToSideTable = false;    // 先去获取对象的isa指针,由于引用计数信息存储在isa里    isa_t oldisa;    isa_t newisa;    oldisa = LoadExclusive(&isa.bits);    if (variant == RRVariant::FastOrMsgSend) {        // These checks are only meaningful for objc_retain()        // They are here so that we avoid a re-load of the isa.        if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {            ClearExclusive(&isa.bits);            if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {                return swiftRetain.load(memory_order_relaxed)((id)this);            }            return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));        }    }    if (slowpath(!oldisa.nonpointer)) {        // a Class is a Class forever, so we can perform this check once        // outside of the CAS loop        if (oldisa.getDecodedClass(false)->isMetaClass()) {            ClearExclusive(&isa.bits);            return (id)this;        }    }    // 焦点逻辑在这个do...while!!!    do {        transcribeToSideTable = false;        newisa = oldisa;        // 1.不是nonpointer_isa的情况,sidetable存储引用计数        if (slowpath(!newisa.nonpointer)) {            ClearExclusive(&isa.bits);            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;            else return sidetable_retain(sideTableLocked);        }        // 下面代码逻辑 是nonpointer_isa的情况        // don't check newisa.fast_rr; we already called any RR overrides        if (slowpath(newisa.isDeallocating())) {            ClearExclusive(&isa.bits);            if (sideTableLocked) {                ASSERT(variant == RRVariant::Full);                sidetable_unlock();            }            if (slowpath(tryRetain)) {                return nil;            } else {                return (id)this;            }        }        // 2.是nonpointer_isa的情况,引用技能位extra_rc存得下,直接+1        uintptr_t carry;        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++        // 3.是nonpointer_isa的情况,引用技能位extra_rc存不下,extra_rc保存一半的引用计数,并准备将另一半copy到sidetable        if (slowpath(carry)) {            // newisa.extra_rc++ overflowed            // 判定extra_rc是否超出            if (variant != RRVariant::Full) {                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.            // extra_rc存不下,保存一半的引用计数,并准备将另一半copy到sidetable            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 (variant == RRVariant::Full) {        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();    } else {        ASSERT(!transcribeToSideTable);        ASSERT(!sideTableLocked);    }    return (id)this;}总结:
retain源码调用流程:objc_retain --> objc_object::retain() --> objc_object::rootRetain
rootRetain操纵逻辑:

  • 1.判定对象是否为taggedPointer范例,如果是 则 return;
  • 2.获取对象的isa指针里取出对象的引用计数信息;
  • 3.判定isa是否是nonpointer_isa:(通常是nonpointer_isa)
    3.1 如果不是nonpointer_isa,将sidetable散列表里的引用计数+1并return。
    3.2 如果是nonpointer_isa,而且对象正在被开释,直接return。
    3.3 如果是nonpointer_isa,对象不是正在被开释,进入下一步4;
  • 4.先让isa里的extra_rc引用计数+1,判定是否可以大概存得下:
    4.1 如果extra_rc位能存得下,就存着。
    4.2 如果extra_rc位能存不下(少数情况才会出现),将has_sidetable_rc标志位为1,extra_rc保存一半的引用计数,将另一半的引用计数存储到sidetable
提问:为什么要计划利用extra_rc来存储引用计数,extra_rc存满了才将另一半的引用计数存储到sidetable呢?
由于sidetable每次读写都必要加锁解锁的操纵,体系就没有那么快。这样计划的目的是进步性能。
八、release源码分析

打开objc4-838.1源码,搜索objc_release函数
21.png rootRelease函数就是处置惩罚对象的引用计数的逻辑:
ALWAYS_INLINE boolobjc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant){    if (slowpath(isTaggedPointer())) return false;    bool sideTableLocked = false;    // 获取对象的isa,由于引用计数信息存储在isa里    isa_t newisa, oldisa;    oldisa = LoadExclusive(&isa.bits);    if (variant == RRVariant::FastOrMsgSend) {        // These checks are only meaningful for objc_release()        // They are here so that we avoid a re-load of the isa.        if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {            ClearExclusive(&isa.bits);            if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {                swiftRelease.load(memory_order_relaxed)((id)this);                return true;            }            ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));            return true;        }    }    if (slowpath(!oldisa.nonpointer)) {        // a Class is a Class forever, so we can perform this check once        // outside of the CAS loop        if (oldisa.getDecodedClass(false)->isMetaClass()) {            ClearExclusive(&isa.bits);            return false;        }    }retry:    // 引用计数真正操纵逻辑在do...while里    do {        newisa = oldisa;        // 1.如果不是nonpointer_isa,操纵sidetable里的引用计数-1        if (slowpath(!newisa.nonpointer)) {            ClearExclusive(&isa.bits);            return sidetable_release(sideTableLocked, performDealloc);        }        // 判定对象是否正在开释,如果是,则直接return        if (slowpath(newisa.isDeallocating())) {            ClearExclusive(&isa.bits);            if (sideTableLocked) {                ASSERT(variant == RRVariant::Full);                sidetable_unlock();            }            return false;        }        // don't check newisa.fast_rr; we already called any RR overrides        // 将nonpointer_isa里的引用计数位extra_rc举行-1操纵        uintptr_t carry;        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--        if (slowpath(carry)) {            // don't ClearExclusive()            goto underflow; // 如果extra_rc的值为0的情况,走underflow代码块        }    } while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));    if (slowpath(newisa.isDeallocating()))        goto deallocate; // 开释对象内存    if (variant == RRVariant::Full) {        if (slowpath(sideTableLocked)) sidetable_unlock();    } else {        ASSERT(!sideTableLocked);    }    return false; underflow:    // newisa.extra_rc-- underflowed: borrow from side table or deallocate    // abandon newisa to undo the decrement    newisa = oldisa;    // 如果nonpointer_isa的sidetable标志位 has_sidetable_rc == 1    if (slowpath(newisa.has_sidetable_rc)) {        if (variant != RRVariant::Full) {            ClearExclusive(&isa.bits);            return rootRelease_underflow(performDealloc);        }        // Transfer retain count from side table to inline storage.        // 将引用计数从sidetable转移回extra_rc        if (!sideTableLocked) {            ClearExclusive(&isa.bits);            sidetable_lock();            sideTableLocked = true;            // Need to start over to avoid a race against             // the nonpointer -> raw pointer transition.            oldisa = LoadExclusive(&isa.bits);            goto retry;        }        // Try to remove some retain counts from the side table.        // 把sidetable里的引用计数移除        auto borrow = sidetable_subExtraRC_nolock(RC_HALF);        // 如果sidetable上没有多余的东西,我们就把sidetable清算干净        bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there        if (borrow.borrowed > 0) {            // Side table retain count decreased.            // Try to add them to the inline count.            // sidetable保存引用计数-1。实验将它们添加到内联计数中。            bool didTransitionToDeallocating = false;            newisa.extra_rc = borrow.borrowed - 1;  // redo the original decrement too            newisa.has_sidetable_rc = !emptySideTable;            bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);            if (!stored && oldisa.nonpointer) {                // Inline update failed.                 // Try it again right now. This prevents livelock on LL/SC                 // architectures where the side table access itself may have                 // dropped the reservation.                uintptr_t overflow;                newisa.bits =                    addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);                newisa.has_sidetable_rc = !emptySideTable;                if (!overflow) {                    stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);                    if (stored) {                        didTransitionToDeallocating = newisa.isDeallocating();                    }                }            }            if (!stored) {                // Inline update failed.                // Put the retains back in the side table.                ClearExclusive(&isa.bits);                sidetable_addExtraRC_nolock(borrow.borrowed);                oldisa = LoadExclusive(&isa.bits);                goto retry;            }            // Decrement successful after borrowing from side table.            // 减量乐成后,从sidetable拿回引用计数给extra_rc。            if (emptySideTable)                sidetable_clearExtraRC_nolock();            if (!didTransitionToDeallocating) {                if (slowpath(sideTableLocked)) sidetable_unlock();                return false;            }        }        else {            // Side table is empty after all. Fall-through to the dealloc path.        }    }deallocate:    // Really deallocate.    ASSERT(newisa.isDeallocating());    ASSERT(isa.isDeallocating());    if (slowpath(sideTableLocked)) sidetable_unlock();    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);    if (performDealloc) {        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));    }    return true;}总结:
release源码调用流程:objc_release --> objc_object::release() --> objc_object::rootRelease
rootRelease操纵逻辑:

  • 1.判定对象是否为taggedPointer范例,如果是则return;
  • 2.获取对象的isa指针里取出对象的引用计数信息;
  • 3.判定isa是否是nonpointer_isa:(通常是nonpointer_isa)
    3.1 如果不是nonpointer_isa,就去操纵sidetable里的引用计数-1,并return。
    3.2 如果是nonpointer_isa,而且对象正在被开释,直接return。
    3.3 如果是nonpointer_isa,对象不是正在被开释,进入下一步4;
  • 4.先让isa里的extra_rc引用计数-1,判定extra_rc即是0:
    4.1 如果extra_rc!=0时,直接return。
    4.2 如果extra_rc==0时,判定has_sidetable_rc即是1:
    4.2.1 如果has_sidetable_rc==0,阐明该对象引用计数全部清零,必要被接纳内存dealloc。
    4.2.2 如果has_sidetable_rc==1(少数情况才会出现),阐明该对象借助sidetable存储引用计数,将sidetable的引用计数赋值给extra_rc,将sidetable的引用计数清空,has_sidetable_rc赋值为0。
九、dealloc源码分析

打开objc4-838.1源码,搜索- (void)dealloc方法
23.png 24.png rootDealloc去判定:
1.如果isa是nonpointer_isa、没有弱引用、没有关联对象、没有析构函数、没有向sidetable借位,则去直接开释free;
2.否则调用object_dispose
object_dispose的逻辑:
1.如果有c++析构函数,去调用析构函数开释该对象的实例成员变量;
2.如果有关联对象,去移除关联对象;
3.扫除弱引用表和散列表里引用计数信息
4.开释对象 free
dealloc源码调用流程:dealloc --> _objc_rootDealloc --> objc_object::rootDealloc()  --> 两个分支
1. -> free
2. -> object_dispose -> free
dealloc总结:

  • 1.判定对象是否为taggedPointer范例,如果是则 return;
  • 2.判定如果isa是nonpointer_isa、没有弱引用、没有关联对象、没有析构函数、没有向sidetable借位,则直接开释对象free。否则进入3;
  • 3.如果有c++析构函数,去调用析构函数开释该对象的实例成员变量;
  • 4.如果有关联对象,去移除关联对象;
  • 5.扫除弱引用表和散列表里引用计数信息;
  • 6.开释对象free。
说到这里本章节就竣事啦,喜好的朋侪点亮⭐️!
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-12-4 16:39, Processed in 0.229324 second(s), 36 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表