Bootstrap

【iOS】Block底层分析


前言

Block是带有局部变量的匿名函数,函数实现就是代码块里的内容,同样有参数和非返回值,本质是一个封装了函数调用以及函数调用环境的OC对象,因为它内部有isa指针

Block的基本使用请看这两篇文章:

  • k
  • l

本篇文章着重探究Block这些特性的底层原理

Block底层结构

声明一个最简单的块并调用:

void (^block)(void) = ^{
    NSLog(@"Hello World!");
};
block();

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m命令将OC代码转换成C++代码:

// 原本的代码有各种强制转换,目前不重要,先删去从简

// 声明并实现一个block
// void (*block)(void) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);


// 调用执行block
// ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
block->FuncPtr(block);
// __main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的,而且相当于直接把__block_impl里的值都放到__main_block_impl_0里

这些穿插了许多下划线的符号实际上是不同的结构体变量,Block本质就是struct __main_block_impl_0类型的结构体,下图清晰地说明了block的底层结构:

在这里插入图片描述
__main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的(相当于直接把__block_impl里的值都放到__main_block_impl_0里)
所以block.impl->FuncPtr(block)就相当于block->FuncPtr(block)

Block捕获变量原理

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

捕获局部变量(auto、static)

auto:自动变量,离开作用域就自动销毁,只存在于局部变量
static:静态局部变量

// 不加关键字默认是auto变量
/*auto*/ int age = 10;
static int height = 175;

void (^block)(void) = ^{
    // age、height的值捕获进来(capture))
    NSLog(@"age is %d, height is %d", age, height);
};

// 修改局部变量的值
age = 20;
height = 180;

block();
NSLog(@"%d %d", age, height);

打印结果:

在这里插入图片描述

可以看到age仍为修改前的值,而height确确实实被修改了

将以上代码转换成C++代码来看一下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  int *height;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • Block结构体的变量多了两个,分别是ageheight,这说明外部的变量被捕获到了Block的内部
  • 构造函数后面的 : age(_age), height(_height)语法会自动将_age、_height赋值给int age、int* height来保存

声明实现Block调用析构函数:

int age = 10;
static int height = 175;

block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));

age = 20;
height = 180;

而后调用Block,实际调用__main_block_func_0

block->FunPtr(block)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *height = __cself->height; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
}

此时的age是值传递,打印的只是Block初始化时传进去的,后面age修改跟这个值无关;height是指针传递,打印的是height变量地址一直所指向那块内存的值

全局变量

int age_ = 10;
static int height_ = 175;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void (^block)(void) = ^{
            NSLog(@"age_ is %d, height_ is %d", age_, height_);
        };

        age_ = 20;
        height_ = 180;
        
        block();
       
    }
    return 0;
}

全局变量一直在内存中,打印的一直是最新的值,不用捕获

在这里插入图片描述

为什么会有这样的差异呢?

auto和static:因为作用域的问题,自动变量的内存随时可能被销毁,所以要捕获就赶紧把它的值拿进来,防止调用的时候访问不到;静态变量就不一样了,它一直在内存中(作用域仅限于定义它们的函数、它们不能在函数外访问),随时可以通过指针访问到最新的值

全局变量:在Block中访问局部变量相当于是跨函数访问,要先将变量存储在Block里(捕获),使用的时候再从Block中取出,而全局变量是直接访问

捕获实例self

- (void)testSelf {
    void (^block)(void) = ^{
    // NSLog(@"--------%p -- %p -- %p -- %p", self, _name, self->_name, self.name);
        NSLog(@"--------%p", self);
        /*
        NSLog(@"--------%p", self->_name);
        相当于NSLog(@"--------%p", _name);
        也会捕获进去
        */
    };
    block();
}

看了它的C++实现后,发现self也会被捕获进去

实际上OC方法转换成C++函数后会发现前两个参数永远是方法调用者self、方法名_cmd

void testSelf(Person* self, SEL _cmd, ) {
	// ...
}

即然self是参数,参数也是局部变量,它被捕获进Block也就能解释得通了

Block类型

上面提到Block是OC对象,因为它有isa指针,对象的isa指向它的类型,那么Block都有什么类型呢?

首先运行以下代码:

