block 简介
block 作为在 C语言的扩展,并不是高新技术,和其他语言的闭包或 lambda 表达式是一回事.需要注意的是由于 Objective-C在 iOS 中不支持 GC机制,使用 block 必须自己管理内存,而内存管理正是使用 bloc 坑最多的地方,错误的内存管理会导致 return cycle 内存泄露要么内存被提前释放了导致 crash.block 的使用很想函数指针,不过于函数最大的不同是:block 可以访问函数意外.词法作用域以内的外部变量的值.换句话说,block 不仅实现函数的功能,还能携带函数的执行环境.
可以这样理解,block 其实包含两个部分内容.
1.block 执行的代码,这是在编译的饿时候已经生成的;
2.一个包含 block 执行是需要的所有外部变量值的数据结构.block 将使用到.作用于附近到的变量的值简历一份快照拷贝到栈上.
block 与函数另一个不同是,block 类似 Obj 的队形,可以使用自动释放池管理内存(但 block 并不完全等于 Obj 对象,后面详细说明).
block 的类型与内存管理
根据 block 在内存中的位置分为三种类型
NSGlobalBlock,NSStackBlock,NSMallockBlobk.
NSGlobalBlock:类似函数 位于 text 段;
NSStackBlock:位于栈内存,函数返回后 Block 将无效;
NSMallocBlock:位于堆内存;
一.NSGlobalBlock 如下,我们可以通过是否引用外部变量识别,未饮用外部变量即为 NSGlobalBlock,可以当做函数使用
int main(int argc, const char * argv[]) {
//创建一个 NSGlobalBlock
float(^sum)(float,float)=^(float a,float b){
return a+b;
};
NSLog(@"block is %@",sum);//打印结果 block is <__NSGlobalBlock__: 0x100001050>
return 0;
}
NSStackBlock如下
int main(int argc, const char * argv[]) {
NSArray *testArr=@[@"1",@"2"];
void(^TestBlock)(void)=^{
NSLog(@"testArr:%@",testArr);
};
NSLog(@"block is %@",^{
NSLog(@"testArr :%@",testArr);
});//打印结果 block is <__NSStackBlock__: 0x7fff5fbff7d8>
//打印可看出 block 是一个 NSStackBlock,即在栈上,当函数返回时 block 将无效
NSLog(@"block is %@",TestBlock);
//打印结果 block is <__NSMallocBlock__: 0x100103b50>
//上面这句话在 非 ARC 中打印是 NSStackBlock. 但是在 ARC中就是 NSMallocBlock
//即在 ARC 中默认会将 block 从栈复制到堆上,而在非 ARC 中需要手动 copy.
return 0;
}
3.NSMallocBlock 只需要对 NSStackBlock 进行 copy 操作就可以获取,但是 retain 操作不可以,会在下面说明 Block 的 copy,retain,release 操作:
不同于 NSObjiect 的 copy,retain,release 操作:
Block_copy 与 copy等效,Block_release 于 release 等效:
对 Block 不管是 retain,copy,release 操作都无效:
NSStackBlock:retain,release操作无效,必须注意的是 ,NSStackBlock在函数返回后,Block 内存将被回收.即使 retain 也没用.容易犯的错误是[mutableArray addObject:stackBlock],(在 ARC 中不用担心此问题)
NSMallocBlock 支持 retain,release,虽然 retain count 始终是1,但内存管理器中仍然会增加,减少计数,copy 之后不会生成新的对象,只是增加了一次引用.类似 retain;
尽量不要对 Block使用retain操作
Block 对外部变量的存取管理
基本数据类型
1.局部布局
局部自动变量,在 Block 中只读.Block 定义时 copy 变量的值.在 Block 中作为常量使用,所以即使变量的值在 Block 外改变,也不影响他在 Block 中的值
int main(int argc, const char * argv[]) {
int base=100;
long(^sum)(int ,int)=^long(int a,int b){
return base+a+b;
};
base=0;
printf("%ld\n",sum(1,2));
//这里输出的是103,而不是3,因为快内的 base 拷贝的常量100
return 0;
}
2.static 修饰符修饰的全局变量
以为全局变量或者静态变量在内存中的地址是固定的,Block 在读取该变量的时候是直接从其所在的呢村读出的,获取到的是最新值,而不是在定义时 copy 的常量,
int main(int argc, const char * argv[]) {
static int base=100;
long(^sum)(int,int)=^long(int a,int b){
base ++;
return base+a+b;
};
base=0;
printf("%ld\n",sum(1,2));
//这里输出的是4,而不是103,因为 base 被设置为0
printf("%d\n",base);
//这里输出1,在块中被自加了
return 0;
}
__BLOCK修饰的变量
Block 变量,被__block 修饰的变量称为 Block 变量.基本类型的 Block 变量等效于全局变量,或静态变量.Block 被另一个 Block 使用时,另一个 Block 被 copy 到堆上时,被使用的 Block 也会被 copy.但作为参数的 Block 不会不会发生 copy
Objc 对象
Block 对于 objc 对象的内存管理较为复杂.这里要分 static global local block 变量分析
循环引用 retain cycle
循环义勇是指两个对象相互强引用了对方,即 retain 了对方,从而导致谁也释放不了谁的内存泄露问题.如生命一个 delegate 是一般用 assign 而不能用 retain 或 strong,因为一旦你那么做了,很大可能一起循环引用.在以往的项目中,我几次用动态内存检查发现了循环引用导致内存泄露的问题
这里将的是 block 得循环义勇问题,因为 block 在拷贝到堆上的时候,会 retain 起引用的外部变量,那么如果 block 中如果引用了他的宿主对象,那么很有可能引起循环引用
self.myblock = ^{
[self doSomething];
};
//为测试循环引用,写了些测试代码用于避免循环引用的方法
- (void)dealloc
{
NSLog(@"no cycle retain");
}
- (id)init
{
self = [super init];
if (self) {
#if TestCycleRetainCase1
//会循环引用
self.myblock = ^{
[self doSomething];
};
#elif TestCycleRetainCase2
//会循环引用
__block TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase3
//不会循环引用
__weak TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase4
//不会循环引用
__unsafe_unretained TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#endif
NSLog(@"myblock is %@", self.myblock);
}
return self;
}
- (void)doSomething
{
NSLog(@"do Something");
}
int main(int argc, char *argv[]) {
@autoreleasepool {
TestCycleRetain* obj = [[TestCycleRetain alloc] init];
obj = nil;
return 0;
}
}
经过上面的测试发现,在加了__weak 和__unsafe_unretained的变量引入后,TestCycleRetain 方法可以正常执行 dealloc 方法,而不转换和使用)__block 转换的变量都会引起循环引用.
因此防止循环引用的方法就是
__unsafe_unretainedTestCuycleRetain *weakSelf=self;
@end