一、 Blocks是C语言的 扩展功能:带有自动变量(局部变量)的匿名函数。
C语言中用到的变量:自动变量(局部变量)、静态变量、静态局部变量、全局变量、函数参数
函数多次调用之间能够传递值的变量有:静态变量(静态局部变量)、静态全局变量、全局变量。因为在整个程序中,一个变量总保持在一个内存区域,虽然多次调用但是该变量值总能保持不变,在任何时候以任何状态调用,使用的都是同样的变量值。
二、Blocks模式
1、Block语法:^ 返回值类型 (参数列表) {表达式}
Block类型变量:
a 通过typedef声明blk_type类型变量
typedef int (^blk_type)(int);
blk_type blk_1;
b 一般声明Block类型变量:
int (^blk_1)(int);
将Block赋值为Block类型变量:
int (^blk_1)(int) = ^(int count){return count+1;};
int (^blk_2)(int);
blk_2=blk_1;
Block类型变量与C语言变量一样,可以作为:自动变量、静态变量、静态局部变量、全局变量、函数参数
(函数返回值、指向Blcok类型的变量即Block的指针类型变量)。
2、截获自动变量值
“带有自动变量值”在Blocks中表现为“截获自动变量值”:即保存该自动变量的瞬间值,保存后就不能改写该值
3、__block说明符
若想在Blocks语法表达式中将 值 赋给在Blocks语法外声明的 自动变量,需要在该自动变量上附加__block说明符。
4、截获的自动变量(非OC对象、OC对象、数组)
a 将值赋值给Block中截获的自动变量,会产生编译错误。
b 截获OC对象,对OC对象赋值会产生编译错误( obj=obj ),但是使用OC对象就不会产生编译错误( [obj addObject:**] )
c 在Blocks中截获自动变量的方法并没有实现对C语言数组的截获,可以使用指针解决该问题。
const char text[]="hello";
void(^blk)(void)=^{printf("%c\n",text[2]);}
以上代码报错。修改为:
const char *text="hello";
void(^blk)(void)=^{printf("%c\n",text[2]);}
三、Blocks实现
1、Block的实质:一个Block是一个结构体实例,结构体中有isa指针,指向所属类,说明:Block是OC对象。
注:所属类也是一个结构体实例,结构体中有isa指针,指向其父类-----一级一级直到超类
“OC中由类生成对象”:意味着“生成由该类生成的对象的结构体实例”,生成的各个对象,即由该类生成的对象的各个结构体实例,结构体中成员变量isa指针保持该类的结构体实例指针。
2、截获自动变量值
“截获自动变量值”:在执行Block语法时,Block语法表达式所 使用 的 自动变量值 被保存到Block的结构体实例(Block自身)中。
问题:c 在Blocks中截获自动变量的方法并没有实现对C语言数组的截获
因为C语言中不能数组赋值给数组,当自动变量为数组,保存到Block的结构体实例中出现数组赋值给数组,所以报错。
3、__block说明符
Block中 仅 “截获自动变量值”,重写该自动变量的值也不会改变原先截获的自动变量。
这样造成无法在Block中保存值,解决这个问题的两种方法:
a 使用静态变量、静态全局变量、全局变量,允许在Block中改值。
(注: 保存到Block结构体中的是指针,使用静态变量的指针进行对其访问)
b 使用__block说明符
自动变量附上__block存储域说明符:__block变量同Block一样变成结构体类型的自动变量,即栈上的生成一个结构体,持有原自动变量的成员变量。并且结构体中有成员变量__forwarding持有指向自身的指针。
注意:Block变量中有成员变量指针:指向__block变量的结构体。Block变量使用多个__block变量,就有多个成员变量指向不同__block变量。
4、Block存储域
Block对象的类有三种:
类 设置对象的存储域
_NSConcreteStackBlock 栈
_NSConcreteGlobalBlock 程序的数据区域(.data区)
_NSConcreteMallocBlock 堆
a 结构体类型的自动变量,即栈上生成的该结构体的实例。
前面Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量,都是在栈上的_NSConcreteStackBlock对象。
b 1、Block表达式中不使用应截获的自动变量时(不截获自动变量,可以将Block用结构体实例存储在数据区)
2、在写全局变量的有Block时(在写全局变量的地方不能使用自动变量,不存在对自动变量截获,Block用结构体的实例内容不 依赖执行时状态,整个程序中只需一个实例)
以上两种情况:Block为_NSConcreteGlobalBlock类对象。
c 将 _NSConcreteStackBlock对象 复制到堆上,就是_NSConcreteMallocBlock对象
-----------------Block超出变量作用域可存在的原因-----------------Blocks提供了将Block和__block变量从栈上复制到堆上的方法,这样即使Block变量的作用域结束,堆上的Block还可以存在,不受作用域影响。
-----------------__block变量用结构体成员变量__forwarding存在的原因-----------------__block变量的成员变量__forwarding可以实现,无论__block变量在栈上还是在堆上,都能正确访问__block变量。栈上__block变量的成员变量__forwarding指向堆上的结构体实例,堆上的__block变量的成员变量__forwarding指向自身结构体。
ARC有效时,以下情况,编译器提供自动生成将Block从栈上复制到堆上的代码
a、Block作为函数返回值时
b、Cocoa框架的方法且方法名含有usingBlock等时、GCD的API
编译器不能判断,需要手动copy情况:
a、Block作为函数参数时
---------------手动用copy复制效果(ARC有效时,多次调用也没有问题)--------------------
类 设置对象的存储域 复制效果
_NSConcreteStackBlock 栈 从栈复制到堆
_NSConcreteGlobalBlock 程序的数据区域(.data区) 什么也不做
_NSConcreteMallocBlock 堆 引用计数增强
5、__block变量存储域
---------------Block从栈复制到堆时对__block变量产生对影响---------------
__block变量对配置存储域 | Block从栈复制到堆时的影响 |
栈 | 从栈复制到堆并被Block持有 |
堆 | 被Block持有 |
多个Block中使用__block变量,在任何一个Block从栈复制到堆时,__block变量也会一并从栈复制到堆并被Block持有,剩下的Block从栈复制到堆时,被复制的__block变量引用计数增加。在堆上的Block被废弃,它所持有的__block变量也就被释放。
__block变量用结构体成员变量__forwarding存在的原因。通过Block的复制,__block变量也会一并从栈复制到堆,可以同时访问栈上和堆上的__block变量,栈上__block变量的成员变量__forwarding指向堆上的结构体实例,堆上的__block变量的成员变量__forwarding指向自身结构体。
val.__forwarding->val;
无论栈上__block变量val还是堆上__block变量val,此时访问的都是同一个__block变量(堆上的)。
6、截获对象
Block中截获的对象(有_strong修饰),是在Block用的结构体中附有_strong修饰符的成员变量,堆上Block持有该截获的对象,【和__block变量一样,由于被堆上Block持有】,因而超出其变量作用域可存在。
只有调用copy函数(自动或手动)才能持有 截获的【有_strong修饰的对象类型】、【__block变量】。
---------------调用copy函数和dispose函数的时机--------------
copy函数 栈上的Block复制到堆时
dispose函数 堆上的Block被废弃时
Block中使用对象类型自动变量时,什么时候栈上的Block会被复制到堆?
ARC有效时,以下情况,编译器提供自动生成将Block从栈上复制到堆上的代码
a、Block作为函数返回值时
b、Cocoa框架的方法且方法名含有usingBlock等时、GCD的API中传递Block时
c、调用Block的copy实例方法时
d、将Block赋值给附有_strong修饰符id类型的类或者Block类型成员变量时
id_obj = ^(){} ;
Block类型成员变量 = ^(){} ;
7、__block变量和对象
__block说明符可以指定任何类型的自动变量。
ARC有效时,id类型以及对象类型变量必定附加所有权修饰符,缺省为_strong修饰符。
__block id obj=[[NSObject alloc] init]; ------等同于------ __block id __strong obj = [[NSObject alloc] init];//超出作用域可存在
id __weak obj=obj1; ---------等同于---------- __block id __weak obj =obj1;//超出作用域无效
8、Block循环引用
如果Block中使用附有__strong修饰符的对象类型自动变量,那么当Block从栈复制到堆时,该对象为Block 持有,容易引起循环引用
typedef void (^blk_t)(void);
@interface MyObject:NSObject
{
blk_t blk;
}
@end
@implementation MyObject
-(id)init{
self = [super init];
blk = ^{NSLog(@"self = %@",self);}//将Block赋值给Block类型的成员变量,自动copy到堆
return self;
}
-(void)dealloc{
NSLog(@"dealloc");
}
@end
int main()
{
id o=[[MyObject alloc] init];
NSLog(@"%@",o);
return 0;
}
使用Block成员变量循环引用
a 声明附有__weak修饰符的变量并赋值self,可以避免循环引用。也可以使用__unsafe_unretained修饰符。
//@interface 声明中MyObject中有属性id obj;
-(id)init{
self=[super init];
id __weak tmp=self;//声明附有--weak修饰符的变量,并将self赋值使用
blk=^{NSLog(@"self = %@",tmp);};
id __weak obj0=obj;
blk=^{NSLog(@"self = %@",obj0);};//此时如果写obj相当于self->obj,依然会引起循环引用
return self;
}
使用Block成员变量避免循环引用
b 使用__block变量避免循环引用
typedef void (^blk_t)(void);
@interface MyObject:NSObject
{
blk_t blk;
}
@end
@implementation MyObject
-(id)init{
self = [super init];
__block id tmp = self;//使用__block变量
blk = ^{ NSLog(@"self = %@",tem);
tmp=ni; //变量赋值nil
}//将Block赋值给Block类型的成员变量,自动copy到堆
return self;
}
-(void)execBlock
{
blk();
}
-(void)dealloc{
NSLog(@"dealloc");
}
@end
int main()
{
id o=[[MyObject alloc] init];
[o execBlock];//执行Block
return 0;
}
如果没有调用execBlock实例方法,即不执行赋值给成员变量blk的Block,会引起循环引用并引起内存泄漏。
通过执行execBlock实例方法,Block被执行,__block变量tmp被赋值nil,__block变量tmp对MyObject类对象的强引用失效。
使用__block变量 和 使用__weak 及__unsafe_unretained 避免循环引对比:
使用__block变量优点:
1、通过__block变量可控制对象的持有期间
2、在不能使用__weak的环境中不使用__unsafe_unretained即可。
在执行Block时可动态决定是否将nil或其他对象赋值在__block变量中。
使用__block变量缺点:
为避免循环引用必须执行Block
9、copy/release
ARC无效时,需要手动将Block从栈复制到堆。用copy实例方法用来复制,用release实例方法来释放。
只要Block有一次复制到堆上,就可以用retain方法持有。在栈上Block调用retain不起任何作用。
ARC无效时,__block说明符被用来避免Block中的循环引用。
这是由于当Block从栈复制到堆时,若Block使用的变量为附有__block的id类型或对象类型的自动变量,不会被retain,,若Block使用的变量为没有__block的id类型或对象类型的自动变量,则被retain,引起循环引用。
由于ARC有效和无效时,__block说明符用途有很大区别,需要注意!