void (^block)(void) = ^{
    NSLog(@"Hello!");
};
NSLog(@"%@ %@", block, [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
/*
 __NSGlobalBlock__
 NSBlock
 NSObject
 */

可以看到Block类型的根类是NSObject,也能说明Block是一个OC对象

不同操作对应的Block类型不同

// Global:没有访问auto变量,跟static变量无关
void (^block1)(void) = ^{
          NSLog(@"Hello");
};

// 函数调用栈:要调用一个函数的时候,就会指定一块栈区空间给这个函数用
// 一旦函数调用完毕后,栈区的这块空间就会回收,变成垃圾数据,会被其他数据覆盖

// Stack:访问了auto变量
int age = 21;
void (^block2)(void) = ^{
    NSLog(@"Hello - %d", age);
};
// ARC下打印Malloc?MRC下确实是Stack

NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
    NSLog(@"%d", age);
} class]); // 打印结果:__NSGlobalBlock__ __NSStackBlock__ __NSStackBlock__

// 编译完成后isa指向是_NSConcreteStackBlock、_NSConcreteMallocBlock、_NSConcreteGlobalBlock
// 首先肯定以运行时的结果为准,Block确实有三种类型,可能会通过Runtime动态修改类型
  • 没有访问自动变量的Block类型是__NSGlobalBlock__,存储在数据段
    其实Global不常用,既然不访问变量,那么将代码块封装成函数一行直接调用才显得更为简洁

  • 访问了自动变量的Block类型是__NSStackBlock__,存储在栈区
    以上代码是在MRC下运行的

  • __NSStackBlock__的Block调用了copy后类型会变为__NSMallocBlock__,存储在堆区
    若是在ARC下运行,即使不用copy修饰编译器也会自动对__NSStackBlock__进行copy操作,block2的类型将会是Malloc类型

手动对每种类型的Block调用copy后的结果如下图所示

请添加图片描述

Block的copy

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上
放到堆上的目的是方便我们来控制他的生命周期,可以更有效的进行内存管理

Block作为返回值

typedef void(^BBlock)(void);

BBlock myBlock(void) {
    int age = 21;
    return ^{
        NSLog(@"----------%d", age);
    };
}

BBlock bblock = myBlock();
bblock();
NSLog(@"%@", [bblock class]); // __NSMallocBlock__
//BBlock myBlock(void) {
//    return [^{
//        NSLog(@"----------");
//    } copy];
//}

由于Block在栈区,所以函数调用完毕后Block内存就被销毁了,再去调用它就很危险,如果在MRC下运行上述代码,编译器会提示报错:

在这里插入图片描述

ARC下不必担心此问题,编译器会自动对返回的Block进行copy操作(如注释所写),返回拷贝到堆上的Block

将Block赋值给__strong指针

int age = 21;
/*__strong*/ BBlock bblock = ^{
    NSLog(@"--------%d", age);
};
NSLog(@"%@", [bblock class]);  // ARC:__NSMallocBlock__

// 没有被强指针指着
NSLog(@"%@", [^{
    NSLog(@"--------%d", age);
} class]); // __NSStackBlock__

Block作为Cocoa API中方法名含有usingBlock的方法参数

NSArray* array = @[@"one", @2, @{@"seven" : @7}];
// 遍历数组并调用Block
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"%@ --- %lu", obj, (unsigned long)idx);
}];

在这里插入图片描述

Block作为GCD API的方法参数

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
  
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  
});

Block属性的写法

因为编译器会自动视情况进行copy操作,所以两种写法都没问题,只是为了统一规范建议使用copy来修饰属性

@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

Block访问对象类型的auto变量

Block在栈上

只要Block存在栈上,无论访问外部变量是用强指针还是弱指针,都不会对外部auto变量产生强引用

Block被拷贝到堆上

如果Block被拷贝到堆上,会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作

BBlock bblock;
{
    __strong Person* person = [[Person alloc] init];
    // __weak Person* person = [[Person alloc] init];
    person.age = 21;
    bblock = ^{
        // 在ARC环境下block会自动拷贝到堆区间,切换修饰符__strong和__weak,person分别会不释放和释放
        NSLog(@"-%d-", person.age);
    };
    
    // MRC环境下block是在栈区间的,所以不会对age进行强引用,person会随着作用域结束而释放
    //[bblock release];
}
NSLog(@"--------------");

将上面代码文件转换成C++文件:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

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

Block内部的__main_block_desc_0结构体会调用copy函数,copy函数内部会调用_Block_object_assign函数,而_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

Block从堆上移除

如果Block从堆上移除,会调用Block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动release引用的auto变量

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

