内存管理之函数参数的__strong、引用计数问题

以下的测试均基于ARC

1.__strong修饰符

在如下函数中:

- (void)xx {
    Son *son = [[Son alloc] init];
    Son *s1 = son;
    NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(son)));
}

son的引用计数是多少?

2018-08-10 09:48:12.275410 内存管理[1597:362731] 2

这是因为s1默认为__strong修饰,则编译器自动添加retain函数,底层调用objc_retain函数,增加了对象的引用计数。相关的汇编代码如下:

4DB6D79F-1721-4D54-92D1-A5EB298C2EA5.png

第一个objc_msgSendalloc,第二个objc_msgSendinit,下面依次是objc_retain和打印时调用的函数CFGetRetainCount

2.__weak修饰符

如果添加了__weak会发生什么?

- (void)xx {
    Son *son = [[Son alloc] init];
    __weak Son *s1 = son;
    NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(son)));
}

结果为:

2018-08-10 09:58:20.115245 内存管理[1606:364519] 1

相关汇编如下:


B0F7DFC2-EAC4-410C-878B-2725A6E927BC.png

不再调用objc_retain,取而代之的是objc_initWeak

3.参数的引用计数

如下函数中:

void test(Son *s){
    NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(s)));
}
int main(int argc, char * argv[]) {
    Son *s = [[Son alloc] init];
    test(s);
}

test(Son *s)函数中,s指针不像我们在示例1和示例2中定义的那样,没有明确的__strong__weak修饰符,甚至都无法知道s指针是如何定义的。那么,s会对传递的对象增加引用计数吗?

结果如下:

2018-08-10 10:05:07.315869 内存管理[1611:365666] 2

显然,作为参数传递的对象,进入函数后,会增加一次引用计数。

那么这一切是怎么做的?

7CCF660C-EA78-4967-8219-CC900AF65D20.png
  • 1~3行为拉伸栈空间和保护寄存器,我们不讨论。
  • 4行将sp中的值加上0x20,然后赋值给x29sp中的值为0x000000016fd6fa10。则x290x000000016fd6fa30
  • 5行将x29的值减去0x8,然后赋值给x8。则x8的值为0x000000016fd6fa28
  • 6行将x9中的值置为0x0
  • 7行,取x29的值减去0x8后的值作为地址,将x9中的值0x0写入到该地址的内存中。(结合第5行,也就是说,如果x8的值做为地址,该地址的值的前8个字节(一个指针的宽度)为空。这个转化很重要!!!)
  • 8行,取sp的值加上0x10后的值作为地址,将x0的值写入到该地址的内存空间中。x0寄存器保存函数调用时的第一个参数,那么在这里,它的值就是参数s
    705FB3F3-6412-49DE-8706-1AF8BCD8A872.png
  • 9行,将x8的值给x0。此时x0的值为0x000000016fd6fa28
  • 10行,将sp的值加上0x10后的值作为地址,从该地址的内存空间中取出值赋给x1。此时x1的值为s指针,指向Son对象。
  • 11行,调用objc_storeStrong函数。
void objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

可以看到,该函数需要两个参数。
第一个参数为二级指针。而此时x00x000000016fd6fa28,上面我们说过,如果这个值作为地址,该地址的值的前8个字节(一个指针的宽度)是空。那么也就是说,该函数中id prev为空。
第二个参数为OC对象。此时x1的值为指针s
可以看到,obj != prev,因此执行objc_retain(obj)Son对象的引用计数加1,为2。
此时0x000000016fd6fa28作为地址,值的前8个字节不再是空,而是指向Son对象的指针。

汇编中间打印部分我们不讨论,现在,跳到第20行。

  • 20行,x8置空。
  • 21行,取x29的值减去0x8后的值,赋给x9x90x000000016fd6fa28。此时该值作为地址,该地址的值的前8个字节,在经过第11行的函数objc_storeStrong后,已经不再是空,而是一个指针,指向Son对象。
  • 22~23行,x00x000000016fd6fa28x10x0
  • 24行,调用objc_storeStrong函数。

此时第一个参数为二级指针,取值为id prev,prev为指向Son对象的指针。
第二个参数obj为空。
可以看到,obj != prev,因此执行objc_retain(nil),然后0x000000016fd6fa28作为地址,该地址的值的前8个字节置为空,最后objc_release(prev),即Son对象的引用计数减1。

结论 : OC对象参数在进入函数时,会增加引用计数,在函数结束时,会减少引用计数。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 众所周知,block可以封装一个匿名函数为对象,并捕获上下文所需的数据,并传给目标对象在适当的时候回调。正因为将抽...
    吸血鬼de晚餐阅读 7,947评论 0 1
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 5,826评论 0 9
  • 1.1 什么是自动引用计数 概念:在 LLVM 编译器中设置 ARC(Automaitc Reference Co...
    __silhouette阅读 10,654评论 1 17
  • 我是一个内向的人,小学路上埋着头扶着墙根走,一个胡同的大人也不敢打招呼!后来去寄宿制学校上初中和高中,情况稍有好转...
    笔尖蜗牛阅读 1,755评论 2 1
  • 上午在图书馆他终于告诉我他喜欢上别的姑娘了 就是我的第六感觉得的那个姑娘 因为任性分手的第十天了 流着眼泪让...
    再见吧喵小哥阅读 864评论 0 0