为什么返回__strong,只有在第一时间之后__autoreleasing可变收益率不同的对象生命周期?

ewc*_*wcy 4 macos memory-management objective-c ios automatic-ref-counting

考虑一下这段代码:

@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");
}
Run Code Online (Sandbox Code Playgroud)

注意标有的线// ATTENTION TO THIS LINE.

我可以用3种口味替换那条线:

  1. return weakShared (__弱)
  2. return tmpHolder (__强大)
  3. 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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

我的问题:为什么案例2的行为如此?我希望它能在返回之前自动释放我的变量,因此表现得像3; 或者只是在没有自动释放的情况下返回它并且表现得像1.

注意:您需要使用该-Os标志来重现上述示例.

ken*_*ytm 5

TL; DR:

在迭代0中,由于ARC辅助函数的延迟绑定打破了返回值优化,因此将对象放入自动释放池中.其余的都会尽快释放,因为符号已被绑定.


案例2中的弱引用是红鲱鱼.删除weakShared变量后,您可以获得相同的行为.

@implementation AppDelegate
-(MyClass*)getMyClass {
    MyClass* tmpHolder = [[MyClass alloc] init];
    return tmpHolder;
}
...
Run Code Online (Sandbox Code Playgroud)

应用ARC后的Objective-C代码如下所示:

MyClass* "-[AppDelegate getMyClass]"(AppDelegate* self, SEL _cmd) {
    MyClass* tmpHolder = [[MyClass alloc] init];
    return objc_autoreleaseReturnValue(tmpHolder);
//         ~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
Run Code Online (Sandbox Code Playgroud)

不同于预ARC -autorelease的方法,该objc_autoreleaseReturnValue()功能直接移动物体进入自动释放池.它将检查调用者的汇编指令,如果调用者将立即" -retain"该值,我们将跳过自动释放池并直接返回+ 1'ed对象.

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);
}
Run Code Online (Sandbox Code Playgroud)

由于这种行为,在迭代1到9中,objc_autoreleaseReturnValueobjc_retainAutoreleasedReturnValue方法变为无操作,并且这些MyClass实例在结束时立即被销毁-logMyClass:.

(还有关于这种返回优化如何在objc_retainAutoreleasedReturnValue如何工作的详细解释由Matt Galloway 撰写.)


但是在迭代0发生了什么?

我们可以阅读其实现callerAcceptsOptimizedReturn描述了如何objc_autoreleaseReturnValue确定调用者将"立即保留".简而言之,它将确保呼叫者在通话后立即获得以下指示:

48 89 c7            movq %rax, %rdi
e8 __ __ __ __      callq <something>
Run Code Online (Sandbox Code Playgroud)

解除引用<something>应指向的地方

ff 25 __ __ __ __   jmpq *<symbol>
Run Code Online (Sandbox Code Playgroud)

这里<symbol>应该是函数指针objc_retainAutoreleasedReturnValue.但是,如果你在一个调试器和跟踪运行程序objc_autoreleaseReturnValue,你会发现,<symbol>不是 objc_retainAutoreleasedReturnValue在第一次通话!

原因是它objc_retainAutoreleasedReturnValue是一个懒惰的符号(__DATA,__la_symbol_ptr).这是链接到外部动态库时的默认行为.在调用之前<symbol>,动态链接器不会将其解析为正确的函数指针.

实际上,如果通过添加-bind_at_load链接器标志来禁用延迟绑定行为,则代码将与"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
$
Run Code Online (Sandbox Code Playgroud)

由于此问题仅在整个程序生命周期中发生一次,这可能是行为保持不变的原因.


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   $0xe8c78948, (%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   $0xe8c78948, (%rax)       ; imm = 0xE8C78948 
    0x7fff4fe52d2b <+14>: jne    0x7fff4fe52d64            ; <+71>
    0x7fff4fe52d2d <+16>: movslq 0x4(%rax), %rcx
    0x7fff4fe52d31 <+20>: movzwl 0x8(%rax,%rcx), %edx
    0x7fff4fe52d36 <+25>: cmpl   $0x25ff, %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   $0x1, %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) $0 = 0x0000000100000e7e

(lldb)  p/x $rcx
(unsigned long) $1 = 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) $2 = 0x00007fff4fe51570

(lldb)  p/x $rcx
(unsigned long) $3 = 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>

...
Run Code Online (Sandbox Code Playgroud)