概要
- 带有自动变量的匿名函数
Blocks模式
语法(表达式/函数区别于类型变量)
^返回值类型(可省)+参数列表(可省)+表达式
^int (int count){
return count + 1;
}
Block类型变量
//声明block类型变量
int (^blk)(int)
//函数指针
int(*funcptr)(int) = &func
- block类型变量与c语言类型变量一样可以作为自动变量、函数参数、全局变量、静态变量、静态全局变量
- 通过block类型变量调用Block与c语言函数调用没有区别
//使用typedef简化类型变量
typedef int (^blk)(int)
截获自动变量值
在Block表达式中使用自动变量时,或截获保存其瞬间值,之后的改动不会影响截获值
__block说明符
在Block表达式中++重新赋值++自动变量(对象类型)时,会产生编译错误,需要声明__block类型
截获的自动变量
- 在Block表达式中使用截获的自动变量没问题,但是赋值会编译报错,例如NSMutableArray对象可以正常添加元素,但是重新赋值给这个指针就会编译报错
使用c语言数组会编译报错
const char text[] = "hello"
void (^blk)(void) = ^{
char text = text[2];
}
Blocks中没有对c语言数组支持,使用指针可以解决
const char *text = "hello"
void (^blk)(void) = ^{
char text = text[2];
}
Blocks实现
Block的实质
将oc代码转为c++代码的clang指令
clang -rewrite-objc 源文件名
block的本质是__main_block_impl_0(mian所在的函数名,0函数调用顺序)结构体实例的调用,结构体为
struct __main_block_impl_0{
void *isa;//初始化为&_NSConcreteStackBlock,相当于对象的class_t的结构体
int flags;
int reserved;
void *Funptr;//初始化指向调用函数__main_block_func_0
struct __main_block_desc_0 *desc;
}
class_t结构体
struct class_t{
struct class_t *isa;
struct class_t *superclass;
Cache cache;//方法缓存
IMP *vtable;//方式实现
unitptr_data_NEVER_USE
}
截获自动变量值
截获的自动变量会赋值到Block的成员变量当中,Block中为使用的自动变量不会截获
struct __main_block_impl_0{
void *isa;
int flags;
int reserved;
void *Funptr;
struct __main_block_decs_0 *decs;
//截获的成员变量,构造函数初始化时进行赋值
int val;
const char *fmt;
}
Block中不能使用c语言数组类型的变量,这是因为c语言数组类型变量不能赋值给c语言数组类型变量,可以使用指针来解决
void func(char a[10]){
char b[10] = a;//c语言数组赋值给数组非法
printf("%d\n",b[0])
}
int main(){
char a[10] = {2};
func(a);
}
__block说明符
在block中保存值的第一种方法是使用c语言中的静态变量,全局变量,静态全局变量;第二种则是使用"__block储存域说明符"
__block编译转换的代码:
struct __Block_byref_val_0{
void *isa;
__Block_byref_val_0 *__forwarding;//指向自身
int __flags;
iny __size;
int val;//使用值
}
struct __main_block_impl_0{
struct __block_impl impl;
struct __main_block_decs_0 *Decs;
__Block_byref_val_0 *val;//block使用__block结构体
...
}
__block变量的赋值代码的转换
^{val = 1};
转为:
static void __main_block_func_0(struct __main_block_impl_0 *__cself){
__Block_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;//通过forwarding指针访问使用值(主要为了从栈拷贝到堆中访问同一个内存区域)
}
Block存储域
Block类的存储区域
类 | 设置对象的区域 |
---|---|
_NSConreteStackBlock | 栈 |
_NSConcreteGlobalBlock | 数据区域(.data区) |
_NSConcreteMallocBlock | 堆 |
存储在程序的数据区域的情况:
- 记述在全局变量的地方有Block语法
- block语法中未使用应截获的对象
impl.isa = &_NSConcreteGlobalBlock
除此之外存储在栈区;
impl.isa = &_NSConcreteStackBlock
为了解决变量作用域结束时,栈上的block和__block变量被释放的问题,将栈上Block复制到堆上
impl.isa = &_NSConcreteMallocBlock
__block结构体变量中的__forwarding指针可以保证无论在栈上还是堆上,都能正确访问__block变量;
ARC有效时,大多数情形编译器都能够恰当的进行判断,自动的将block从栈上复制到堆上;例如将block作为函数返回值就会自动生成objc_retainBlock(),即_Block_copy;
栈上复制到堆上会消耗CUP资源,如果在栈上能够使用就不必复制,某些情况就需要手动复制;
- (NSArray *)getBlockArray{
int val = 10;
//需要执行copy复制到堆上,否则变量作用域结束,会销毁,执行异常
return [[NSArray alloc] initWithObjects:[^{NSLog(@"blk1--%d",val);} copy], [^{NSLog(@"blk2--%d",val);} copy], nil];
}
typedef void (^blk_t)(void);
testObj *obj = [[testObj alloc] init];
NSArray *arr = [obj getBlockArray];
blk_t blk = [arr objectAtIndex:1];
NSLog(@"blk class %@",[blk class]);
blk();
按不同储存区域copy之后的变化:
类 | 设置对象的区域 | 复制效果 |
---|---|---|
_NSConreteStackBlock | 栈 | 从栈复制到堆上 |
_NSConcreteGlobalBlock | 数据区域(.data区) | 什么都不做 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
ARC有效时多次复制不会有问题
__Block变量存储区域
1.当Block从栈中复制到堆中时,所截获的__block也会从栈中赋值到堆中,并且被Block所持有;当多个Block截获__block变量时,__block引用计数的会增加,符合引用计数的内存管理思考方式.
2.使用__block变量结构体中的__fowarding指针可以使得不管__block变量配置的栈上还是堆上都可以正确访问解释如下:
__block int val = 0;
void (^blk)(void) = [^{++ val;} copy];//堆上__block变量
++ val;//栈上__block变量
blk()
当__block从栈上复制到堆上时,栈上的__forwarding指针会指向堆上的__Block_byref_val_0结构体实例,确保访问同一个__block变量
val->__forwarding->val;//栈上和堆上都是这样访问
截获对象
概要:Block中捕获对象的持有与废弃,对象自动从栈copy复制到堆的情况
typedef void (^blk_t)(id obj);
blk_t blk;
{
NSMutableArray *array1 = [[NSMutableArray alloc] init];
blk = [^(id obj){
[array1 addObject:obj];
NSLog(@"array count : %ld",[array1 count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
NSLog(@"class:%@",[blk class]);
clang编译之后:
struct __main_block_impl_0{
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
id __strong array;//注意使用了__strong修饰符
}
c语言结构体中是不能使用__strong修饰符的对象类型,但是此处oc运行库可以准确把握Block从栈复制到堆以及从堆中废弃的时机;为此,需要在__main_block_desc_0结构体中增加copy和dispose成员变量,以及赋值的成员变量函数__main_block_copy_0和__main_block_dispose_0;两个函数定义如下:
__Block_object_assign函数相当于retain实例方法,持有对象
static void __main_block_copy_0(struct __main_block_impl_0 *dst,struct __main_block_impl_0 *src){
__Block_object_assign(&dst->array,src->array,BLOCK_FIELD_IS_OBJECT);//BLOCK_FIELD_IS_OBJECT表示对象;__block类型则是BLOCK_FIELD_IS_BYREF
}
__Block_object_dispose函数相当于release的实例方法,谁都不持有Block时调用
static void __main_block_dispose_0(struct __main_block_impl_0 *dst,){
__Block_object_dispose(&dst->array,BLOCK_FIELD_IS_OBJECT);
}
这两个函数不会主动调用,从栈copy到堆(本质调用_Block_copy)的时机如下:
- 调用Block函数的copy方法
- Block作为函数的返回值
- 将Block赋值给__strong修饰符的id类型或者赋值给Block类型的成员变量
- 方法名中含有usingBlock的cocoa框架方法或者GCD中传递的Block
__block也是同理,截获的__block从栈复制到堆上时调用copy函数,不再持有__block变量时调用dispose函数,所使用的参数是BLOCK_FIELD_IS_BYREF
__block变量和对象
__block修饰符可以指定任何类型的自动变量,例如指定对象类型
__block id object = [[NSObject alloc] init];
在Block中使用对象类型的的自动变量时,当Block从栈拷贝到堆中时,调用吧_Block_object_assign函数,持有Block捕获的对象;当堆上的Block废弃时,调用_Block_object_dispose函数,释放截获的对象.__block变量也会发生同样的过程,参数不一样BLOCK_FIELD_IS_OBJECT/BLOCK_FIELD_IS_BYREF
使用__weak修饰的对象则不能超出变量作用域存在,因为Block不持有对象,会被释放
Block循环引用
有两种方式:1.使用weak临时变量;2使用__block变量类型,在Block函数中将捕获的变量值为nil
一个循环引用的例子:
typedef void (^blk_t)(void);
@interface testObj : NSObject{
blk_t blk_;//testObj类持有block
}
@end
@implementation testObj
- (instancetype)init{
if (self = [super init]) {
blk_ = ^{NSLog(@"self = %@",self);};//block赋值给成员变量,block从栈拷贝到堆中;self是__strong修饰的id类型,block持有self
}
return self;
}
1.第一种方法,使用__weak修饰符的方式
id __weak weakSelf = self;
blk_ = ^{NSLog(@"self = %@",weakSelf);};//Block结构体中捕获的self为__weak类型,不再持有self
1.2.面向iOS4的程序可以使用__unsafe_unretained修饰符.由于在调用block函数时,self必定是存在的,不用担心调用时weakSelf为空,产生悬垂指针的情况
id __unsafe_unretained weakSelf = self;
blk_ = ^{NSLog(@"self = %@",weakSelf);};
1.3.Block中使用了成员变量,捕获的也是self
typedef void (^blk_t)(void);
@interface testObj : NSObject{
blk_t blk_;//testObj类持有block
id obj_;
}
@end
@implementation testObj
- (instancetype)init{
if (self = [super init]) {
blk_ = ^{NSLog(@"obj_ = %@",obj_);};//obj_只是对象的成员变量,捕获的是self
}
return self;
}
使用个临时weak变量传到Block中(或者__unsafe_unretained)
id __weak weakObj = obj_;
blk_ = ^{NSLog(@"obj_ = %@",weakObj);}
2.第二种方法,使用__block变量
__block id tmp = obj_;
blk_ = ^{
NSLog(@"obj_ = %@",tmp);
tmp = nil;//将对象置位nil,使得__block结构体类型__Block_byref_tmp_0不再持有tmp对象
}
如果不执行blk_()调用,则会引起内存泄露,Block->__Block变量->tmp对象->Block(成员函数)
使用__block变量的优点:
- 通过__block变量可以控制对象的持有期
- 在ios4的环境中使用,不必担心悬空指针(unsafe_unretained)
- 在执行Block时可以动态的决定是否将nil或者其他对象赋值到__block变量中
缺点:
- 为避免循环引用,必须执行block调用
copy/release
1.ARC无效时copy和release;2.ARC有效无效时__block解决循环引用的区别
1.ARC无效时,一般需要手动从栈复制到堆中.
blk_t blk_on_heap = [blk_on_stack copy];
[blk_on_heap release];
- 推荐使用copy来持有Block,使用retain的话,如果是[blk_on_stack retain]则不起作用;
- 在c语言中可以使用Block_copy()/Block_release()来替换
2.ARC有效无效时__block解决循环引用的区别
ARC无效时,使用了__Block修饰的对象类型,该对象不会retain,即__block不持有对象;无__Block修饰则会自动retain.在解决循环引用上与ARC有效时有区别:
__block id tmp = self;
blk_ = ^{NSLog("self = %@",tmp)};//不需要tmp置nil,因为ARC无效时__block不持有对象