block
概念
- block 是 C 语言的
- 是一种数据类型,可以当作参数传递
- 是一组预先准备好的代码,在需要的时候执行
block 的注意事项
(1)block 在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,然后在 block 块内使用该只读拷贝。
(2)非内联(inline) block 不能直接访问 self,只能通过将 self 当作参数传递到 block 中才能使用,并且此时的 self 只能通过 setter 或 getter 方法访问其属性,不能使用句点式方法。但内联 block 不受此限制。
(3)使用 weak–strong dance 技术来避免循环引用
(4)block 内存管理分析
block 其实也是一个 NSObject 对象,并且在大多数情况下,block 是分配在栈上面的,只有当 block 被定义为全局变量或 block 块中没有引用任何 automatic 变量时,block 才分配在全局数据段上。 __block 变量也是分配在栈上面的。在 ARC 下,编译器会自动检测为我们处理了 block 的大部分内存管理,但当将 block 当作方法参数时候,编译器不会自动检测,需要我们手动拷贝该 block 对象。
在 ARC 下,对 block 变量进行 copy 始终是安全的,无论它是在栈上,还是全局数据段,还是已经拷贝到堆上。对栈上的 block 进行 copy 是将它拷贝到堆上;对全局数据段中的 block 进行 copy 不会有任何作用;对堆上的 block 进行 copy 只是增加它的引用记数。
如果栈上的 block 中引用了__block 类型的变量,在将该 block 拷贝到堆上时也会将 __block 变量拷贝到堆上如果该 __block 变量在堆上还没有对应的拷贝的话,否则就增加堆上对应的拷贝的引用记数。
block 基本演练
1 最简单的 block
- (void)blockDemo1 {
// 定义block
// 类型 变量名 = 值
void (^block)() = ^ {
NSLog(@"Hello block");
};
// 执行
block();
}
tip :使用 inlineBlock 可以快速定义 block.
- 当作参数传递
- (void)blockDemo2 {
void (^block)() = ^ {
NSLog(@"Hello block");
};
[self demoBlock:block];
}
/// 演示 block 当作参数传递
- (void)demoBlock:(void (^)())completion {
NSLog(@"干点什么");
completion();
}
- 使用局部变量
- (void)blockDemo3 {
// 栈区变量
int i = 10;
NSLog(@"%p", &i);
void (^block)() = ^ {
// 定义 block 的时候会对栈区变量进行一次 copy
NSLog(@"Hello block %d %p", i, &i);
};
[self demoBlock:block];
}
如果 block 中使用了外部变量,会对外部变量做一次 copy
- 在 block 中修改外部变量
- (void)blockDemo4 {
// 栈区变量
__block int i = 10;
NSLog(@"%p", &i);
void (^block)() = ^ {
// 定义 block 的时候会对栈区变量进行一次 copy
NSLog(@"Hello block %d %p", i, &i);
i = 20;
};
NSLog(@"block 定义完成 %p %d", &i, i);
[self demoBlock:block];
NSLog(@"===>%d", i);
}
如果要在 block 内部修改栈区变量,需要使用 __block 修饰符,并且定义 block 之后,栈区变量的地址会变化为堆区地址
block 的内存位置
- 全局区:如果block中没有使用任何全局变量
- 栈区:如果 block 中使用了外部变量
MRC 模式可以看到
ARC 模式,系统会自动将 Block 复制到堆中
- 堆区:将 block 设置给 copy 属性
@property (nonatomic, copy) void (^myBlock)();
- (void)blockDemo5 {
int i = 10;
void (^block)() = ^ {
NSLog(@"i --- %d", i);
};
NSLog(@"%@", block);
self.myBlock = block;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"%@", self.myBlock);
}
注意:虽然目前 ARC 编译器在设置属性时,已经替程序员复制了 block,但是定义 block时,仍然建议使用 copy 属性