TL; DR:
На итерации 0 объект помещается в пул автоматического выпуска из-за ленивого связывания вспомогательной функции ARC, нарушающей оптимизацию возвращаемого значения.Остальные освобождаются как можно скорее, потому что символ был связан.
Слабая ссылка в случае 2 - красная сельдь.Вы можете получить то же поведение после удаления переменной weakShared
.
@implementation AppDelegate
-(MyClass*)getMyClass {
MyClass* tmpHolder = [[MyClass alloc] init];
return tmpHolder;
}
...
Код Objective C после применения ARC выглядит следующим образом:
MyClass* "-[AppDelegate getMyClass]"(AppDelegate* self, SEL _cmd) {
MyClass* tmpHolder = [[MyClass alloc] init];
return objc_autoreleaseReturnValue(tmpHolder);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
В отличие от pre-ARC -autorelease
метод, функция objc_autoreleaseReturnValue()
будет не перемещать объект непосредственно в пул авто-релиза.Он проверит инструкции по сборке вызывающей стороны, и если вызывающая сторона собирается немедленно "-retain
" значение, мы вместо этого пропустим пул автоматического выпуска и вернем объект + 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:
.
(естьтакже подробное объяснение того, как эта оптимизация возврата работает на Как работает objc_retainAutoreleasedReturnValue? от Matt Galloway .)
Но что произошло на итерации 0?
Мы могли бы прочитать реализацию callerAcceptsOptimizedReturn
, которая описывает, как objc_autoreleaseReturnValue
определяет, что вызывающая сторона "немедленно сохранит".Короче говоря, он гарантирует, что вызывающий абонент сразу после вызова получает следующие инструкции:
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
).Это поведение по умолчанию при подключении к внешней динамической библиотеке.Перед вызовом через <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
$
Поскольку эта проблема возникает только один раз за все время существования программы, возможно, поэтому поведение остается неизменным.
Сценарий 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>
...