[iOS开发]ARC基础知识和Strong部分的实现

1,097 阅读20分钟

内存管理四大原则

  1. 自己生成的对象自己持有
  2. 非自己生成的对象自己也能持有
  3. 不再需要自己持有的对象时释放
  4. 非自己持有的对象无法释放

iOS底层对内存管理的方案

  1. taggedPointer :很熟悉了存储小的对象如NSNumber
  2. NONPOINTER_ISA :在 64 位架构下,isa 指针是占 64 比特位的,实际上只有 30 多位就 已经够用了,为了提高利用率,剩余的比特位存储了内存管理的相关数据内容。 nonpointer: 表示是否对 isa 指针开启指针优化 • 0: 纯 isa 指针 • 1: 不止是类对象地址, isa 中包含了类信息、对象的引用计数等
  3. 散列表:复杂的数据结构 :复杂的数据结构,包括了引用计数表和弱引用表 通过 SideTables()结构来实现的,SideTables()结构下,有很多 SideTable 的数据结构。 而 sideTable 当中包含了自旋锁,引用计数表,弱引用表。 SideTables()实际上是一个哈希表,通过对象的地址来计算该对象的引用计数在哪个 sideTable 中。

ARC规则

__strong修饰符

__strong修饰符是id类型和对象类型==默认的==所有权修饰符。

不论调用哪种方法,强引用修饰的变量会持有该对象,如果已经持有则引用计数不会增加。

对象的所有者和对象的生命周期

持有强引用的变量在超出其作用域时被废弃 随着强引用的失效 引用的对象会随之释放

__strong对象相互赋值

__strong修饰符的变量不仅只在变量作用域中,在赋值上也能够正确的管理其对象的所有者。

id __strong obj0 = [[NSObject alloc] init];//生成对象A			
id __strong obj1 = [[NSObject alloc] init];//生成对象B		
id __strong obj2 = nil;
obj0 = obj1;//obj0强引用对象B;而对象A不再被ojb0引用,被废弃
obj2 = obj0;//obj2强引用对象B(现在obj0,ojb1,obj2都强引用对象B)	
obj1 = nil;//obj1不再强引用对象B	
obj0 = nil;//obj0不再强引用对象B	
obj2 = nil;//obj2不再强引用对象B,不再有任何强引用引用对象B,对象B被废弃

即如下表格 在这里插入图片描述 赋值的本质是强引用的转变

方法参数中使用__strong

==废弃Test对象的同时,Test对象的obj_成员变量也被废除==

==即成员变量的生存周期是与对象同步的==

__strong导致的循环引用

内存泄漏:在内存该被释放的时候没有释放,导致内存被浪费使用了

我们还是以方法参数中的例子来说

Test类中有一个强引用类型的成员变量obj_ 同时设置其set方法

@interface Test : NSObject {
    id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end

#import "Test.h"

@implementation Test
- (id)init {
    self = [super init];
    return self;
}

- (void)setObject:(id __strong)obj {
    obj_ = obj;
}
@end

主函数

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id test0 = [[Test alloc] init];//生成TestA
        id test1 = [[Test alloc] init];//生成TestB
        [test0 setObject:test1];
        [test1 setObject:test0];
        //NSLog(@"%lu",CFGetRetainCount((__bridge CFTypeRef)test0));
    }
    return 0;
}

我们声明两个对象 test0和test1

其对应内部也有自己的成员变量obj_

通过set方法给 两个对象的成员变量分别赋值另一个对象所持有的TestA/TestB对象

我们就会造成如下结果

test0 持有 TestA test0.obj 持有 TestB test1 持有 TestB test1.obj 持有 TestA

失效阶段的表示

在这里插入图片描述 testA 不能dealloc 因为test1.obj还持有testA 想要废除test1.obj就是要遵行成员变量的生命周期是与对象同步的这个观点 所以我们需要废除testB

而对于testB来说也是这样 想废除test0.obj就是要废除testA

所以大家都是强引用 我为什么要让你

这就造成循环引用的问题

__weak修饰符

紧接着__strong 来学习一下__weak修饰符

__strong 强引用 () __weak 弱引用(引用计数不会加一 对象随时可能会被dealloc) 内部使用__autoreleasing来维持该对象不被dealloc

