以下的测试均基于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
函数,增加了对象的引用计数。相关的汇编代码如下:
第一个
objc_msgSend
为alloc
,第二个objc_msgSend
为init
,下面依次是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
,然后赋值给x29
。sp
中的值为0x000000016fd6fa10
。则x29
为0x000000016fd6fa30
。 -
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);
}
可以看到,该函数需要两个参数。
第一个参数为二级指针。而此时x0
为0x000000016fd6fa28
,上面我们说过,如果这个值作为地址,该地址的值的前8个字节(一个指针的宽度)是空。那么也就是说,该函数中id prev为空。
第二个参数为OC
对象。此时x1
的值为指针s
。
可以看到,obj != prev
,因此执行objc_retain(obj)
。Son
对象的引用计数加1,为2。
此时0x000000016fd6fa28
作为地址,值的前8个字节不再是空,而是指向Son
对象的指针。
汇编中间打印部分我们不讨论,现在,跳到第20
行。
-
20
行,x8
置空。 -
21
行,取x29
的值减去0x8
后的值,赋给x9
。x9
为0x000000016fd6fa28
。此时该值作为地址,该地址的值的前8个字节,在经过第11行的函数objc_storeStrong
后,已经不再是空,而是一个指针,指向Son
对象。 -
22~23
行,x0
为0x000000016fd6fa28
,x1
为0x0
。 -
24
行,调用objc_storeStrong
函数。
此时第一个参数为二级指针,取值为id prev,prev为指向
Son
对象的指针。
第二个参数obj
为空。
可以看到,obj != prev
,因此执行objc_retain(nil)
,然后0x000000016fd6fa28
作为地址,该地址的值的前8个字节置为空,最后objc_release(prev)
,即Son
对象的引用计数减1。
结论 : OC对象参数在进入函数时,会增加引用计数,在函数结束时,会减少引用计数。
网友评论