注:

  • 只有在引用对象类型的变量时,才会生成copydispose函数
  • 如果引用的是static修饰的对象类型,那么捕获的变量在C++代码中将会是Person *__strong *person;
  • 代码里有__weak,转换C++文件可能会报错cannot create __weak reference in file using manual reference,可以指定支持ARC、指定运行时系统版本xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

使用GCD API验证Block对外部变量的强弱引用(Github Demo):

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Person* person = [[Person alloc] init];
    
    __weak Person* weakPerson = person;
    
    // 强引用了,Block调用完毕释放了person才会释放
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        NSLog(@"---%@", person);
//    });
    
    // 弱引用,调用Block之前person已经释放
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        NSLog(@"---%@", weakPerson);
//    });
    
    // 编译器已经检查到会有强引用
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        NSLog(@"---1%@", weakPerson);
//        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//            NSLog(@"---2%@", person);
//        });
//    });
    
    // 不会等到弱引用就释放了
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"---1%@", person);
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"---2%@", weakPerson);
        });
    });
    
    NSLog(@"Screen Touched");
}

修饰符__block

如果在Block内部修改捕获的auto变量值,编译器将会报错:

int age = 21;
BBlock block = ^{
    age = 20;
    NSLog(@"%d", age);
};
block();

在这里插入图片描述

从底层可看出在这里修改变量的值,实际上是通过改变__main_block_fun_0函数里的局部变量达到改变main函数里的变量,这是两个独立的函数,显然不可能

1. 使用static修饰变量

static来修饰age属性,底层用指针访问,block内部引用的是age的地址值,函数间会传递变量的地址,可以根据地址去修改age的值,修改的就是同一块内存
但不好的是age属性会一直存放在内存中不销毁,造成多余的内存占用,而且会改变age属性的性质,不再是一个auto变量

2. 使用__block修饰变量

__block来修饰属性,底层会生成__Block_byref_age_0类型的结构体对象,里面存储着age的真实值

在这里插入图片描述

转换成C++文件来查看内部结构,经__block修饰后,会根据__main_block_impl_0里生成的age对象来修改内部的成员变量age而且在外面打印的age属性的地址值也是__Block_byref_age_0结构体里的成员变量age的地址,目的就是不需要知道内部的真实实现,所看到的就是打印出来的值

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;  // 指向结构体本身
 int __flags;
 int __size;
 int age;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
		
		// 传进去的是age的地址
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        
        Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

总结

  • __block可以用于解决block内部无法修改auto变量值的问题
  • 编译器会将__block变量包装成一个对象
  • 其实修改的变量是__block生成的对象里面存储的变量的值,而不是外面的auto变量,但是内部生成的相同的变量的地址和外面的auto变量地址值是一样的,所以修改了内部的变量也会修改了外面的auto变量
  • __block不能修饰全局变量、静态变量(static)

__block内存管理

程序编译时,block__block都是在栈中的,这时并不会对__block变量产生强引用

因为__block也会包装成 OC对象,所以block底层也会生成copy函数dispose函数

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

Block复制到堆上

blockcopy到堆时,会调用block内部的copy函数copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(retain)
实际上,这时__block修饰的变量因为被包装成了OC对象,所以也会被拷贝到堆上,如果再有block强引用__block,由于__block变量已经拷贝到堆上了,就不会再拷贝了

在这里插入图片描述

Block从堆上移除

block从堆中移除时,会调用block内部的dispose函数dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的__block变量(release)

如果有多个block同时持有着__block变量,那么只有所有的block都从堆中移除了,__block变量才会被释放

在这里插入图片描述

__block和OC对象在block中的区别

__block生成的对象就是强引用,而NSObject对象会根据修饰符__strong或者__weak来区分是否要进行retain操作

注意:__weak不能修饰基本数据类型,编译器会报__weak' only applies to Objective-C object or block pointer types; type here is 'int'警告

__forwarding指针

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
  (age->__forwarding->age) = 20;
}
  • 在栈中,__block中的__forwarding指针指向自己的内存地址
  • 复制到堆中之后,__forwarding指针指向堆中的__block,堆中的__forwarding指向堆中的__block
  • 这样的目的都是为了不论访问的__block是在栈上还是在堆上,都可以通过__forwarding指针找到存储在堆中的auto变量

在这里插入图片描述

保证20被存储在堆中Block所引用的变量

__block修饰对象类型

情况类似于Block捕获对象类型的auto变量,__block包装的对象结构体里的对象变量会有__strong__weak修饰

__block对象在栈上时,不会对指向的对象产生强引用

