Block详解------已完结
1.Block的使用
Block是什么?
块,封装了函数调用以及调用环境的OC对象,
Block的声明
//1.
@property (nonatomic, copy) void(^myBlock1)(void);
// 2.BlockType:类型别名
typedef void(^BlockType)(void);
//3.
//返回值类型(^block变量名)(参数1类型,参数2类型, ...)
void(^block)(void);
Block的定义
// ^返回值类型(参数1,参数2,...){};
//1.无返回值,无参数
void(^block1)(void) = ^{};//2.无返回值,有参数
void(^block2)(int) = ^(int a) {} ;//3.有返回值,无参数(不管有没有返回值,定义的返回值类型都可以省略)
int(^block3)(void)^int {};
//以上Block的定义也可以这样写:
int(^block3)(void) = ^ {};//4.有返回值,有参数
int(^block5)(int) = ^int(int a) {return 3*a;
};//Block的调用
//1.无返回值,无参数
block1();
//2.有返回值,有参数
int a = block5(2);//使用示例
int multipler = 7;
int (^myBlock)(int) = ^(int num) {return num * multiplier;
};printf("%d", myBlock(3));
// prints "21"
2.Block的底层数据结构
Block本质上也是一个OC对象,它内部也有个ias指针;
Block是封装了函数调用以及调用环境的OC对象;
Block的底层数据结构如下图所示:
通过Clang将以下Block代码转换为C++代码,来分析Block的底层实现。
// Clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
//main.m
int main(int argc, const char * argv[]) {@autoreleasepool {void(^block)(void) = ^ {NSlog(@"调用了block");};block();}return 0;
}
// main.cpp
struct __main_block_impl_0 {struct __block_impl impl; // block的结构体struct __main_block_desc_0* Desc; // block的描述对象,描述block的大小等/* 构造函数** 返回值:__main_block_impl_0 结构体** 参数一:__main_block_func_0 结构体** 参数二:__main_block_desc_0 结构体的地址** 参数三:flags 标识位*/__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock; //_NSConcreteStackBlock 表示block存在栈上impl.Flags = flags; impl.FuncPtr = fp;Desc = desc;}
};// __main_block_func_0 封装了block里的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_58a448_mi_0);
}struct __block_impl {void *isa; // block的类型int Flags; // 标识位int Reserved; // void *FuncPtr; // block的执行函数指针,指向__main_block_func_0
};static struct __main_block_desc_0 {size_t reserved;size_t Block_size; // block本质结构体所占内存空间
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; /*** void(^block)(void) = ^{NSLog(@"调用了block");};** 定义block的本质:** 调用__main_block_impl_0()构造函数** 并且给它传了两个参数 __main_block_func_0 和 &__main_block_desc_0_DATA** __main_block_func_0 封装了block里的代码** 拿到函数的返回值,再取返回值的地址 &__main_block_impl_0,** 把这个地址赋值给 block*/void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,&__main_block_desc_0_DATA));/*** block();** 调用block的本质:** 通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数地址,直接调用*/ ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);}return 0;
}Block底层数据结构就是一个 __main_block_impl_0 结构体对象,其中有 __block_impl 和 __main_block_desc_0 两个结构对象成员。
main:表示block所在的函数
block:表示这是一个block
impl:表示实现(implementation)
0: 表示这是该函数中的第一个block
__main_block_func_0 结构体封装了block里的代码;
__block_impl 结构体才是真正定义block的结构,其中的FuncPtr指针指向 __main_block_func_0;
__main_block_desc_0 是block的描述对象,存储着block内存大小等;
定义block的本质:
调用 __main_block_impl_0() 构造函数,并且给它传了两个参数 __main_block_func_0 和 &__main_block_desc_0_DATA 。拿到函数的返回值,再取返回值的地址 &__main_block_impl_0,把这个地址赋值给block变量。
调用block的本质:
通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数地址,直接调用。
Block 的变量捕获机制
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制。
对于全局变量,不会捕获到block内部,访问方式为直接访问;
对于auto类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,用来存储这个变量的值,访问方式为值传递;
对于static类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,用来存储这个变量的地址,访问方式为指针传递;
对于对象类型的局部变量,block会连同它的所有权修饰符一起捕获。
auto类型的局部变量
auto自动变量:我们定义出来的变量,默认都是auto类型,只是省略了。
auto int age = 10;
通过Clang将以下代码转换为C++代码:
int age = 10;
void(^block)(void) =^ {NSLog(@"%d", age);
}
block();
//__main_block_impl_0 对象内部会生成一个相同的age变量;
//__main_block_impl_0() 构造函数多了个参数,用来捕获访问的外面的age变量的值,将它赋值给__main_block_impl_0 对象内部的 age 变量。struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int age;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int age = __cself->age; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_5ed490_mi_0,age);
}......int age = 10;void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
由于是值传递,我们修改外部的age变量的值,不会影响到block内部的age变量。
int age = 10;
void(^block)(void) = ^ {NSLog(@"%d", age);
};
age = 20;
block();
// 10
static类型的局部变量
static 类型的局部变量会捕获到block内部,访问方式为指针传递。
通过Clang将以下代码转换成为C++代码:
static int age = 10;void(^block)(void) = ^{NSLog(@"%d",age);};block();
__main_block_impl_0 对象内部会生成一个相同类型的 age 指针;
__main_block_impl_0() 构造函数多了个参数,用来捕获访问的外面的age变量的地址,将它赋值给 __main_block_impl_0 对象内部的age指针。struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int *age;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int *age = __cself->age; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_a4bc7d_mi_0,(*age));
}......static int age = 10;void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
由于是指针传递,我们修改外部的age变量的值,会影响到block内部的age变量。
static int age = 10;void(^block)(void) = ^{NSLog(@"%d",age);};age = 20;block();// 20
全局变量
全局变量不会捕获
到 block 内部,访问方式为直接访问
。
通过 Clang 将以下代码转换为 C++ 代码:
int _age = 10;
static int _height = 20;
......void(^block)(void) = ^{NSLog(@"%d,%d",_age,_height);};block();
__main_block_impl_0
对象内并没有生成对应的变量,也就是说全局变量没有捕获到 block 内部,而是直接访问。
int _age = 10;
static int _height = 20;struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_12efa5_mi_0,_age,_height);
}......void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
为什么局部变量需要捕获,全局变量不用捕获呢?
为什么局部变量需要捕获,全局变量不用捕获呢?
- 作用域的原因,全局变量哪里都可以直接访问,所以不用捕获;
- 局部变量,外部不能直接访问,所以需要捕获;
- auto 类型的局部变量可能会销毁,其内存会消失,block 将来执行代码的时候不可能再去访问那块内存,所以捕获其值;
- static 变量会一直保存在内存中, 所以捕获其地址即可。
对象类型的auto变量
当block内部访问了对象类型的auto变量时:
如果block是在栈上,将不会对auto变量产生强引用
如果block被拷贝到堆上:block内部的desc结构会新增两个函数:copy(__main_block_copy_0, 函数名命名规范同__main_block_impl_0)dispose( __main_block_dispose_0)会调用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)
函数 | 调用时机 |
---|---|
copy 函数 | 栈上的 block 复制到堆时 |
dispose 函数 | 堆上的 block 被废弃时 |
如下代码,block 保存在堆中,当执行完作用域2的时候,Person 对象并没有被释放,而是在执行完作用域1的时候释放,说明 block 内部对 Person 对象产生了强引用。
typedef void(^MyBlock)(void);int main(int argc, const char * argv[]) {@autoreleasepool { //作用域1MyBlock block;{ //作用域2Person *p = [Person new];p.name = @"zhangsan"; block = ^{NSLog(@"%@",p.name);};}NSLog(@"-----");}return 0;
}
// -----
// Person-dealloc
通过 Clang 将以上代码转换为 C++ 代码:
// 弱引用需要运行时的支持,所以需要加上 -fobjc-arc -fobjc-runtime=ios-8.0.0
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
__main_block_impl_0
中生成了一个Person *__strong p
指针,指向外面的 person 对象,且是强引用。
typedef void(*MyBlock)(void);struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;Person *__strong p; // 强引用__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_p, int flags=0) : p(_p) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {Person *__strong p = __cself->p; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("name")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}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; MyBlock block;{Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_0);block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, 570425344));}NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_2);}return 0;
}
添加了__weak
修饰后,当执行完作用域2的时候,Person 对象就被被释放了。
typedef void(^MyBlock)(void);int main(int argc, const char * argv[]) {@autoreleasepool { //作用域1MyBlock block;{ //作用域2__weak Person *p = [Person new];p.name = @"zhangsan"; block = ^{NSLog(@"%@",p.name);};}NSLog(@"-----");}return 0;
}
// Person-dealloc
// -----
同样的,通过 Clang 将以上代码转换为 C++ 代码。 __main_block_impl_0
中生成了一个Person *__weak p
指针,指向外面的 person 对象,且是弱引用。 说明当 block 内部 访问了对象类型的 auto 变量时,如果 block 被拷贝到堆上,会连同对象的所有权修饰符一起捕获。
......
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;Person *__weak p; //弱引用__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _p, int flags=0) : p(_p) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {Person *__weak p = __cself->p; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_c61841_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("name")));
}
......
_block修饰的变量
__block作用
默认情况下block是不能修改外面的auto变量的,解决办法?
变量用static修饰(原因:捕获static类型的局部变量是指针传递,可以访问到该变量的内存地址)
全局变量
__block(我们只是希望临时用一下这个变量临时改一下而已,而改为static变量和全局变量会一直在内存中)
__block修饰符
__block可以用于解决block内部无法修改auto变量值的问题;
__block不能修饰全局变量、静态变量;
编译器会将__block变量包装成一个对象(struct_Block_byref_age_0 byref:按地址传递);
加__block修饰不会修改变量的性质,它还是auto变量;
一般情况下,对被捕获变量进行赋值(赋值!= 使用)操作需要添加__block修饰符。比如给给数组添加或删除对象,就不用加 __block
使用示例
__block int age = 10;void(^block)(void) = ^{age = 20;NSLog(@"block-%d",age);};block();NSLog(@"%d",age);// block-20// 20
通过 Clang 将以上代码转换为 C++ 代码。
- 编译器会将 __block 修饰的变量包装成一个
__Block_byref_age_0
对象; - 以上
age = 20;
的赋值过程为:通过 block 结构体里的(__Block_byref_age_0
)类型的 age 指针,找到__Block_byref_age_0
结构体的内存(即被 __block 包装成对象的内存),把__Block_byref_age_0
结构体里的 age 变量的值改为20。 - 由于编译器将 __block 变量包装成了一个对象,所以它的内存管理几乎等同于访问对象类型的 auto 变量,但还是有差异,下面会讲到。
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 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;NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9578d0_mi_0,(age->__forwarding->age));
}
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*/);}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; __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9578d0_mi_1,(age.__forwarding->age));}return 0;
}
__block的内存管理
当block在栈上时,并不会对__block变量产生强引用
当block被copy到堆时
block内部的desc结构会新增两个函数 copy(__main_block_copy_0
,函数名命名规范同__main_block_impl_0
) dispose(__main_block_dispose_0
)
会调用block内部的copy函数
copy函数内部会调用 _Block_object_assign 函数
_Block_object_assign 函数会对 __block变量形成引用(retain)
当block从堆中移除时
会调用block内部的dispose函数
dispose函数内部会调用 _Block_object_dispose 函数
_Block_object_dispose 函数会自动释放引用的 __block变量(release)
__block 的__forwarding指针存在的意义?
为什么要通过 age 结构体里的__forwarding指针拿到 age 变量的值,而不直接 age 结构体拿到 age 变量的值呢?
__block 的__forwarding是指向自己本身的指针,为了不论在任何内存位置,都可以顺利的访问同一个 __block 变量。block 对象 copy 到堆上时,内部的 __block 变量也会 copy 到堆上去。为了防止 age 的值赋值给栈上的 __block 变量,就使用了__forwarding;
当 __block 变量在栈上的时候,__block 变量的结构体中的__forwarding指针指向自己,这样通过__forwarding取到结构体中的 age 给它赋值没有问题;
当 __block 变量 copy 到堆上后,栈上的__forwarding指针会指向 copy 到堆上的 _block 变量结构体,而堆上的__forwarding指向自己;
这样不管我们访问的是栈上还是堆上的 __block 变量结构体,只要是通过__forwarding指针访问,都是访问到堆上的 __block 变量结构体;给 age 赋值,就肯定会赋值给堆上的那个 __block 变量中的 age。
3.5.5 对象类型的 auto 变量、__block 变量内存管理区别
- 当 block 在栈上时,对它们都不会产生强引用
- 当 block 拷贝到堆上时,都会通过 copy 函数来处理它们
对象类型的 auto 变量(假设变量名叫做p) | __block 变量(假设变量名叫做a) |
---|---|
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/); | _Block_object_assign((void*)&dst->a, (void*)src->a, 8/BLOCK_FIELD_IS_BYREF/); |
_Block_object_assign函数会根据 auto 变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用 | _Block_object_assign函数会对 __block 变量形成强引用(retain) |
- 当 block 从堆上移除时,都会通过 dispose 函数来释放它们
对象类型的 auto 变量(假设变量名叫做p) | __block 变量(假设变量名叫做a) |
---|---|
_Block_object_dispose((void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/); | _Block_object_dispose((void*)src->a, 8/BLOCK_FIELD_IS_BYREF/); |
3.5.6 被 __block 修饰的对象类型
- 当 __block 变量在栈上时,不会对指向的对象产生强引用
- 当 __block 变量被 copy 到堆时 ①
__Block_byref_object_0
即 __block 变量内部会新增两个函数: copy(__Block_byref_id_object_copy
) dispose(__Block_byref_id_object_dispose
) ② 会调用 __block 变量内部的 copy 函数 ③ copy 函数内部会调用_Block_object_assign
函数 ④_Block_object_assign
函数会根据所指向对象的修饰符(__strong
、__weak
、__unsafe_unretained
)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于 ARC 时会 retain,MRC 时不会 retain) - 如果 __block 变量从堆上移除 ① 会调用 __block 变量内部的 dispose 函数 ② dispose 函数内部会调用
_Block_object_dispose
函数 ③_Block_object_dispose
函数会自动释放指向的对象(release)
int main(int argc, char * argv[]) {NSString * appDelegateClassName;@autoreleasepool {// Setup code that might create autoreleased objects goes here.appDelegateClassName = NSStringFromClass([AppDelegate class]);__block NSObject *object = [[NSObject alloc] init];void(^block)(void) = ^{object = [[NSObject alloc] init];};}return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
struct __Block_byref_object_0 {void *__isa;__Block_byref_object_0 *__forwarding;int __flags;int __size;void (*__Block_byref_id_object_copy)(void*, void*); // copyvoid (*__Block_byref_id_object_dispose)(void*); // disposeNSObject *__strong object;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_object_0 *object; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_object_0 *_object, int flags=0) : object(_object->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_object_0 *object = __cself->object; // bound by ref(object->__forwarding->object) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->object, (void*)src->object, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->object, 8/*BLOCK_FIELD_IS_BYREF*/);}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, char * argv[]) {NSString * appDelegateClassName;/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));__attribute__((__blocks__(byref))) __Block_byref_object_0 object = {(void*)0,(__Block_byref_object_0 *)&object, 33554432, sizeof(__Block_byref_object_0), __Block_byref_id_object_copy_131,__Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_object_0 *)&object, 570425344));}return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}static void __Block_byref_id_object_copy_131(void *dst, void *src) {// __block 对象结构体的地址+40个字节,即为结构体中 object 对象的地址_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);
}
注意:在 MRC 下使用 __block 修饰对象类型,在 block 内部不会对该对象进行 retain 操作,所以在 MRC 环境下可以通过 __block 解决循环引用的问题。
示例(MRC)
// 对象类型的捕获,连同所有权修饰符一起捕获,所以 block 对 person 强引用HTPerson *person = [[HTPerson alloc] init];void(^block)(void) = [^{NSLog(@"%p", person);} copy];[person release];block();[block release];// 0x10053da30
// -[HTPerson dealloc]
// __block 修饰的对象类型的捕获,MRC 下在 block 内部不会对该对象进行 retain 操作__block HTPerson *person = [[HTPerson alloc] init];void(^block)(void) = [^{NSLog(@"%p", person);} copy];[person release];block();[block release];// -[HTPerson dealloc]
// 0x1007337d0
4.Block 的类型
block 有 3 种类型,可以通过调用 class 方法或者 isa 指针 查看具体类型,最终都是继承自 NSBlock 类型。
block类型 | 描述 | 环境 |
---|---|---|
__ NSGlobalBlock __( _NSConcreteGlobalBlock ) | 全局block,保存在数据段 | 没有访问auto变量 |
__ NSStackBlock __( _NSConcreteStackBlock ) | 栈block,保存在栈区 | 访问了auto变量 |
__ NSMallocBlock __( _NSConcreteMallocBlock ) | 堆block,保存在堆区 | __ NSStackBlock __调用了copy |
打印各种 block 的类型,以及遍历 block 的父类类型,如下:
int main(int argc, const char * argv[]) {@autoreleasepool {void(^block1)(void) = ^{NSLog(@"hello");};/* block2** 在ARC下会自动copy,从栈复制到堆,所以为__NSMallocBlock__类型** 在MRC下为__NSStackBlock__类型,需要手动调用copy方法才会变为__NSMallocBlock__类型** 同时,在不需要该block的时候需要手动调用release方法*/ int age = 10;void(^block2)(void) = ^{NSLog(@"%d",age);};NSLog(@"%@,%@,%@", [block1 class], [block2 class], [^{NSLog(@"%d",age);} class]);Class class = [block1 class];while (class) {NSLog(@"%@",class);class = [class superclass];}}return 0;
}
// __NSGlobalBlock__,__NSMallocBlock__,__NSStackBlock__
// __NSGlobalBlock__
// __NSGlobalBlock
// NSBlock
// NSObject
每一种类型的 block 调用 copy 后的结果如下所示:
block类型 | 副本源的配置存储区 | 复制效果 |
---|---|---|
_NSConcreteGlobalBlock | 程序的数据段区 | 什么也不做 |
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
__ NSStackBlock __ 存在的问题:
以下是在 MRC 环境下,block 类型为__NSStackBlock__
。 当 test() 函数执行完毕,栈上的东西可能会被销毁,数据就会变成垃圾数据。尽管 block 还能正常调用,但是输出的 age 的值发生了错乱。
void (^block)(void);
void test()
{ // __NSStackBlock__int age = 10;block = ^{NSLog(@"%d", age);};
}
int main(int argc, const char * argv[]) {@autoreleasepool {test();block();}return 0;
}
// 272632936
解决办法:调用copy
方法,将栈 block 复制到堆。
void (^block)(void);
void test()
{ // __NSMallocBlock__int age = 10;block = [^{NSLog(@"%d", age);} copy];
}
int main(int argc, const char * argv[]) {@autoreleasepool {test();block();[block release];}return 0;
}
// 10
Block 的 copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下几种情况:
手动调用block的copy方法时;
block作为函数返回值时(Masonry 框架中用很多);
将block赋值给__strong 指针时;
block作为Cocoa API中方法名含有usingBlock 的方法参数时;
block 作为 GCD API 的方法参数时。
block作为属性的写法:
ARC下写strong 或者 copy 都会对block进行强引用,都会自动将block从栈copy到堆上;
建议都写成copy,这样MRC和ARC下一致。
// MRC
@property (nonatomic, copy) void(^block)(void);
// ARC
@property (nonatomic, copy) void(^block)(void);
@property (nonatomic, strong) void(^block)(void);
Block 的循环引用问题
为什么block会产生循环引用?
1.自循环引用:如果block 对当前对象的某一成员变量进行捕获的话,可能会对他产生强引用。而当前block又犹豫当前对象对其有一个强引用,就产生了自循环引用的问题;
2.大环引用:我们如果使用__block的话,在ARC下可能会产生循环引用(MRC则不会),在ARC下可以通过断环的方式去解除循环引用。但是有一个弊端,如果该block一直得不到调用,循环引用就一直存在。
//. ARC
__weak typeof(self) weakSelf = self;
self.block = ^{NSLog(@"%p", weakSelf);
};__unsafe_unretained id weakSelf = self;
self.block = ^{NSLog(@"%p", weakSelf);
};
//注意:__unsafe_unretained 会产生悬垂指针
用__block解决(必须要调用block):
缺点:必须要调用block,而且block里要将指针置为nil。如果一直不调用block,对象就会一直保存在内存中,造成内存泄露。
__block id weakSelf = self;self.block = ^{NSLog(@"%p",weakSelf);weakSelf = nil;};self.block();
//. MRC用__unsafe_unretained 解决:同ARC用 __block 解决(在MRC下使用__block修饰对象类型,在block内部不会对该对象进行retain操作,所以在MRC环境下可以通过__block解决循环引用的问题)__block id weakSelf = self;
self.block = ^ {NSLog(@"%p", weakSelf);
};
总结:
block的本质是什么?
封装了函数调用以及调用环境的OC对象。
block的修饰属性词为什么是copy?使用block有哪些使用注意?
block 一旦没有进行copy操作,就不会在堆上。
使用注意:循环引用问题。
block在给NSMutableArray添加或移除对象,需不需要添加__block?
不需要
block的变量捕获机制
block的变量捕获机制,是为了保证block内部能够正常正常访问外部的变量。
对于全局变量,不会捕获到block内部,访问方式为直接访问;
对于auto类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,用来存储这个变量的值,访问方式为值传递;
对于static类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,用来存储这个变量的地址,访问方式为指针传递;
对于对象类型的局部变量,block会连同它的所有权修饰符一起捕获。
为什么局部变量需要捕获,全局变量不用捕获呢?
作用域的原因,全局变量哪里都可以直接访问,所以不用捕获;
局部变量,外部不能直接访问,所以需要捕获;
auto类型的局部变量可能会销毁,起内存会消失,block将累执行代码的时候不可能再去访问那块内存,所以捕获其值;
static变量会一直保存在内存中,所以捕获其地址即可。
self会不会捕获到block内部?
会捕获
OC方式都有两个隐式参数,方法调用者self和方法名 _cmd。参数也是一种局部变量。
_name会不会捕获到block内部?会捕获。不是将 _name 变量进行捕获,而是直接将 self捕获到block内部,_name是一个属性
__NSStackBlock_存在的问题:
如果没有将block从栈上copy到堆上,那我们访问栈上的block的话,可能会由于变量作用域结束导致栈上的block以及__block变量被销毁,而造成内存崩溃。或者数据可能会变成垃圾数据,尽管将来block还呢正常调用,但是它捕获的变量的值已经错乱了。
解决办法:将block的内存放在堆里,意味着它就不会自动销毁,而是我们程序员来决定什么时候销毁它。
默认情况下block是不能修改外面的auto变量的,解决办法?
变量用static修饰(原因:捕获static类型的局部变量是指针传递,可以访问到该变量的内存地址)
全局变量
__block(我们只希望临时用一下这个变量临时改一下而已,而改为static变量和全局变量会一直在内存中)
__block修饰符使用注意点:在MRC下使用__block修饰对象类型,在block内部不会对该对象进行retain操作,所以在MRC环境下可以通过__block解决循环引用的问题。
__block的_forwarding指针存在的意义?为什么要通过age结构体里的__forwarding指针拿到age变量的值,而不直接age结构体拿到age变量的值呢?见__block的_forwarding指针
为什么通过_weak去修饰成员变量或对象就可以达到规避循环引用的目的呢?block对于对象类型的局部变量联通所有权修饰符一起截获,多亿如果我们在外部定义的对象是__weak所有权修饰符的,那么在block中所产生的结构体里所持有的变量也是__weak类型的
Q:解决在 block 内部通过弱指针访问对象成员时编译器报错的问题:
__weak typeof(self) weakSelf = self;self.block = ^{__strong typeof(weakSelf) strongSelf = weakSelf;NSLog(@"%d",strongSelf->age);};
Q:以下代码的打印结果是?
__block int multiplier = 6;int (^block)(int) = ^(int num) {return num * multiplier;};multiplier = 4;NSLog(@"%d",block(2));// 8
Q:以下代码有问题吗?
__block id weakSelf = self;self.block = ^{NSLog(@"%p",weakSelf);};
- 在 MRC 下,不会产生循环引用;
- 在 ARC 下,会产生循环引用,导致内存泄漏,解决方案如下。
__block id weakSelf = self;self.block = ^{NSLog(@"%p",weakSelf);weakSelf = nil;};self.block();
缺点:必须要调用 block,而且 block 里要将指针置为 nil。如果一直不调用 block,对象就会一直保存在内存中,造成内存泄漏。
Block详解------已完结相关推荐
- Linux进程详解(二)完结
原创架构师之路2019-08-13 22:08 接Linux进程详解(一) 4. 进程运行 程序运行时大部分进程状态为运行或睡眠.调度算法解决可以跑的运行状态(就绪和运行),剩下的不可以跑的进程就是睡 ...
- Ardupilot姿态控制详解(完结篇)
本文主要是对过去写的一些APM中姿态控制函数博文的汇总. 按照之前写的博文以及个人认为的学习顺序,给出APM姿态控制器的学习顺序如下: APM姿态旋转理论基础 Ardupilot姿态控制器 PID控制 ...
- LSTM反向传播详解(完结篇)Part3/3代码实现
1.前记 LSTM系列的前面两篇文章<LSTM反向传播详解Part1><LSTM反向传播详解Part2>将LSTM的理论梳理了一遍,本文主要着重用代码实现LSTM,其实也是 ...
- oc开发之block详解
2019独角兽企业重金招聘Python工程师标准>>> BLOCK是什么?苹果推荐的类型,效率高,在运行中保存代码.用来封装和保存代码,有点像函数,BLOCK可以在任何时候执行.标识 ...
- linux存储--inode与block详解(七)
基本概念 首先讲下inode和块的基本概念.在Linux系统中,文件由元数据和数据块组成.数据块就是多个连续性的扇区(sector),扇区是文件存储的最小单位(每个512字节).块(block)的大小 ...
- inode与block详解
创建一个文件后,会同时创建一个inode和一个block,inode存放的是文件的属性信息,但是不包括文件名,并存放所对应数据所在的block块的地址的指针:block存放文件的数据,每个block最 ...
- linux存储--inode与block详解(八)
Linux下的格式化命令是mkfs,mkfs在格式化的时候需要制定分区以及文件系统类型.该命令其实就是把我们的连续的磁盘空间进行划分和管理.我在我的机器上执行了一下,输出如下: # mkfs -t e ...
- 信息安全-5:RSA算法详解(已编程实现)[原创]
转发注明出处:http://www.cnblogs.com/0zcl/p/6120389.html 背景介绍 1976年以前,所有的加密方法都是同一种模式: (1)甲方选择某一种加密规则,对信息进行加 ...
- HDFS 概念之 block 详解
一般情况下,任何磁盘都有'最小读写单位'的概念,可以理解为该磁盘的'block'.建立在该磁盘之上的文件系统也有'block'的概念,一般为磁盘'block'大小的整数倍.对于用户来说,这些读写限制都 ...
最新文章
- 11大Java开源中文分词器的使用方法和分词效果对比
- A query was run and no Result Maps were found
- 文字两侧加横线的解决方案
- 通过JDK动态代理实现拦截器
- 文远知行公布自动驾驶运营路线图:全新升级自动驾驶方案,建立500辆规模的自动驾驶车队...
- PLC编程入门基础技术知识
- HTML制作简易个人简历(表单)
- (十五)TcpClient
- Vasp 石墨烯能带计算
- 去国企1年后,我后悔了!重回大厂内卷
- tp6多表联合查询的几种方式(模糊搜索+分页+字段限制)
- reboot Linux 命令使用,linux中的reboot命令的详细解释
- Java exception was raised during method invocation
- 浙江大学2020计算机考研复试线,浙江大学2020考研复试分数线已公布
- 微服务架构-服务网关(Gateway)-服务网关在微服务中的应用
- 线性代数28——复矩阵和快速傅立叶变换
- 怎么用python读取excel图_Python如何读取excel中的图片
- 结构-02. 有理数加法(15)
- Hive教程(一)---hive入门
- [转载]从100PV到1亿级PV网站架构演变