对象的引用计数是记录在一张表上的,不在对象本身或者指针中,系统通过访问这张表来确定是否释放该对象。

将上面相互引用例子中的成员变量变为weak,即可避免相互引用。

weak还有个作用。在持有某对象的弱引用时,若该对象被废弃,则此若引用将自动失效且处于nil被赋值的状态(空弱引用)。 在这里插入图片描述 weak提供弱引用,弱引用不持有对象,NSObject对象会被销毁 所以会报一个警告

我们可以像下面这样将对象赋值给附有__strong修饰符的变量之后再赋值给附有__weak修饰符的变量,就不会发生警告了

__weak修饰符的引用计数的问题

那么问题来了 下面这两个的引用计数的值是多少呢

id __strong obj0 = [[NSObject alloc] init];//生成对象A
        id __weak obj1 = obj0;
        NSLog(@"%lu",CFGetRetainCount((__bridge CFTypeRef)obj0));
        NSLog(@"%lu",CFGetRetainCount((__bridge CFTypeRef)obj1));

按常理应该是1 1 __weak修饰符不持有对象 引用计数值两个都应该为1

打印一下结果

在这里插入图片描述

芜湖 一个1 一个2 __weak修饰的反而为2了

这是为什么呢?

打开汇编看一下

在这里插入图片描述

给两个NSLog加上断点看一下

第一个NSLog

在这里插入图片描述

第二个NSLog

在这里插入图片描述

对比一下就能发现 两行NSLog的汇编代码并不一样

第二个__weak修饰符的NSLog在于开始先loadWeakRetained 然后在打印结束后有一个release操作

所以在打印__weak的引用计数时 NSLog先将其以强引用 为了安全起见,如果不强引用,防止万一还没打印就被释放了。在NSLog结束时,会调用objc_release使引用计数减一。

__unsafe_unretained

__unsafe_unretained修饰符正如其名unsafe所示,是不安全的所有权修饰符。 附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。

为什么说是不安全呢

  1. weak 修饰的指针变量,在指向的内存地址销毁后,会在 Runtime 的机制下,自动置为 nil。 _Unsafe_Unretain 不会置为 nil,容易出现 悬垂指针,发生崩溃。但是 _Unsafe_Unretain 比 __weak 效率高。 悬垂指针 指针指向的内存已经被释放了,但是指针还存在,这就是一个 悬垂指针 或者说 迷途指针。野指针,没有进行初始化的指针,其实都是 野指针

  2. 附有__unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样,生成的对象会立即释放。

在使用__unsafe_unretained修饰符时,赋值给附有__strong修饰符的变量时有必要确保被赋值的对象确实存在,如果不存在,那么程序就会崩溃。

__autoreleasing修饰符

与MRC进行比较

  • MRC中autorelease的使用方法
    1. 生成并持有NSAutoreleasePool对象。
    1. 调用已分配对象的autorelease方法【将对象注册到pool中】
    1. 废弃NSAutoreleasePool对象。

在ARC环境下 __autoreleasing如下 在这里插入图片描述

自动调用

编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是讲自动将返回值的对象注册到autoreleasepool中

下面情况不使用__autoreleasing修饰符也能使对象注册到autoreleasepool中。

+ (id) array {
	return [[NSMutableArray alloc]init];
}

如下:
+ (id) array {
	id obj = [[NSMutableArray alloc]init];
	return obj;
}

由于return使得对象变量超出其作用域,所以该强引用对应的自己持有的对象会被自动释放,但该对象作为函数的返回值,编译器也会自动注册到自动释放池

自动调用时的失效过程

随着obj超出其作用域,强引用失效,所以自动释放自己持有的对象。 同时,随着@autoreleasepool块的结束,注册到autoreleasepool中的所有对象被自动释放。 因为对象的拥有者不存在,所以废弃对象。

weak修饰符与autoreleasing修饰符

书上提到 weak修饰符的实现需要借助autoreleasing修饰符 就是把weak修饰符修饰的对象放到池子中 即

id __strong obj0 = [[NSObject alloc] init];//生成对象A
id __weak obj1 = obj0;

等同于

id __strong obj0 = [[NSObject alloc] init];//生成对象A
id __weak obj1 = obj0;
id __autoreleasing obj2 = obj1;

但通过lldb对自动释放池进行打印

