Block定义
闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称为自由变量)
block实际上就是OC对于闭包的实现。
block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
Block结构分析
void blockTest()
{
void (^block)(void) = ^{
NSLog(@"Hello World!");
};
block();
}
int main(int argc, char * argv[]) {
@autoreleasepool {
blockTest();
}
}
编译后得到👇:
struct __block_impl {
void *isa;//isa指针,指向一个类对象,有三种类型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock,可以看出这里使用的是_NSConcreteStackBlock
int Flags;//Block的负载信息(引用计数和类型信息),按位存储
int Reserved;//保留变量
void *FuncPtr;//一个指针,指向Block执行时的函数,也就是Block需要执行的代码块,在这里指向的是__blockTest_block_func_0函数
};
//通常包含两个成员变量:__block_impl、__blockTest_block_desc_0,和一个构造函数
struct __blockTest_block_impl_0 {
struct __block_impl impl;//
struct __blockTest_block_desc_0* Desc;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//__blockTest_block_func_0就是Block执行调用的函数,参数是一个__blockTest_block_impl_0类型的指针
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_42229c_mi_0);
}
static struct __blockTest_block_desc_0 {
size_t reserved;//Block版本升级所需预留区空间,这里为0
size_t Block_size;//Block的大小, sizeof(struct __blockTest_block_impl_0)
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};//是__blockTest_block_desc_0的一个实例
void blockTest()
{
/**
1.block的定义:通过__blockTest_block_impl_0结构体生成一个实例,并用一个指针指向了当前实例,
__blockTest_block_impl_0q在初始化时需要两个参数:
__blockTest_block_func_0:Block块的函数指针
__blockTest_block_desc_0_DATA:作为静态全局变量初始化__blockTest_block_desc_0_DATA结构体的实例指针
*/
void (*block)(void) = (&__blockTest_block_impl_0(
__blockTest_block_func_0,
&__blockTest_block_desc_0_DATA)
);
/**
2.调用block:通过block)->FuncPtr找到__blockTest_block_func_0函数指针
然后将block作为参数传递给这个函数
*/
(block)->FuncPtr)(block);
}
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
blockTest();
}
}
- block的定义部分:
block是一个结构体,该结构体需要两个参数
__blockTest_block_func_0
:Block块的函数指针;
__blockTest_block_desc_0_DATA
:作为静态全局变量初始化__blockTest_block_desc_0_DATA结构体的实例指针;
通过__blockTest_block_impl_0结构体生成一个实例,并用一个指针指向了当前实例。 - block调用部分:
通过block)->FuncPtr找到__blockTest_block_func_0函数指针,并将step1 block指针传递给该函数,
__blockTest_block_func_0就是block执行时调用的函数,接收的参数是__blockTest_block_impl_0类型的指针,step1生成的就是__blockTest_block_impl_0结构体的实例
Flags(Block_private.h)
enum {
BLOCK_DEALLOCATING = (0x0001), // 释放标记。一般常用 BLOCK_NEEDS_FREE 做 位与 操作,一同传入 Flags ,告知该 block 可释放。
BLOCK_REFCOUNT_MASK = (0xfffe), // 一般参与判断引用计数,是一个可选用参数。
BLOCK_NEEDS_FREE = (1 << 24), // 通过设置该枚举位,来告知该 block 可释放。意在说明 block 是 heap block ,即我们常说的 _NSConcreteMallocBlock 。
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // 是否拥有拷贝辅助函数(a copy helper function)。
BLOCK_HAS_CTOR = (1 << 26), // 是否拥有 block 析构函数(dispose function)。
BLOCK_IS_GC = (1 << 27), // 是否启用 GC 机制(Garbage Collection)。
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30) // 与 BLOCK_USE_STRET 相对,判断是否当前 block 拥有一个签名。用于 runtime 时动态调用。
};
Block结构如图(网上借的):
Block
Block捕获变量
捕获auto变量(局部变量)
先看下面这段代码:
void blockTest()
{
int num = 10;
void (^block)(void) = ^{
NSLog(@"%d",num);
};
num = 20;
block();
}
int main(int argc, char * argv[]) {
@autoreleasepool {
blockTest();
}
}
num应该输出什么?
答案:应该输出10
编译后的代码👇:
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
int num;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
void blockTest()
{
int num = 10;
void (*block)(void) = (&__blockTest_block_impl_0(
__blockTest_block_func_0,
&__blockTest_block_desc_0_DATA,
num)
);
num = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
__blockTest_block_impl_0
结构体多了一个成员变量num
,
构造函数__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0)
可以看到第三个参数num只是变量的值,这就解释了为什么num打印的是10,因为block捕获auto变量时,捕获的是其值。
捕获static变量
void blockTest()
{
static int num = 10;
void (^block)(void) = ^{
NSLog(@"%d",num);
num = 30;
};
num = 20;
block();
NSLog(@"%d",num);
}
两次num分别输出什么?
答案:block块内的num输出20,第二个num输出30
编译后👇:
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
int *num;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
int *num = __cself->num; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a383ea_mi_0,(*num));
(*num) = 30;
}
void blockTest()
{
static int num = 10;
void (*block)(void) = (&__blockTest_block_impl_0(
__blockTest_block_func_0,
&__blockTest_block_desc_0_DATA,
&num));
num = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a383ea_mi_1,num);
}
构造函数__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0)
第三个参数*num传入的是num的指针,所以可以在内部和外部修改变量的值。
为什么auto变量就是传递的值,而static变量传递的是指针呢?
auto变量保存在栈中,并且会随着当前作用域(blockTest)消失而销毁,有可能销毁时机会比block更早,所以block内访问销毁的变量时会产生问题,而static变量保存在全局存储区(静态存储区),不会出现这样的问题。
全局变量
int num = 10;
void blockTest()
{
void (^block)(void) = ^{
NSLog(@"%d",num);
num = 30;
};
num = 20;
block();
NSLog(@"%d",num);
}
编译后👇:
int num = 10;
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_141607_mi_0,num);
num = 30;
}
static struct __blockTest_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};
void blockTest()
{
void (*block)(void) = (&__blockTest_block_impl_0(
__blockTest_block_func_0,
&__blockTest_block_desc_0_DATA)
);
num = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_141607_mi_1,num);
}
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
blockTest();
}
}
可以看到这里构造函数并没有传入变量的值或者指针,因为全局变量是直接可以访问的。
总结一下:
变量类型 | 是否捕获到block内部 | 访问方式 |
---|---|---|
auto变量 | 是 | 值访问 |
static变量 | 是 | 指针访问 |
全局变量 | 否 | 直接访问 |
__block修饰的变量
1.在 block 内为什么不能修改 block 外部变量
答案:block 本质上是一个对象,block 的花括号区域是对象内部的一个函数,变量进入 花括号,实际就是已经进入了另一个函数区域---改变了作用域。在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。又比如我想在block内声明了一个与外部同名的变量,此时是允许呢还是不允许呢?只有加上了这样的限制,这样的情景才能实现。详解
2.除了使用static变量、全局变量外如何在block内改变变量的值?为什么?
答案:
使用__block;
static变量: block 内部对外部static修饰的变量进行指针捕获;
全局变量:block 内外可直接访问全局变量;
__block变量:要想在block内部修改auto变量,需要两个条件:
(1)从栈区拷贝到堆区(栈的内存是由系统管理,堆由我们管理,其实在ARC下所有进入block内的auto变量都会被拷贝到堆区见这里)
(2)把auto变量包装成结构体(对象),_block 作用是将 auto 变量封装为结构体(对象),在结构体内部新建一个同名 auto 变量,block 内截获该结构体的指针,在 block 中使用自动变量时,使用指针指向的结构体中的自动变量。于是就可以达到修改外部变量的作用。
总结一下就是如果想在block内修改变量:将 auto 从栈 copy 到堆;将 auto 变量封装为结构体(对象)
3.这三种修改变量值的方式哪个最好的?
这个问题请查看https://github.com/ChenYilong/iOSInterviewQuestions第38题,结论是__block是最优解。
从源码层面论证
void blockTest()
{
__block int num = 10;
void (^block)(void) = ^{
NSLog(@"%d",num);
num = 30;
};
num = 20;
block();
NSLog(@"%d",num);
}
编译后👇:
struct __Block_byref_num_0 {
void *__isa;
__Block_byref_num_0 *__forwarding;
int __flags;
int __size;
int num;
};
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
__Block_byref_num_0 *num; // by ref
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
__Block_byref_num_0 *num = __cself->num; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a71778_mi_0,(num->__forwarding->num));
(num->__forwarding->num) = 30;
}
static void __blockTest_block_copy_0(struct __blockTest_block_impl_0*dst, struct __blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __blockTest_block_dispose_0(struct __blockTest_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __blockTest_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __blockTest_block_impl_0*, struct __blockTest_block_impl_0*);
void (*dispose)(struct __blockTest_block_impl_0*);
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0), __blockTest_block_copy_0, __blockTest_block_dispose_0};
void blockTest()
{
__attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 10};
void (*block)(void) = ((void (*)())&__blockTest_block_impl_0(
__blockTest_block_func_0,
&__blockTest_block_desc_0_DATA,
(__Block_byref_num_0 *)&num,
570425344));
(num.__forwarding->num) = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a71778_mi_1,(num.__forwarding->num));
}
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
blockTest();
}
}
__block修饰的变量的拷贝
static void __blockTest_block_copy_0(struct __blockTest_block_impl_0*dst, struct __blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
注意:__block变量的结构体__Block_byref_num_0内有一个__Block_byref_num_0类型的指针 __forwarding,而且获取__Block_byref_num_0结构体时候都会使用__forwarding获取,至于原因会在后面讲
该方法有一个dst(接收拷贝完成的对象)指针和一个src对象(被拷贝的对象),并调用了方法_Block_object_assign
方法, _Block_object_assign
需要三个参数,分别是:dst->num
、src->num
、和一个flags 8
,前两个参数就是__Block_byref_num_0
的对象,这里先看一下这个flags枚举(Block_private.h):
// Runtime support functions used by compiler when generating copy/dispose helpers
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, //OC对象类型
BLOCK_FIELD_IS_BLOCK = 7, //一个block变量
BLOCK_FIELD_IS_BYREF = 8, // 在栈上被__block修饰的变量
BLOCK_FIELD_IS_WEAK = 16, // 被__weak修饰的变量,只在Block_byref管理内部对象内存时使用
BLOCK_BYREF_CALLER = 128, // 处理Block_byref内部对象内存的时候会加的一个额外标记(告诉内部实现不要进行retain或者copy)
};
这里使用的是BLOCK_FIELD_IS_BYREF
_Block_object_assign(__block对象的copy)
接着通过runtime源码查看该方法的实现(只截取了关键部分):
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
调用_Block_byref_copy
方法把object(src)
传入到该函数,并返回到一个新的对象赋值给dest
,dest
就是新得到从栈上拷贝到堆上的新值。
_Block_object_dispose(__block对象的释放)
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
当需要释放堆上的auto变量对象时,调用_Block_byref_release
释放该对象
__block修饰的变量的包装
被__block修饰的auto变量会被包装成一个 __Block_byref_num_0
的结构体,同样拥有isa,因此也是一个对象;
Block的内存管理
__block修饰的变量什么时候会被从栈拷贝到堆?
看这个问题之前我们先看一下我们先了解下Block的内存管理,
Block一共有三种类型:
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
他们都继承自NSBlock,NSBlock继承自NSObject
三种类型对应的内存分配以及调用copy后的效果如下:
Block类型 | 副本源的配置存储域 | copy效果 |
---|---|---|
NSGlobalBlock | 数据区 | 什么也不做 |
NSStackBlock | 栈 | 从栈复制到堆 |
NSMallocBlock | 堆 | 引用计数增加 |
有以下情况会把BLock从栈拷贝到堆:
1.调用Block的copy实例方法时
2.Block作为函数返回值返回时
3.在带有usingBlock的Cocoa方法或者GCD的API中传递Block时候
4.将block赋给带有__strong修饰符的id类型或者Block类型时
下面通过验证一下第4种情况看下是否准确(前三种可自行测试):
无__strong修饰符的id类型或者Block类型,代码⬇️:
int num = 10;
NSLog(@"%@",[^{
NSLog(@"%d",num);
} class]);
打印结果:
__NSStackBlock__
有__strong修饰符的id类型或者Block类型,代码⬇️:
void (^block)(void) = ^{
NSLog(@"%d",num);
};
NSLog(@"%@",[block class]);
打印结果:
__NSMallocBlock__
Block的拷贝时机介绍完了,那么__block修饰的变量何时会从栈区拷贝到堆区呢?
答案:当Block从栈拷贝到堆区的时候,__block变量也会跟着Block被拷贝到堆区。
验证一下👇,
先看一下不拷贝到堆区的情况:
__block int num = 10;
NSLog(@"block前:%p",&num);
^{
num = 20;
NSLog(@"block内:%p",&num);
}();
NSLog(@"block后:%p",&num);
打印结果:
block前:0x7ffeea3f2c98
block内:0x7ffeea3f2c98
block后:0x7ffeea3f2c98
拷贝到堆区的情况:
NSLog(@"block前:%p",&num);
void (^block)(void) = ^{
num = 20;
NSLog(@"block内:%p",&num;
};
block();
NSLog(@"block后:%p",&num);
打印:
block前:0x7ffee4af3c98
block内:0x600003650738
block后:0x600003650738
从内存地址不难看出不拷贝堆区时,__block变量也不会进行拷贝,当Block从栈区拷贝到堆区,__block变量也会进行拷贝
下面这段代码为什么MRC和ARC下打印不一样
int num = 1;
void (^block)(void) = ^{
NSLog(@"%d",num);
};
NSLog(@"%@",[block class]);
MRC打印:
__NSStackBlock__
ARC打印:
__NSMallocBlock__
原因:
由于在ARC环境下,使用strong修饰的变量指向block,会持有这个block。因此临时变量block会从栈复制到堆上
__forwarding指针存在的意义是什么?
__forwarding.png__forwarding指针是为了在__block变量从栈复制到堆上后,在Block外对__block变量的修改也可以同步到堆上实际存储__block变量的结构体上。
__forwarding确保不管是堆栈访问__block变量结构体时都能访问到同一个对象
Block捕获对象
NSStackBlock
在栈上的Block不会对auto对象进行强引用;
NSMallocBlock
堆上的Block会对auto对象进行强引用,直到Block释放时,才解除对auto对象的强引用
typedef void(^Block)(void);
int main(int argc, char * argv[]) {
Block block;
{
Person *person = [[Person alloc] init];
person.name = @"toby";
block = ^{
NSLog(@"%@",person.name);
};
person.name = @"david";
NSLog(@"即将退出person作用域");
}
NSLog(@"已经退出person作用域");
block ();
}
打印结果:
即将退出person作用域
已经退出person作用域
david
-[Person dealloc]
Block的循环引用
什么情况下会造成循环引用?
当一个对象person持有了了block对象,而block内又持有了person互相持有,这就造成了循环引用
如何打破循环引用?
1.使用__block修饰对象person
2.使用__unsafe_unretained修饰对象person
3.使用__weak修饰对象person
__block
__block Person *person = [[Person alloc] init];
person.blockTest = ^{
person.name = @"toby";
person = nil;
};
person.blockTest();
需要在block内指定person=nil,并且需要调用调用block函数。
__unsafe_unretained
Person *person = [[Person alloc] init];
__unsafe_unretained typeof(person) weakPerson = person;
weakPerson.blockTest = ^{
weakPerson.name = @"toby";
};
使用__unsafe_unretained虽然能解除循环引用,但是不安全,当指向对象销毁时,指针存储地址不变,如果再次访问可能会造成悬垂指针⬇️:
__weak
Person *person = [[Person alloc] init];
__weak typeof(person) weakPerson = person;
weakPerson.blockTest = ^{
weakPerson.name = @"toby";
};
查看编译后的源码👇:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到当前block内引用的是一个weak类型的person,对person的引用变成了弱引用,这就打破了双向持有的局面,至于该weak person的释放时机是由runtime维护的一个hash table决定的,当person对象dealloc时,会以person 地址当作键值在hash table中查找weak对象置为nil。runtime如何实现weak变量的自动置nil
但是实际应用会发现如果此时Block回调晚一些(异步线程执行耗时任务),此时的person已经出作用域,在block内访问weakPerson的时候就nil了。
Person *person = [[Person alloc] init];
__weak typeof(person) weakPerson = person;
person.blockTest = ^{
dispatch_async(dispatch_queue_create(0,DISPATCH_QUEUE_CONCURRENT), ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"person = %@",weakPerson);
});
};
person.blockTest();
此时的办法就是在block内使用__strong再修饰一下weakPerson,让person延迟释放,至于释放时机当然是block执行完成
Person *person = [[Person alloc] init];
__weak typeof(person) weakPerson = person;
person.blockTest = ^{
__strong typeof(person) strongPerson = weakPerson;
dispatch_async(dispatch_queue_create(0,DISPATCH_QUEUE_CONCURRENT), ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"strong person = %@",strongPerson);
});
};
person.blockTest();
}
总结
1.Block是一个对象
2.Block捕获变量:
(1)auto变量:捕获的是值
(2)static变量:捕获指针
(3)global变量:无需捕获,直接访问
3.Block捕获__block修饰的auto变量时,会把该变量包装成一个对象,并会根据Block是否会被拷贝到堆区对auto变量进行拷贝,修改auto变量时需要满足两个条件:
(1)将 auto 从栈 copy 到堆;
(2)将 auto 变量封装为结构体(对象)
4.Block有三种类型,他们都继承自NSBlock->NSObject
NSGlobalBlock ( _NSConcreteGlobalBlock ) 数据区域
NSStackBlock ( _NSConcreteStackBlock ) 栈区
NSMallocBlock ( _NSConcreteMallocBlock ) 堆区
5.Block会被从栈拷贝到堆的情况:
(1)调用Block的copy实例方法时
(2)Block作为函数返回值返回时
(3)在带有usingBlock的Cocoa方法或者GCD的API中传递Block时候
(4)将block赋给带有__strong修饰符的id类型或者Block类型时
6. __forwarding指针是为了在__block变量从栈复制到堆上后,在Block外对__block变量的修改也可以同步到堆上实际存储__block变量的结构体上。
7.在栈上的Block不会对auto对象进行强引用;堆上的Block会对auto对象进行强引用,直到Block释放时,才解除对auto对象的强引用
8.解除Block的循环引用,最安全的方法是使用__weak修饰auto变量,并在block内部对auto变量进行__strong修饰
网友评论