为什么仅在第一次返回 __strong 和 __autoreleasing 变量后才会产生不同的对象生命周期?
Why does returning a __strong and __autoreleasing variable yields different object lifecycle only after the first time?
考虑这段代码:
@implementation MyClass
-(void)dealloc {
NSLog(@"MyClass dealloc: %@", self);
}
@end
@implementation AppDelegate
__weak static MyClass *weakShared = nil;
- (MyClass *)getMyClass {
MyClass *tmpHolder = [[MyClass alloc] init]; // PREPEND "__autoreleasing"
weakShared = tmpHolder;
return weakShared; // ATTENTION TO THIS LINE
}
- (void)logMyClass:(NSUInteger)i {
MyClass *mc = [self getMyClass];
NSLog(@"(%@) this is MyClass: %@", @(i), mc);
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
@autoreleasepool {
for (int i = 0; i < 10; i++) {
[self logMyClass:i];
}
NSLog(@"end");
}
NSLog(@"outside pool");
}
注意标有// ATTENTION TO THIS LINE
的行。
我可以用 3 种口味替换该行:
return weakShared
(__weak)
return tmpHolder
(__strong)
return tmpHolder
并将 __autoreleasing
添加到上述 tmpHolder
变量。
以上每种情况的输出为:
1.
(0) this is MyClass: <MyClass: 0x600000020490>
MyClass dealloc: <MyClass: 0x600000020490>
(1) this is MyClass: <MyClass: 0x600000024270>
MyClass dealloc: <MyClass: 0x600000024270>
(2) this is MyClass: <MyClass: 0x600000024270>
MyClass dealloc: <MyClass: 0x600000024270>
end
outside pool
2.
(0) this is MyClass: <MyClass: 0x600000010570>
(1) this is MyClass: <MyClass: 0x600000010530>
MyClass dealloc: <MyClass: 0x600000010530>
(2) this is MyClass: <MyClass: 0x600000010530>
MyClass dealloc: <MyClass: 0x600000010530>
end
MyClass dealloc: <MyClass: 0x600000010570>
outside pool
3.
(0) this is MyClass: <MyClass: 0x600000020060>
(1) this is MyClass: <MyClass: 0x600000020040>
(2) this is MyClass: <MyClass: 0x600000020030>
end
MyClass dealloc: <MyClass: 0x600000020030>
MyClass dealloc: <MyClass: 0x600000020040>
MyClass dealloc: <MyClass: 0x600000020060>
outside pool
我的问题:为什么情况 2 会这样?我希望它在 returning 之前自动释放我的变量,因此表现得像 3;或者只是 return 它没有自动释放并且表现得像 1.
注意:您将需要 -Os
标志来重现上述示例。
长话短说:
在第 0 次迭代中,由于 ARC 辅助函数的惰性绑定破坏了 return 值优化,对象被放入自动释放池中。剩下的尽快释放,因为符号已经绑定了
案例 2 中的弱引用是红色鲱鱼。删除 weakShared
变量后,您可以获得相同的行为。
@implementation AppDelegate
-(MyClass*)getMyClass {
MyClass* tmpHolder = [[MyClass alloc] init];
return tmpHolder;
}
...
应用 ARC 后的 Objective-C 代码如下所示:
MyClass* "-[AppDelegate getMyClass]"(AppDelegate* self, SEL _cmd) {
MyClass* tmpHolder = [[MyClass alloc] init];
return objc_autoreleaseReturnValue(tmpHolder);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
不同于pre-ARC -autorelease
方法,objc_autoreleaseReturnValue()
function会不会将对象直接移动到自动释放池。它将检查调用者的汇编指令,如果调用者要立即“-retain
”这个值,我们将跳过自动释放池和return直接+1的对象。
void "-[AppDelegate logMyClass:]"(AppDelegate* self, SEL _cmd, NSUInteger i) {
MyClass* mc = objc_retainAutoreleasedReturnValue([self getMyClass]);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
NSNumber* ii = objc_retainAutoreleasedReturnValue([NSNumber numberWithInt:i]);
NSLog(@"(%@) this is MyClass: %@", ii, mc);
objc_release(ii);
objc_release(mc);
}
由于这种行为,在迭代 1 到 9 中,objc_autoreleaseReturnValue
和 objc_retainAutoreleasedReturnValue
方法变为无操作,并且那些 MyClass
实例在 -logMyClass:
.
(也有详细说明此 return 优化如何在 How does objc_retainAutoreleasedReturnValue work? by Matt Galloway 上起作用。)
但是第 0 次迭代发生了什么?
我们可以阅读 implementation of callerAcceptsOptimizedReturn
,它描述了 objc_autoreleaseReturnValue
如何确定调用者将 "immediately retain"。简而言之,它将确保调用者在调用后立即具有以下指令:
48 89 c7 movq %rax, %rdi
e8 __ __ __ __ callq <something>
取消引用 <something>
应该指向
ff 25 __ __ __ __ jmpq *<symbol>
其中<symbol>
应该是objc_retainAutoreleasedReturnValue
的函数指针。但是,如果您 运行 调试器中的程序并跟踪 objc_autoreleaseReturnValue
,您会发现 <symbol>
不是 而不是 objc_retainAutoreleasedReturnValue
第一个电话!
原因是objc_retainAutoreleasedReturnValue
是惰性符号(__DATA,__la_symbol_ptr
)。这是 the default behavior when linking to an external dynamic library. Before calling through <symbol>
, the dynamic linker 不会将其解析为正确的函数指针。
事实上,如果您通过添加 -bind_at_load
linker flag 禁用惰性绑定行为,代码的行为将与 "case 1"
相同
$ clang -fobjc-arc -framework Foundation -bind_at_load -Og 1.m
$ ./a.out
2018-05-30 19:25:58.838 a.out[4923:19498647] (0) this is MyClass: <MyClass: 0x7fa392400200>
2018-05-30 19:25:58.838 a.out[4923:19498647] MyClass dealloc: <MyClass: 0x7fa392400200>
2018-05-30 19:25:58.838 a.out[4923:19498647] (1) this is MyClass: <MyClass: 0x7fa392400200>
2018-05-30 19:25:58.838 a.out[4923:19498647] MyClass dealloc: <MyClass: 0x7fa392400200>
...
2018-05-30 19:25:58.839 a.out[4923:19498647] (9) this is MyClass: <MyClass: 0x7fa392600400>
2018-05-30 19:25:58.839 a.out[4923:19498647] MyClass dealloc: <MyClass: 0x7fa392600400>
2018-05-30 19:25:58.839 a.out[4923:19498647] end
2018-05-30 19:25:58.839 a.out[4923:19498647] outside pool
$
由于这个问题在整个程序生命周期中只发生一次,这可能是行为保持不变的原因。
显示延迟加载行为的 LLDB 脚本:
(lldb) target create "a.out"
(lldb) b objc_autoreleaseReturnValue
Breakpoint 1: where = libobjc.A.dylib`objc_autoreleaseReturnValue, address = 0x000000000000cc6f
(lldb) r
Process 4580 launched: '~/a.out' (x86_64)
1 location added to breakpoint 1
Process 4580 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.2
frame #0: 0x00007fff4fe52d1d libobjc.A.dylib`objc_autoreleaseReturnValue
libobjc.A.dylib`objc_autoreleaseReturnValue:
-> 0x7fff4fe52d1d <+0>: pushq %rbp
0x7fff4fe52d1e <+1>: movq %rsp, %rbp
0x7fff4fe52d21 <+4>: movq 0x8(%rbp), %rax
0x7fff4fe52d25 <+8>: cmpl [=16=]xe8c78948, (%rax) ; imm = 0xE8C78948
Target 0: (a.out) stopped.
(lldb) disass
libobjc.A.dylib`objc_autoreleaseReturnValue:
-> 0x7fff4fe52d1d <+0>: pushq %rbp
0x7fff4fe52d1e <+1>: movq %rsp, %rbp
0x7fff4fe52d21 <+4>: movq 0x8(%rbp), %rax
0x7fff4fe52d25 <+8>: cmpl [=16=]xe8c78948, (%rax) ; imm = 0xE8C78948
0x7fff4fe52d2b <+14>: jne 0x7fff4fe52d64 ; <+71>
0x7fff4fe52d2d <+16>: movslq 0x4(%rax), %rcx
0x7fff4fe52d31 <+20>: movzwl 0x8(%rax,%rcx), %edx
0x7fff4fe52d36 <+25>: cmpl [=16=]x25ff, %edx ; imm = 0x25FF
0x7fff4fe52d3c <+31>: jne 0x7fff4fe52d64 ; <+71>
0x7fff4fe52d3e <+33>: leaq 0x8(%rax,%rcx), %rax
0x7fff4fe52d43 <+38>: movslq 0x2(%rax), %rcx
0x7fff4fe52d47 <+42>: movq 0x6(%rax,%rcx), %rax
0x7fff4fe52d4c <+47>: leaq 0x14e65(%rip), %rcx ; objc_unsafeClaimAutoreleasedReturnValue
0x7fff4fe52d53 <+54>: cmpq %rcx, %rax
0x7fff4fe52d56 <+57>: je 0x7fff4fe52d6a ; <+77>
0x7fff4fe52d58 <+59>: leaq -0x17ef(%rip), %rcx ; objc_retainAutoreleasedReturnValue
0x7fff4fe52d5f <+66>: cmpq %rcx, %rax
0x7fff4fe52d62 <+69>: je 0x7fff4fe52d6a ; <+77>
0x7fff4fe52d64 <+71>: popq %rbp
0x7fff4fe52d65 <+72>: jmp 0x7fff4fe52920 ; objc_autorelease
0x7fff4fe52d6a <+77>: movq [=16=]x1, %gs:0x160
0x7fff4fe52d77 <+90>: movq %rdi, %rax
0x7fff4fe52d7a <+93>: popq %rbp
0x7fff4fe52d7b <+94>: retq
(lldb) b 0x7fff4fe52d5f
Breakpoint 2: where = libobjc.A.dylib`objc_autoreleaseReturnValue + 66, address = 0x00007fff4fe52d5f
(lldb) br del 1
1 breakpoints deleted; 0 breakpoint locations disabled.
(lldb) br com add 2
Enter your debugger command(s). Type 'DONE' to end.
> p/x $rax
> p/x $rcx
> c
> DONE
(lldb) c
Process 4580 resuming
(lldb) p/x $rax
(unsigned long) [=16=] = 0x0000000100000e7e
(lldb) p/x $rcx
(unsigned long) = 0x00007fff4fe51570
(lldb) c
Process 4580 resuming
Command #3 'c' continued the target.
2018-05-30 19:09:38.677022+0800 a.out[4580:19476452] (0) this is MyClass: <MyClass: 0x100103850>
(lldb) p/x $rax
(unsigned long) = 0x00007fff4fe51570
(lldb) p/x $rcx
(unsigned long) = 0x00007fff4fe51570
(lldb) c
Process 4580 resuming
Command #3 'c' continued the target.
2018-05-30 19:09:38.685472+0800 a.out[4580:19476452] (1) this is MyClass: <MyClass: 0x100200050>
2018-05-30 19:09:38.685565+0800 a.out[4580:19476452] MyClass dealloc: <MyClass: 0x100200050>
...
考虑这段代码:
@implementation MyClass
-(void)dealloc {
NSLog(@"MyClass dealloc: %@", self);
}
@end
@implementation AppDelegate
__weak static MyClass *weakShared = nil;
- (MyClass *)getMyClass {
MyClass *tmpHolder = [[MyClass alloc] init]; // PREPEND "__autoreleasing"
weakShared = tmpHolder;
return weakShared; // ATTENTION TO THIS LINE
}
- (void)logMyClass:(NSUInteger)i {
MyClass *mc = [self getMyClass];
NSLog(@"(%@) this is MyClass: %@", @(i), mc);
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
@autoreleasepool {
for (int i = 0; i < 10; i++) {
[self logMyClass:i];
}
NSLog(@"end");
}
NSLog(@"outside pool");
}
注意标有// ATTENTION TO THIS LINE
的行。
我可以用 3 种口味替换该行:
return weakShared
(__weak)return tmpHolder
(__strong)return tmpHolder
并将__autoreleasing
添加到上述tmpHolder
变量。
以上每种情况的输出为:
1.
(0) this is MyClass: <MyClass: 0x600000020490>
MyClass dealloc: <MyClass: 0x600000020490>
(1) this is MyClass: <MyClass: 0x600000024270>
MyClass dealloc: <MyClass: 0x600000024270>
(2) this is MyClass: <MyClass: 0x600000024270>
MyClass dealloc: <MyClass: 0x600000024270>
end
outside pool
2.
(0) this is MyClass: <MyClass: 0x600000010570>
(1) this is MyClass: <MyClass: 0x600000010530>
MyClass dealloc: <MyClass: 0x600000010530>
(2) this is MyClass: <MyClass: 0x600000010530>
MyClass dealloc: <MyClass: 0x600000010530>
end
MyClass dealloc: <MyClass: 0x600000010570>
outside pool
3.
(0) this is MyClass: <MyClass: 0x600000020060>
(1) this is MyClass: <MyClass: 0x600000020040>
(2) this is MyClass: <MyClass: 0x600000020030>
end
MyClass dealloc: <MyClass: 0x600000020030>
MyClass dealloc: <MyClass: 0x600000020040>
MyClass dealloc: <MyClass: 0x600000020060>
outside pool
我的问题:为什么情况 2 会这样?我希望它在 returning 之前自动释放我的变量,因此表现得像 3;或者只是 return 它没有自动释放并且表现得像 1.
注意:您将需要 -Os
标志来重现上述示例。
长话短说:
在第 0 次迭代中,由于 ARC 辅助函数的惰性绑定破坏了 return 值优化,对象被放入自动释放池中。剩下的尽快释放,因为符号已经绑定了
案例 2 中的弱引用是红色鲱鱼。删除 weakShared
变量后,您可以获得相同的行为。
@implementation AppDelegate
-(MyClass*)getMyClass {
MyClass* tmpHolder = [[MyClass alloc] init];
return tmpHolder;
}
...
应用 ARC 后的 Objective-C 代码如下所示:
MyClass* "-[AppDelegate getMyClass]"(AppDelegate* self, SEL _cmd) {
MyClass* tmpHolder = [[MyClass alloc] init];
return objc_autoreleaseReturnValue(tmpHolder);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
不同于pre-ARC -autorelease
方法,objc_autoreleaseReturnValue()
function会不会将对象直接移动到自动释放池。它将检查调用者的汇编指令,如果调用者要立即“-retain
”这个值,我们将跳过自动释放池和return直接+1的对象。
void "-[AppDelegate logMyClass:]"(AppDelegate* self, SEL _cmd, NSUInteger i) {
MyClass* mc = objc_retainAutoreleasedReturnValue([self getMyClass]);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
NSNumber* ii = objc_retainAutoreleasedReturnValue([NSNumber numberWithInt:i]);
NSLog(@"(%@) this is MyClass: %@", ii, mc);
objc_release(ii);
objc_release(mc);
}
由于这种行为,在迭代 1 到 9 中,objc_autoreleaseReturnValue
和 objc_retainAutoreleasedReturnValue
方法变为无操作,并且那些 MyClass
实例在 -logMyClass:
.
(也有详细说明此 return 优化如何在 How does objc_retainAutoreleasedReturnValue work? by Matt Galloway 上起作用。)
但是第 0 次迭代发生了什么?
我们可以阅读 implementation of callerAcceptsOptimizedReturn
,它描述了 objc_autoreleaseReturnValue
如何确定调用者将 "immediately retain"。简而言之,它将确保调用者在调用后立即具有以下指令:
48 89 c7 movq %rax, %rdi
e8 __ __ __ __ callq <something>
取消引用 <something>
应该指向
ff 25 __ __ __ __ jmpq *<symbol>
其中<symbol>
应该是objc_retainAutoreleasedReturnValue
的函数指针。但是,如果您 运行 调试器中的程序并跟踪 objc_autoreleaseReturnValue
,您会发现 <symbol>
不是 而不是 objc_retainAutoreleasedReturnValue
第一个电话!
原因是objc_retainAutoreleasedReturnValue
是惰性符号(__DATA,__la_symbol_ptr
)。这是 the default behavior when linking to an external dynamic library. Before calling through <symbol>
, the dynamic linker 不会将其解析为正确的函数指针。
事实上,如果您通过添加 -bind_at_load
linker flag 禁用惰性绑定行为,代码的行为将与 "case 1"
$ clang -fobjc-arc -framework Foundation -bind_at_load -Og 1.m
$ ./a.out
2018-05-30 19:25:58.838 a.out[4923:19498647] (0) this is MyClass: <MyClass: 0x7fa392400200>
2018-05-30 19:25:58.838 a.out[4923:19498647] MyClass dealloc: <MyClass: 0x7fa392400200>
2018-05-30 19:25:58.838 a.out[4923:19498647] (1) this is MyClass: <MyClass: 0x7fa392400200>
2018-05-30 19:25:58.838 a.out[4923:19498647] MyClass dealloc: <MyClass: 0x7fa392400200>
...
2018-05-30 19:25:58.839 a.out[4923:19498647] (9) this is MyClass: <MyClass: 0x7fa392600400>
2018-05-30 19:25:58.839 a.out[4923:19498647] MyClass dealloc: <MyClass: 0x7fa392600400>
2018-05-30 19:25:58.839 a.out[4923:19498647] end
2018-05-30 19:25:58.839 a.out[4923:19498647] outside pool
$
由于这个问题在整个程序生命周期中只发生一次,这可能是行为保持不变的原因。
显示延迟加载行为的 LLDB 脚本:
(lldb) target create "a.out" (lldb) b objc_autoreleaseReturnValue Breakpoint 1: where = libobjc.A.dylib`objc_autoreleaseReturnValue, address = 0x000000000000cc6f (lldb) r Process 4580 launched: '~/a.out' (x86_64) 1 location added to breakpoint 1 Process 4580 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.2 frame #0: 0x00007fff4fe52d1d libobjc.A.dylib`objc_autoreleaseReturnValue libobjc.A.dylib`objc_autoreleaseReturnValue: -> 0x7fff4fe52d1d <+0>: pushq %rbp 0x7fff4fe52d1e <+1>: movq %rsp, %rbp 0x7fff4fe52d21 <+4>: movq 0x8(%rbp), %rax 0x7fff4fe52d25 <+8>: cmpl [=16=]xe8c78948, (%rax) ; imm = 0xE8C78948 Target 0: (a.out) stopped. (lldb) disass libobjc.A.dylib`objc_autoreleaseReturnValue: -> 0x7fff4fe52d1d <+0>: pushq %rbp 0x7fff4fe52d1e <+1>: movq %rsp, %rbp 0x7fff4fe52d21 <+4>: movq 0x8(%rbp), %rax 0x7fff4fe52d25 <+8>: cmpl [=16=]xe8c78948, (%rax) ; imm = 0xE8C78948 0x7fff4fe52d2b <+14>: jne 0x7fff4fe52d64 ; <+71> 0x7fff4fe52d2d <+16>: movslq 0x4(%rax), %rcx 0x7fff4fe52d31 <+20>: movzwl 0x8(%rax,%rcx), %edx 0x7fff4fe52d36 <+25>: cmpl [=16=]x25ff, %edx ; imm = 0x25FF 0x7fff4fe52d3c <+31>: jne 0x7fff4fe52d64 ; <+71> 0x7fff4fe52d3e <+33>: leaq 0x8(%rax,%rcx), %rax 0x7fff4fe52d43 <+38>: movslq 0x2(%rax), %rcx 0x7fff4fe52d47 <+42>: movq 0x6(%rax,%rcx), %rax 0x7fff4fe52d4c <+47>: leaq 0x14e65(%rip), %rcx ; objc_unsafeClaimAutoreleasedReturnValue 0x7fff4fe52d53 <+54>: cmpq %rcx, %rax 0x7fff4fe52d56 <+57>: je 0x7fff4fe52d6a ; <+77> 0x7fff4fe52d58 <+59>: leaq -0x17ef(%rip), %rcx ; objc_retainAutoreleasedReturnValue 0x7fff4fe52d5f <+66>: cmpq %rcx, %rax 0x7fff4fe52d62 <+69>: je 0x7fff4fe52d6a ; <+77> 0x7fff4fe52d64 <+71>: popq %rbp 0x7fff4fe52d65 <+72>: jmp 0x7fff4fe52920 ; objc_autorelease 0x7fff4fe52d6a <+77>: movq [=16=]x1, %gs:0x160 0x7fff4fe52d77 <+90>: movq %rdi, %rax 0x7fff4fe52d7a <+93>: popq %rbp 0x7fff4fe52d7b <+94>: retq (lldb) b 0x7fff4fe52d5f Breakpoint 2: where = libobjc.A.dylib`objc_autoreleaseReturnValue + 66, address = 0x00007fff4fe52d5f (lldb) br del 1 1 breakpoints deleted; 0 breakpoint locations disabled. (lldb) br com add 2 Enter your debugger command(s). Type 'DONE' to end. > p/x $rax > p/x $rcx > c > DONE (lldb) c Process 4580 resuming (lldb) p/x $rax (unsigned long) [=16=] = 0x0000000100000e7e (lldb) p/x $rcx (unsigned long) = 0x00007fff4fe51570 (lldb) c Process 4580 resuming Command #3 'c' continued the target. 2018-05-30 19:09:38.677022+0800 a.out[4580:19476452] (0) this is MyClass: <MyClass: 0x100103850> (lldb) p/x $rax (unsigned long) = 0x00007fff4fe51570 (lldb) p/x $rcx (unsigned long) = 0x00007fff4fe51570 (lldb) c Process 4580 resuming Command #3 'c' continued the target. 2018-05-30 19:09:38.685472+0800 a.out[4580:19476452] (1) this is MyClass: <MyClass: 0x100200050> 2018-05-30 19:09:38.685565+0800 a.out[4580:19476452] MyClass dealloc: <MyClass: 0x100200050> ...