在这里插入图片描述 可以看到__weak的变量并没有把变量放入自动释放池

所以认为作者可能仅仅想提醒我们最好使用自动释放池来对对象进行维护 但这样自动释放池也会改变其引用计数 在池子销毁时会被release

具体ARC规则

规则

  • 不能使用retain/release/retainCount/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 必须遵守内存管理的方法名规则
  • 不要显式调用dealloc
  • 使用@autorelease块代替NSAutoreleasePool
  • 不能使用区域(NSZone)
  • 对象型变量不能作为C语言结构体的成员
  • 显式转换id和void*

不要显式调用dealloc

dealloc==无法释放不属于该对象的一些东西==,需要我们重写时加上去,例如

  • 通知的观察者,或KVO的观察者
  • 对象强委托/引用的解除(例如XMPPMannerger的delegateQueue)
  • 做一些其他的注销之类的操作(关闭程序运行期间没有关闭的资源)

直接打开官方文档

In the implementation of dealloc, do not call the implementation of superclass. You should try to avoid using dealloc to manage the lifetime of limited resources, such as file descriptors. You never send a dealloc message directly. Instead, the dealloc method of the object is called by the runtime.

在dealloc的实现中,不要调用超类的实现。您应该尽量避免使用dealloc管理有限资源(如文件描述符)的生存期。 不要直接发送dealloc消息。与直接发送dealloc消息不同,对象的dealloc方法由运行时调用。

Special Considerations When not using ARC, your implementation of dealloc must invoke the superclass’s implementation as its last instruction.

特别注意事项 当不使用ARC时,dealloc的实现必须调用父类(super)的实现作为它的最后一条指令。

__bridge

在这里插入图片描述

属性关键字与所有权修饰符

在这里插入图片描述

ARC实现

__Strong

自己生成并持有

和Block一样 我们直接Clang转成 LLVM 中间码

OC:

void defaultFunction() {
    id __strong obj0 = [NSObject new];
}

转为LLVM中间码:


define void @defaultFunction() #0 {
  %1 = alloca i8*, align 8
  %2 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
  %3 = bitcast %struct._class_t* %2 to i8*
  %4 = call i8* @objc_opt_new(i8* %3)
  %5 = bitcast i8* %4 to %0*
  %6 = bitcast %0* %5 to i8*
  store i8* %6, i8** %1, align 8
  call void @llvm.objc.storeStrong(i8** %1, i8* null) #1
  ret void
}

仔细分析一下主要就由以下部分组成

id obj0 = objc_alloc_init(i8* %3)
objc_storeStrong(obj0, null)

其实就是两步
新建一个对象
调用storeStrong这个函数

storeStrong

在runtime文件中找到这个函数
如下
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

其做了四件事

  1. 检查输入的 obj 地址 和指针指向的地址是否相同。
  2. 持有对象,引用计数 + 1 。
  3. 指针指向 obj。
  4. 原来指向的对象引用计数 - 1(释放对象)。

==对于这里传入的NULL来说 这就等同于向对象发送release消息==

所以两步变为新建一个obj0对象 作用域结束时候释放这个对象

StoreStrong在赋值时也可以使用 在这里插入图片描述

objc_retain

来学习一下objc_object的具体实现

objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

看下一层

