1、观察效果

在MRC环境下观察以下代码:

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. Person *person = [[Person alloc] init];
  4. [person release];
  5. NSLog(@"mark");
  6. }
  7. return 0;
  8. }

打印结果:

  1. ~: -[Person dealloc]
  2. ~: mark
  3. Program ended with exit code: 0

当person对象调用release之后会自动销毁。再观察下面代码:

  1. typedef void(^XLBlock)(void);
  2. int main(int argc, const char * argv[]) {
  3. @autoreleasepool {
  4. XLBlock block;
  5. Person *person = [[Person alloc] init];
  6. block = ^{
  7. NSLog(@"person = %@", person);
  8. };
  9. block();
  10. NSLog(@"block = %@", block);
  11. [person release];
  12. NSLog(@"mark");
  13. [block release];
  14. }
  15. return 0;
  16. }

打印结果:

  1. ~: person = <Person: 0x100670df0>
  2. ~: block = <__NSStackBlock__: 0x7ffeefbff418>
  3. ~: -[Person dealloc]
  4. ~: mark
  5. Program ended with exit code: 0

当block是Stack类型时,访问person对象,person在调用release方法时可以正常销毁,再观察以下代码:

  1. typedef void(^XLBlock)(void);
  2. int main(int argc, const char * argv[]) {
  3. @autoreleasepool {
  4. XLBlock block;
  5. Person *person = [[Person alloc] init];
  6. block = [^{
  7. NSLog(@"person = %@", person);
  8. } copy];
  9. block();
  10. NSLog(@"block = %@", block);
  11. [person release];
  12. NSLog(@"mark");
  13. [block release];
  14. }
  15. return 0;
  16. }

打印结果:

  1. ~: person = <Person: 0x105b12900>
  2. ~: block = <__NSMallocBlock__: 0x105b2cb30>
  3. ~: mark
  4. ~: -[Person dealloc]
  5. Program ended with exit code: 0

当block调用了copy编程malloc类型后,person调用release方法后没有立即释放,等到block调用release方法后才释放。可以总结出:当block在栈上时不会引用对象类型的auto变量,当block被复制到堆上时,会引用对象类型的auto变量。
在ARC环境下观察如下代码:

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. XLBlock block;
  4. {
  5. Person *person = [[Person alloc] init];
  6. block = ^{
  7. NSLog(@"person = %@", person);
  8. };
  9. block();
  10. NSLog(@"block = %@", block);
  11. }
  12. NSLog(@"mark");
  13. }
  14. return 0;
  15. }

*ARC环境下,block访问auto变量,block会被复制到堆上。

打印结果:

  1. ~: person = <Person: 0x105035910>
  2. ~: block = <__NSMallocBlock__: 0x105043350>
  3. ~: mark
  4. ~: -[Person dealloc]
  5. Program ended with exit code: 0

在person离开作用域时,没有马上释放,而是在block离开作用域被释放时,person才被释放。再观察如下代码:

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. XLBlock block;
  4. {
  5. Person *person = [[Person alloc] init];
  6. __weak typeof(person)weakPerson = person;
  7. block = ^{
  8. NSLog(@"weakPerson = %@", weakPerson);
  9. };
  10. block();
  11. NSLog(@"block = %@", block);
  12. }
  13. NSLog(@"mark");
  14. }
  15. return 0;
  16. }

打印结果:

  1. ~: weakPerson = <Person: 0x10063a3c0>
  2. ~: block = <__NSMallocBlock__: 0x10064eeb0>
  3. ~: -[Person dealloc]
  4. ~: mark

在person离开作用域时,马上被释放了。可以总结出block对对象类型的auto变量强引用还是弱引用取决于访问的对象变量的引用方式。

2、底层探究

新建一个Person类继承于NSObject,在block内访问person对象

  1. Person *person = [[Person alloc] init];
  2. void (^block)(void) = ^{
  3. NSLog(@"person = %p",person);
  4. };
  5. block();

转成C++代码后查看:

  1. struct __main_block_impl_0 {
  2. struct __block_impl impl;
  3. struct __main_block_desc_0* Desc;
  4. Person *person;
  5. };

block结构体内有一个person对象的指针,且__main_block_desc_0结构体里会比访问基础类型auto变量多处了两个方法,copy和dispose:

  1. // desc结构体,多了copy和depose方法
  2. static struct __main_block_desc_0 {
  3. size_t reserved;
  4. size_t Block_size;
  5. void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  6. void (*dispose)(struct __main_block_impl_0*);
  7. }

copy方法:

  1. static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
  2. _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
  3. }

当block执行copy方法时,内部的main_block_copy_0方法会根据捕获auto变量的引用类型(strong、weak)进行引用。(比如外部person用weak修饰,那么内部block也将弱引用person)
dispose方法:

  1. static void __main_block_dispose_0(struct __main_block_impl_0*src) {
  2. _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
  3. }

当block从堆上移除时,会调用内部的__main_block_dispose_0方法,释放内部捕获的对象类型auto变量,相当于release。
image.png

3、总结

当block内部访问了对象类型的auto变量时:
如果block是在栈上,将不会对auto变量产生强引用。

如果block被拷贝到堆上,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(strong、weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。

如果block从堆上移除,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)。

4、应用举例

创建一个Person类,添加test方法,重写Person的dealloc方法。
1、创建一个Person对象,使用GCD延时调用person的test方法:

  1. {
  2. NSLog(@"start");
  3. Person *person = [[Person alloc] init];
  4. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  5. [person test];
  6. });
  7. }

打印结果:

  1. ~: start
  2. /** 3秒后 **/
  3. ~: person dealloc

person会在3秒后销毁,原因是person对象被block强引用,等block销毁时,执行dispose方法后才会释放person。
2、在block内部访问弱引用的person对象:

  1. {
  2. NSLog(@"start");
  3. Person *person = [[Person alloc] init];
  4. __weak Person* weakPerson = person;
  5. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  6. [weakPerson test];
  7. });
  8. }

打印结果:

  1. ~: start
  2. ~: person dealloc

person对象会被马上销毁,因为block内部也弱引用了person对象,所以在代码执行结束后会自动销毁。