前言
上篇文章整理了关于“内存管理”的知识点,这篇主要整理“Blocks”的内容,在学习Blocks的内容时,我一开始也采取了学习“内存管理”时采用的先阅读《iOS与OS X多线程和内存管理》再配合《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法 》,但是在学习《iOS与OS X多线程和内存管理》时有些理解比较困难,恰恰《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法 》中的内容虽然比较少,但对阅读《iOS与OS X多线程和内存管理》中的内容很有帮助,当然还有别的学习途径。以下内容如有理解错误的地方,希望大家指出,我们一起学习和探讨~
什么是blocks
BIocks是C语言的扩充功能,可概括为:带有自变量值的(局部变量)的匿名函数(匿名函数就是不带名称的函数)
-
C语言的函数中可能使用的变量
- 自动变量(局部变量)
- 函数的参数
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
-
在函数的多次调用之间能够传递值的变量(虽然这些变量的作用域不同,但在整个程序中,一个变量总保持在一个内存区域,所以虽然多次调用函数,该变量值总保持不变)
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
-
C++和OC使用类可保持变量值且能够多次持有该变量自身,它会声明持有成员变量的类,有类生成的实例或对象保持该成员变量的值
-
‘带有自变量值的的匿名函数’这一个概念不仅指Blocks,它还存在于其他许多程序语言中,在计算机科学中,次概念也称为‘闭包(closure)’、‘lambda’
FD8CBA1E-3A58-48D1-8DE9-1BDD7061F00D.png
Blocks模式
Block语法(Block表达式语法)
-
Block语法与函数的不同
- 没有函数名
- 带有 ‘^’
-
Block语法(无省略)
^int (int count){return count + 1;}
//表达式中含有return语句时,其类型必须与返回值类型相同
- Block语法(省略返回值类型)
^ (int count){return count + 1;}
//省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,如果表达式中没有return语句就是void类型,表达式中含有多个return语句时,所以return的返回值类型必须相同
- Block语法(省略返回值类型、参数列表)
//不使用参数的Block语法
^void (void){ }
//省略后表达式为
^{ }
Block类型变量
-
在Block语法下,可将Block语法赋值给声明为Block类型的变量中,即代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的‘值’
-
Blocks中由Block语法生成的值也被称为‘Block’
-
Block即指代码中的Block语法,又指由Block语法所生成的值
-
声明Block类型变量
int (^blk)(int);
返回类型(^变量名)(参数)
- Block类型的变量用途
- 自动变量
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
//-使用Block语法将Block赋值为Block类型变量
int(^blk)(int) = ^int(int count){return count + 1;};
//由Block类型变量向Block类型变量赋值
int(^blk1)(int) = blk;
int(^blk2)(int);
blk2 = blk1;
//在函数参数中使用Block类型变量可以函数传递Block(值)
void fun(int(^blk)(int)){
}
//在函数返回值中指定Block类型,可以将Block作为函数的返回值返回
int(^func())int{
return ^(int count){return count + 1;};
}
//使用 typedef
typedef int(^blk_t) int;
//原记述方式
void fun(int(^blk)(int)){
}
//等价于
void fun(int(blk_t blk){
}
//原记述方式
int(^func())int{
return ^(int count){return count + 1;};
}
//等价于
blk_t func{
}
截获自动变量值
- ‘带有自动变量值’在Blocks中表现为‘截获自动变量值’
int main{
int day = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^ blk)(void) = ^{
printf(fit , val);
};
val = 2;
fmt = "There values were changed . val = %d\n";
blk();
return 0;
}
/*
*Block语法的表达式使用的是它之前声明的自动变量fmt和val
*Blokcs中,Block表达式中截获所使用的自动变量的值,即Block表达式保存该自动变量的瞬间值
*执行Block语法后,即使改写Block中使用的自动变量也不会影响Block执行时自定变量的值
*结果输出 val = 10;
*/
_ _block说明符
- 自动变量的值截获只能保存执行Block语法瞬间的值,保存后就不能改写该值
int val = 0;
void (^blk)(void) = ^{ val = 1;};
blk();
printf("val = %d\n");
//编译报错
- 如果想在Block语法表达式中将值赋给Block语法外声明的自动变量(例如:val),需要在该自动变量上附加_ _block说明符
_ _block int val = 0;
void (^blk)(void) = ^{ val = 1;};
blk();
printf("val = %d\n");
//结果输出:val = 1;
- 使用附有_ block说明符在自动变量可以在Block中赋值,该变量称为 _block变量
截获自动变量
id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^{
id obj = [[NSObject alloc] init];
[arrray addObject:obj];
}
/*
*编译不会报错
*代码中截获的是变量值为NSMutableArray类的对象(即截获的是NSMutableArray类对象用的结构体实例指针)
*截获OC对象,调用变更改对象的方法(addObject),即使用截获对象不会出现任何错误
*/
id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^{
array = [[NSMutableArray alloc] init];
}
/*
*编译报错
*向截获的变量array赋值会产生编译错误
*/
_ _block id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^{
array = [[NSMutableArray alloc] init];
}
//编译成功
Blocks实现
Block的实质
- Block即为Object_C的对象
- C语言中有一下存储类的说明符
- typedef
- extern
- static(作为静态变量存储在数据区)
- auto(表示作为自动变量存储在栈中)
- register
Block存储域
- Block转换为Block的结构体类型自动变量
- _ block变量转换为 _block变量的结构体类型自动变量
- 结构体类型的自动变量,即栈上生成的该结构体的实例
-
Block也是OC对象,该Block的类
FDA8982E-C03A-46CE-8321-AE8D4E28DC85.png
- 记述全局变量的地方有Block语法时、Bolck语法的表达式中不使用应截获的自动变量时,Block为_NSConcreteGlobalBlock类的对象,即Block配置的程序的数据区域中
- 除上述情况Block配置的程序的栈上
-配置在全局变量上的Block,从变量的作用域外也可以通过指针安全使用 - 配置在栈上的Block,如果其所属的变量作用域结束,该Block就被废弃,由于_ _block变量也配置在栈上,同理。
- Blocks提供了将Block和_ _block变量从栈上复制到堆上的方法来解决,将配置在栈上的Block复制到堆上,这样即使Block语法记述的作用域结束,堆上的Block还是可以继续存在(ARC有效,自动生成将Block从栈上复制到堆上)
_ _block变量存储域
- Block从栈中复制到堆上时,_ _block变量也会受影响(下图)
- 若在1个Block中使用_ block变量,当该Block从栈中复制到堆上,使用的所有 block变量也必定配置在栈上,这些 block变量也全部被从栈中复制到堆上,此时,Block持有堆 _block变量,即使在该Block已被复制在堆上的情况下,复制Block也对所使用的_block变量没有任何影响(下图)
3FD99E0A-F6A2-4C4B-AEC0-98089992EEBC.png
-
在多个Block中使用_ block变量时,因为最先会将所有的Bolck配置在栈上,所以 block变量也会配置在栈上,在任何一个Block从栈复制到堆时, block变量也会一并从栈中复制到堆并被该Block所持有,当剩下的Block从栈中复制到堆时,被复制的Block持有 block变量(之前复制到堆上的),并增加 _block变量的引用计数(下图)
65B9DA56-8EC4-41CB-BE34-0884FC1F45E3.png
-
如果配置在堆上的Block被废弃,那么它所使用的_ block变量也就被释放(同理引用计数式内存管理,使用 block变量的Block持有 block变量,如果Block被废弃,它所持有的 _block变量也就被释放)(下图)
723DF0DA-B19B-4D66-8A64-1FD9129DEDD8.png
- Block的_ _main_block_impl_0结构体实例持有指向 _ _block变量的 _ _Block_dyref_val_0结构体实例指针
- _ _Block_dyref_val_0结构体实例的成员变量 _ _forwarding持有指向该实例自身的指针,通过成员变量 _ _forwarding访问成员变量val(成员变量val是该实例自身持有的变量,它相当于原自动变量)
- 不管_ block变量配置在栈上还是堆上,都能够正确的访问该变量(通过Block的复制,_ block变量也从栈上复制到堆,此时可同时访问栈上的 block变量和堆上的 _block变量)
- 栈上的_ block变量用结构体实例在 _block变量从栈复制到堆上时,会将成员变量 _ forwarding的值替换为复制目标堆上 _block变量用结构体实例的地址
- 通过该功能,无论是在Block语法中,Block语法外使用,_ block变量,还是 block变量配置在栈上还是堆上,都可以顺利访问同一个 _block变量。
截获对象
- 生成并持有NSMutableArray类的对象,由于附有_strong修饰符的赋值目标变量作用域立即结束,所以对象也被立即释放并废弃
id array = [[NSMutableArray alloc] init];
- 在Block语法中使用该变量array
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj){
[array addObjct:obj];
NSLog(@"array count = %ld",[array coun]);
} copy];
}
blk ([[NSObject alloc] init]);
blk ([[NSObject alloc] init]);
blk ([[NSObject alloc] init]);
//输出结果
array count = 1
array count = 2
array count = 3
/*
*输出结果意味着,赋值给变量array的NSMutableArray类的对象在该源代码最后Block的执行部分超出变量作用域而存在
*被赋值NSMutableArray类对象并被截获的自动变量array,它是Block用的结构体中附有_strong修饰符的成员变量
struct _main_block_impl_0{
struct _block_impl impl;
struct _main_block_desc_0 *Desc;
id strong array;
}
*Block用结构体中,还有附有_strong修饰符的对象类型变量array,所以需要恰当管理赋值给变量array的对象,因此_main_block_copy_0函数使用_Block_object_assjgn函数将对象类型对象赋值给Block用结构体的成员变量array中并持有该对象
*_Block_object_assjgn函数调用相当一retain实例方法函数,将对象赋值给对象类型的结构体成员变量中
*_main_block_dispose_0函数使用_Block_object_dispose函数,释放赋值在Block用结构体成员变量array中的对象,相当于release实例方法,释放赋值在对象类型结构体成员变量中的对象
*/
- 在Block从栈复制到堆时以及堆上的Block被废弃时会调用的函数
-
什么时候栈上的Block会复制到堆?
- 调用Blcok的coyp实例方法时(如果Block配置在栈上,那么该Block会从栈上复制到堆)
- Block作为函数返回值返回时(编译器自动的将对象的Block作为参数并调用_Block_cpoy函数-等同与copy实例方法)
- 将Block赋值给附有_strong修饰符id类型的类或Block类型成员变量时(编译器自动的将对象的Block作为参数并调用_Block_cpoy函数-等同与copy实例方法)
- 在方法名中含有usingBlock的Cocoa框架方法或GrandCentralDispatch的API中传递Block时(该方法或函数内部对传递过来的Block调用Block的copy实例方法或_Block_cpoy函数)
-
上述情况下栈上的Block被复制到堆上,其实实质是_Block_cpoy函数被调用时Block从栈上复制到堆
-在释放复制到堆上的Block后,谁都不持有Blcok而是其被废弃时调用despose函数,相当于对象的release实例方法 -
综上所述,通过使用附有_strong修饰符的自动变量,Block中截获的对象就能够超出其变量的作用域而存活
-
截获对象时和使用_ _block对象时copy函数或dispose函数的不同
- 通过BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF参数,区分copy函数和dispose函数的对象类型时对象还是_ _block变量
- 与copy函数持有截获的对象,dispose函数释放截获的对象相同,copy函数持有所使用的_ block变量,dispose函数释放所使用的 _block变量
- Block中使用的赋值给附有strong修饰符的自动变量的对象和复制到堆上的 _block变量由于被堆上的Block所持有,因而可超出其作用域而存在
- 只有调用_Block_cpoy函数才能持有截获的附有_strong修饰符的对象类型的自动变量值,如果没有调用_Block_cpoy函数,即使截获了对象,它也会随着变量的作用域结束而被废弃
- Block 中使用对象类型自动变量时,除以下情况外,推荐调用Block的copy实例方法
- Blcok作为函数返回值返回时
- 将Block赋值给类的附有_strong修饰符的id类型或Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或GrandCentralDispatch的API中传递Block时
_ _block变量和对象
-
_ _block说明符可指定任何类型的自动变量
-
在Block赋值给附有_strong修饰符的id类型或对象类型自动变量的情况下,当Block从栈中复制到堆时,使用_Block_object_assjgn函数,持有Block截获的对象,到堆上的Block被废弃时,使用_Block_object_dispose函数函数,释放Block截获的对象
-
在_ _block变量为附有strong修饰符的id类型或对象类型自动变量的情况下,当 _block变量从栈中复制到堆时,使用Block_object_assjgn函数,持有赋值给 block变量的对象,到堆上的 _block变量被废弃时,使用Block_object_dispose函数函数,释放赋值给 _block变量的对象
-
即使对象赋值复制到堆上的附有strong修饰符的对象类型 block变量中,只有 _block变量在堆上继续存在,那么该对象就会继续处于被持有的状态
-
在Block语法中使用该变量array
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
id _ _weak array2 = array;
_ _block id _ _weak array3 = array;
blk = [^(id obj){
[array2 addObjct:obj];
NSLog(@"array2 count = %ld,array2 count = %ld",[array2 coun],[array3 coun]);
} copy];
}
blk ([[NSObject alloc] init]);
blk ([[NSObject alloc] init]);
blk ([[NSObject alloc] init]);
//输出结果
array2 count = 0 array3 count = 0
array2 count = 0 array3 count = 0
array2 count = 0 array3 count = 0
/*
*由于附有_strong修饰符的变量array在该变量作用域结束的同时释放、废弃,nil被赋值在附有_ _weak修饰符的变量array2中,即使附加 _ _block修饰符附有_strong修饰符的变量array在该变量作用域结束的同时释放、废弃,nil被赋值在附有_ _weak修饰符的变量array3中
*/
Block循环引用
- 通过_weak修饰符,避免循环引用
typedef void (^blk_t) (void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
-(id)init{
self = [super init];
blk_ = ^{ NSLog(@"self = %@", self); }
return self;
}
-(void)dealloc{
NSLog(@"dealloc");
}
@end
int mian(){
id o = [[MyObject alloc] init];
NSLog(@"%@", o );
return o;
}
/*
* MyObject类的dealloc实例方法一定没有被调用
* MyObject类对象的Block类型成员变量blk_持有赋值为Block的强引用(MyObject类对象持有Block)
*init实例方法中执行的Block语法使用附有_strong修饰符的id类型变量self
*由于Block语法赋值在成员变量blk_中,通过Block语法生成在栈上的Block此时有栈复制在堆上,并持有所使用的self,self持有Block,Block持有self,形成循环引用
*/
D7124496FF78DECEAC96909FD5C49DCF.jpg
- 为避免此循环,可声明附有_weak修饰符的变量,并将self赋值使用
-(id)init{
self = [super init];
id _ _weak tmp = self;
blk_ = ^{ NSLog(@"self = %@", tmp); }
return self;
}
F2598D7757C5843EE46FC8CFA534C296.jpg
-
由于Block存在时,持有该Block的Myobject类对象即复制在变量tmp中的self必定存在,因此不需要判断变量tmp的值是否为nil
-
使用_ _block变量来避免循环引用
typedef void (^blk_t) (void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
-(id)init{
self = [super init];
_ _block id tmp = self;
blk_ = ^{
NSLog(@"self = %@", tmp);
tmp = nil;
}
return self;
}
-(void)execBlock{
blk_();
}
-(void)dealloc{
NSLog(@"dealloc");
}
@end
int mian(){
id o = [[MyObject alloc] init];
[o execBlock];
return o;
}
- 不执行execBlock实例方法,即不执行赋值给成员变量blk_的Block,会引起循环引用引起的内存泄露,存在循环引用:
- MyObject类对象持有Block
- Block持有 _ _block 变量
- _ _block变量持有MyObject类对象
- 执行execBlock实例方法,Block被实行,nil被赋值在 _ _block 变量tmp中
blk_ = ^{
NSLog(@"self = %@", tmp);
tmp = nil;
}
/*
*_ _block 变量tmp对MyObject类对象的强引用失效,避免循环引用
* MyObject类对象持有Block
* Block持有 _ _block 变量
*/
AF9D11E4CFFC05E678884E0135F637DC.jpg
copy/release
- ARC无效时,一般需要手动使用copy实例方法将Block从栈上复制到堆。通过release实例方法释放复制的Block
- 只要Block有一次复制并配置在堆上,就可通过retain实例方法持有(但对于配置在栈上的Block调用retain实例方法则不起作用)
- 在ARC无效时,_ block说明符被用来避免循环引用,这是由于当Block从栈复制到堆时,若Block使用的变量为附有 block说明符的id类型或对象类型的自动变量,不会被retain,若Block使用的变量为没有 block说明符的id类型或对象类型的自动变量,则会被retain(ARC有效或无效时 _block说明符的用途不同,要注意)
网友评论