objc_object::retain()
{
    assert(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        return rootRetain();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}

==系统会对是否支持NONPOINTER_ISA进行不同的处理==

每个OC对象都含有一个isa指针,__arm64__之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。

union isa_t 
{
    Class cls;
    uintptr_t bits;
    struct {
         uintptr_t nonpointer        : 1;//->表示使用优化的isa指针
         uintptr_t has_assoc         : 1;//->是否包含关联对象
         uintptr_t has_cxx_dtor      : 1;//->是否设置了析构函数,如果没有,释放对象更快
         uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针
         uintptr_t magic             : 6;//->固定值,用于判断是否完成初始化
         uintptr_t weakly_referenced : 1;//->对象是否被弱引用
         uintptr_t deallocating      : 1;//->对象是否正在销毁
         uintptr_t has_sidetable_rc  : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
        uintptr_t extra_rc          : 19;  //->存储引用计数
    };
};

在这里插入图片描述

支持Nonpointer isa的处理
objc_object::rootRetain()
{
    return rootRetain(false, false);
}

不支持的处理

objc_object::rootRetain()
{
    if (isTaggedPointer()) return (id)this;
    return sidetable_retain();
}

可以看到==不支持Nonpointer isa的处理就是直接sidetable_retain==,这是由于计数都存储在sidetable中了,处理逻辑较支持Nonpointer isa的情况要简单一些。

不支持Nonpointer isa 的处理

去sidetable取出计数信息 执行加一操作

支持Nonpointer isa的处理
  • 首先判断是否为标签指针类型 如果是 直接返回
  • 进入do-while处理逻辑
    1. 先判断是否为 其一定支持Nonpointer isa的架构,但是isa没有额外信息 如果没有额外信息 那就和不支持意义一样(判断是否有优化) 引用计数存储在sidetable中,走sidetable的引用计数+1的流程
    2. 判断对象是否正在释放,如果正在释放则执行dealloc流程。
    3. 有存储额外信息,包含引用计数。我们尝试对isa中的extra_rc++加一进行测试 3.1 如果没有溢出越界的情况,我们将isa的值修改为extra_rc++之后的值 3.2 如果有溢出 将一半的计数存储到extra_rc,另一半存储到sidetable中去 设置设置标志位位true 在这里插入图片描述
retain的总结

==所以如果isa可以存储额外信息,那么有extra_rc位用来存储引用计数,当引用计数满了之后 就会存储到sidetable中 。==

==retain的流程也是针对isa是否支持存储信息分别进行处理==

==当extra_rc存储溢出了,这个时候是一半(extra_rc能表示的最大值+1的一半)在extra_rc一半存储在sidetable中,这里跟release的操作是对应的(extra_rc不够减了也是去sidetable借extra_rc最大值的一半的计数),这样设计的好处避免了频繁的去sidetable中读取计数信息---假如我们溢出了把计数全部存到sidetable中去,那么有release的时候,extra_rc也不够减了,又去借,这就大大降低了效率,比起直接操作isa。==

release
// 真正的release方法
// 两个参数分别是 是否需要调用dealloc函数,是否需要处理 向下溢出的问题
ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    // 如果是TaggedPointer 不需要进行release操作
    if (isTaggedPointer()) return false;
    // 局部变量sideTable是否上锁 默认false
    bool sideTableLocked = false;

    // 两个局部变量用来记录这个对象的isa指针
    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        // 加载这个isa指针
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        // 如果没有进行nonpointer优化
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            // 如果是类对象直接返回false 不需要释放
            if (rawISA()->isMetaClass()) return false;
            // 如果sideTableLocked 则解锁 这里默认是false
            if (sideTableLocked)
                sidetable_unlock();
            // 调用sidetable_release 进行引用计数-1操作
            return sidetable_release(performDealloc);
        }

        // 溢出标记位
        uintptr_t carry;
        // newisa 对象的extra_rc 进行-1操作
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        // 如果-1操作后 向下溢出了 结果为负数
        if (slowpath(carry)) {
            // don't ClearExclusive()
            // 调用underflow 进行向下溢出的处理
            goto underflow;
        }
        //  开启循环,直到 isa.bits 中的值被成功更新成 newisa.bits
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    //走到这说明引用计数的 -1 操作已完成
    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    //newisa的extra_rc在执行-1操作后导致了向下溢出
    // 放弃对newisa的修改 使用之前的oldisa
    newisa = oldisa;

    // 如果 isa 的 has_sidetable_rc 标志位标识引用计数已溢出
    // has_sidetable_rc 用于标识是否当前的引用计数过大,无法在isa中存储,
    // 而需要借用sidetable来存储。(这种情况大多不会发生)
    if (slowpath(newisa.has_sidetable_rc)) {
        // 是否需要处理下溢
        if (!handleUnderflow) {
            // 清除原 isa 中的数据的原子独占
            ClearExclusive(&isa.bits);
            // 如果不需要处理下溢 直接调用 rootRelease_underflow方法
            return rootRelease_underflow(performDealloc);
        }

        // 如果sidetable是上锁状态
        if (!sideTableLocked) {
            // 解除清除原 isa 中的数据的原子独占
            ClearExclusive(&isa.bits);
            // sidetable 上锁
            sidetable_lock();
            sideTableLocked = true;
            // 跳转到 retry 重新开始,避免 isa 从 nonpointer 类型转换成原始类型导致的问题
            goto retry;
        }

        // sidetable_subExtraRC_nolock 放回要从sidetable移动到isa的extra_rc的值
        // 默认是获取extra_rc可存储的长度一半的值
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.
        //  为了避免冲突 has_sidetable_rc 标志位必须保留1的状态 及时sidetable中的个数为0
        if (borrowed > 0) {
            // 将newisa中引用计数值extra_rc 设置为borrowed - 1
            // -1 是因为 本身这次是release操作
            newisa.extra_rc = borrowed - 1;
            // 然后将修改同步到isa中
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            // 如果保存失败
            if (!stored) {
                // 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.
                // 从新装载isa
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                // 如果newisa2是nonpointer类型
                if (newisa2.nonpointer) {
                    // 下溢出标志位
                    uintptr_t overflow;
                    // 将从 SideTables 表中获取的引用计数保存到 newisa2 的 extra_rc 标志位中
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    //
                    if (!overflow) {
                        // 如果没有溢出再次将 isa.bits 中的值更新为 newisa2.bits
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

            // 如果重试之后依然失败
            if (!stored) {
                // 将从sidetable中取出的引用计数borrowed 重新加到sidetable中
                sidetable_addExtraRC_nolock(borrowed);
                // 重新尝试
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            // 完成对 SideTables 表中数据的操作后,为其解锁
            sidetable_unlock();
            return false;
        }
        else {
            // 在从Side table拿出一部分引用计数之后 Side table为空
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

    // 如果当前的对象正在被释放
    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        // 如果sideTableLocked被锁 那么解锁
        if (sideTableLocked) sidetable_unlock();
        // 兑现被过度释放
        return overrelease_error();
        // does not actually return
    }
    // 将对象被释放的标志位置为true
    newisa.deallocating = true;
    // 将newisa同步到isa中 如果失败 进行重试
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))
        goto retry;

    // 如果sideTableLocked= true
    if (slowpath(sideTableLocked))
        // Side table解锁
        sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    // 如果需要执行dealloc方法 那么调用该对象的dealloc方法
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}



在这里插入图片描述

学明白了retain release就很好理解了

  1. 依旧是判断是否为taggedPointer
  2. 判断是否有优化 如果没有 就直接操作散列表,使引用计数-1
  3. 判断是引用计数为否为0 如果是0则执行dealloc流程
  4. 若isa有优化,则对象的isa位建议,且通过carry判断是否向下溢出了 结果为负数(下图有点问题 应该是判断是有向下溢出),如果是,如果到-1 就放弃newisa改为old,并将散列表中一半引用计数取出来,然后将这一半引用计数减一在存到isa的extra_rc
  5. 如果sidetable的引用计数为0,对象进行dealloc流程 在这里插入图片描述

其实和retain一样 不过release操作变成-1 并且需要注意从sidetable中的一半减一放入

retainCount
  1. 当对象的isa经过优化,首先获取isa位域extra_rc中的引用计数,默认会+1(防止你没持有就要打印),uintptr_t rc = 1 + bits.extra_rc;然后获取散列表的引用计数表中的引用计数,两者相加得到对象的最终的引用计数
  2. 当对象的isa没有经过优化,则直接获取散列表的引用计数表中的引用计数,返回。 为什么alloc/new后引用计数值为1 ?
  3. 当我们alloc一个对象时,然后调用retainCount函数,得到对象的引用计数为1。这是因为在底层rootRetainCount方法中,引用计数默认+1了,这里只有对引用计数的读取操作,是没有写入操作的,简单来说就是:为了防止alloc创建的对象被释放(引用计数为0会被释放),所以在编译阶段,程序底层默认进行了+1操作。实际上在extra_rc中的引用计数仍然为0

所以 通过alloc或者new这样赋值来新建一个对象 ARC MRC环境下都是1 这个1是底层默认的返回值加一 没有调用retain 其他强引用 才会调用objc_retain来持有

可以看下图 alloc后rc引用计数热然为0 在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

非自己生成并持有

前面走了不少弯路 其实直接看汇编更简单

        id __strong obj = [NSMutableArray array];      

在这里插入图片描述

上面的正常的都可以理解 还有几个问题

objc_retainAutoreleasedReturnValue为什么非自己生成并持有时会出现?

这个就涉及很多问题了 一点一点来说 Autorelease对象什么时候释放? 对这个问题的第一反应 “当前作用域大括号结束时释放” 其实并不是的

在没有手动加Autorelease Pool的情况下 Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop

done 后面的原理后面再说 先回到正题

objc_retainAutoreleasedReturnValue是啥?

objc_retainAutoreleasedReturnValue 是优化程序运行的,将直接return对象不会注册到autorelease之中。

它的作用就是持有对象,持有的对象来自自动释放池中。因为obj是强引用,所以要持有这个对象,这个方法将来自自动释放池的对象的引用计数+1.使用alloc/new/copy/mutableCopy之外的方法就会插入这条

那么就有这样一个问题 array的对象不是应该放到自动释放池中吗,为什么没有呢 这就是iOS对其进行的优化了

这就要提及他的另一个好兄弟 objc_autoreleaseReturnValue了 我们在哪可以看到它呢 新建一个类 我们在这个类重写一下array方法

在这里插入图片描述 就能看到array时我们自动返回了这个objc_autoreleaseReturnValue了

打开runtime库搜索一下这俩兄弟 在这里插入图片描述 在这里插入图片描述

看一下俩兄弟各自是干嘛的

1. objc_autoreleaseReturnValue
1.1 prepareOptimizedReturn
// Try to prepare for optimized return with the given disposition (+0 or +1).
// Returns true if the optimized path is successful.
// Otherwise the return value must be retained and/or autoreleased as usual.
static ALWAYS_INLINE bool 
prepareOptimizedReturn(ReturnDisposition disposition)
{
    assert(getReturnDisposition() == ReturnAtPlus0);

    if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
        if (disposition) setReturnDisposition(disposition);
        return true;
    }

    return false;
}

核心是判断callerAcceptsOptimizedReturn为true的时候才返回true,开启优化;那么这里是怎么知道需要优化的了,需要看看callerAcceptsOptimizedReturn的具体实现;在这之前先了解下__builtin_return_address和setReturnDisposition的作用。

1.2 __builtin_return_address

搜一下

其作用就是得到函数的返回地址,0--表示返回当前函数的返回地址,1--表示返回当前函数的调用方的返回地址;

1.3 setReturnDisposition

enum ReturnDisposition : bool {
    ReturnAtPlus0 = false, ReturnAtPlus1 = true
};

static ALWAYS_INLINE void 
setReturnDisposition(ReturnDisposition disposition)
{
    tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}

作用就是将disposition的值存储到TLS(Thread Local Storage)中

1.4 callerAcceptsOptimizedReturn

看名字意思就是调用方是否接受优化的返回

当调用方在返回值之后如果调用了objc_retainAutoreleasedReturnValue或者objc_unsafeClaimAutoreleasedReturnValue的时候就表示可以优化;

至此我们就引出来objc_retainAutoreleasedReturnValue这个东西了

2. objc_retainAutoreleasedReturnValue
objc_retainAutoreleasedReturnValue(id obj)
{
    if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;

    return objc_retain(obj);
}
2.1 acceptOptimizedReturn
// Try to accept an optimized return.
// Returns the disposition of the returned object (+0 or +1).
// An un-optimized return is +0.
static ALWAYS_INLINE ReturnDisposition 
acceptOptimizedReturn()
{
    ReturnDisposition disposition = getReturnDisposition();//从TSL中读取对应的值
    setReturnDisposition(ReturnAtPlus0);  // reset to the unoptimized state 将数据置为ReturnAtPlus0
    return disposition;
}

acceptOptimizedReturn区域读取TLS中1.3存储的值,同时读取之后置为ReturnAtPlus0

读取返回值 如果通过1.4的判断 后面有调用 那么此时标志位为ture 那么就直接返回这个对象 否则,就返回objc_retain?(这里还没看懂) 我暂时的理解就是 如果标志位不为ture 就返回到自动缓冲池中让自动缓冲池进行持有

在这里插入图片描述

非自己生成并持有的总结

所以对这两兄弟理解就是

  • objc_retainAutoreleasedReturnValue会检验调用者是否会对该对象执行retain,如果会的话就不执行autorelease,直接设置标志符
  • objc_autoreleaseReturnValue在检验到标志服后,也不retain了,直接返回对象本身