__block对象被copy到堆上时,也会生成一个新的结构体对象,并且只会被block进行强引用,会根据不同的修饰符__strong__weak来对应着该对象类型成员变量是被强引用(retain)或弱引用

struct __Block_byref_weakPerson_0 {
	void __isa;
	__Block_byref_weakPerson_0 __forwarding;
	int __flags;
	int __size;
	void (__Block_byref_id_object_copy)(void, void);
	void (__Block_byref_id_object_dispose)(void*);
	Person *__weak weakPerson;
};


static void __Block_byref_id_object_copy_131(void *dst, void src) {
_Block_object_assign((char)dst + 40, *(void * ) ((char)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void src) {
_Block_object_dispose((void * ) ((char)src + 40), 131);

// __Block_byref_weakPerson_0 weakPerson = {0, &weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};
__attribute__((__blocks__(byref))) __Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};

注:在MRC环境下即使用__block修饰,对于结构体对象的成员变量,__block内部只会对auto变量进行弱引用,无论加不加__weak,block还没有释放,__block修饰的变量就已经释放了,这点和在ARC环境下不同

Block循环引用

两个对象相互强引用,导致谁的引用计数都不会归零,谁都不会释放

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person* person = [[Person alloc] init];
        person.age = 21;
        person.block = ^{
            NSLog(@"%d", person.age);
        };
    }
    NSLog(@"111111111111");
    return 0;
}

结果就是person对象不会释放,因为没有调用dealloc方法

person对象里面的block属性强引用着block对象,而block对象内部也会有一个person的成员变量指向这个Person对象,这样就会造成循环引用,谁也无法释放

@implementation Person

- (void)test {
    self.block = ^{
        NSLog(@"%d", self.age);
    };
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person* person = [[Person alloc] init];
        person.age = 21;
        [person test];
    }
    return 0;
    NSLog(@"111111111111");
}

block引用(捕获,之前提到self就是函数的第一个参数,参数也是局部变量)selfself又持有block,同样会造成循环引用

在这里插入图片描述

解决办法

  • 使用__weak__unsafe_unretainedBlock指向对象的引用变为弱引用

    在这里插入图片描述

    //    __unsafe_unretained typeof(self)weakSelf = self;
    __weak typeof(self)weakSelf = self;
      
    self.block = ^{
        NSLog(@"%d", weakSelf.age);
    };
    
  • __block解决,用__block修饰对象会造成三者相互引用造成循环引用,需要手动调用block

    在这里插入图片描述

    __block Person* person = [[Person alloc] init];
    person.age = 21;
    person.block = ^{
        NSLog(@"%d", person.age);
        person = nil;
    };
    person.block();
    

    block内部也需要手动将person置空,这个person__block内部生成的指向Person对象的变量

  • block传参,将self作为参数传入block中,进行指针拷贝,并没有对self进行持有

    // Person.m
    self.block = ^(Person * _Nonnull person) {
        NSLog(@"%d", person.age);
    };
    self.block(self);
    
  • MRC下不支持__weak,只能使用__unsafe_unretained
    MRC下直接使用__block即可解决循环引用,上面提到了MRC环境下__block修饰的变量只会被弱引用,已达成效果:

    __block Person *person = [[Person alloc] init];
    person.age = 10;
    
    person.block = [^{
    	NSLog(@"age is %d", person.age);
    } copy];
    
    [person release];
    

强弱共舞

这种情况虽没有引起循环引用,但block延迟执行2秒,等person释放后,就无法获取其age,很不合理

__weak typeof(person) weakPerson = person;
person.block = ^{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%d", weakPerson.age);
    });
};
person.block();

改进一下:

__weak typeof(person) weakPerson = person;
person.block = ^{
    __strong __typeof(weakPerson)strongPerson = weakPerson;
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%d", strongPerson.age);
    });
};
person.block();

通过运行结果发现,完全解决了以上self中途被释放的问题,这是为什么呢?分析如下:

  • 在完成block中的操作之后,才调用了dealloc方法。添加strongWeak之后,持有关系为:self -> block -> strongWeak -> weakSelf -> self
  • weakSelf被强引用了就不会自动释放,因为strongWeak只是一个临时变量,它的声明周期只在block内部,block执行完毕后,strongWeak就会释放,而弱引用weakSelf也会自动释放

总结

Block在iOS开发中极为重要,非常适合处理异步操作、回调、集合操作等场景,重点学习Block的内存管理、变量捕获和循环引用解决方案

;