该文章参考多篇文章,已记不清,如有问题请联系我。
参考:http://blog.csdn.net/zm_yh/article/details/51469275
Block理解
1. Block执行的代码,是在编译的时候已经生成好的;
2. Block是一个包含执行时需要的所有外部变量值的数据结构。 Block将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上。
Block的修饰
ARC情况下
1.如果用copy修饰Block,该Block就会存储在堆空间。则会对Block的内部对象进行强引用,导致循环引用。内存无法释放。
解决方法:
新建一个指针(__weak typeof(Target) weakTarget = Target )指向Block代码块里的对象,然后用weakTarget进行操作。就可以解决循环引用问题。 ARC中 GCD 中并不需要对self 弱引用,因为self 并不持有 GCD 的block 直接self即可。
MRC情况下
用copy修饰后,如果要在Block内部使用对象,则需要进行(__block typeof(Target) blockTarget = Target )处理。在Block里面用blockTarget进行操作。
Block的定义格式
返回值类型 Block变量名 形参列表 传递 形参列表
NSString* (^ TestBlock)(id parameA,id parameB…) = ^(id parame1,id parame2…) { };
调用Block的代码:block变量名(实参);
安全调用block
一般调用block调用时都要对其是否为nil进行判断,是处于安全的考虑,如下
if(block) {
block();
}
Block的模式
1.无参数无返回值的Block
/**
* 无参数无返回值的Block
*/
- (void)func1{
/**
* void :就是无返回值
* emptyBlock:就是该block的名字
* ():这里相当于放参数。由于这里是无参数,所以就什么都不写
*/
void (^emptyBlock)() = ^(){
NSLog(@"无参数,无返回值的Block");
};
emptyBlock();
}
2.有参数无返回值的Block
/**
* 调用这个block进行两个参数相加
* @param int 参数A
* @param int 参数B
*
* @return 无返回值
*/
void (^sumBlock)(int ,int ) = ^(int a,int b){
NSLog(@"%d + %d = %d",a,b,a+b);
};
/**
* 调用这个sumBlock的Block,得到的结果是20
*/
sumBlock(10,10);
3.有参数有返回值的Block
/**
* 有参数有返回值
* @param NSString 字符串1
* @param NSString 字符串2
*
* @return 返回拼接好的字符串3
*/
NSString *(^logBlock)(NSString *,NSString *) = ^(NSString * str1,NSString *str2){
return [NSString stringWithFormat:@"%@%@",str1,str2];
};
//调用logBlock,输出的是 我是Block
NSLog(@"%@", logBlock(@"我是",@"Block"));
4. Block作为参数使用
typedef long (^BlkSum)(int, int);
- (BlkSum) sumBlock {
int base = 100;
BlkSum blk = ^ long (int a, int b) {
return base + a + b;
};
return [blk copy];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
BlkSum tttt = [self sumBlock];
tttt(10,20);
NSLog(@"tttt----%ld",tttt(10,20));
}
Block结合typedef使用,作为属性
.h
/**
* 定义了一个logBlock的Block。这个logBlock必须带2参数,这个参数的类型必须为NSString类型的
* 返回值类型 NSString
* @param NSString
*/
#import <UIKit/UIKit.h>
typedef NSString* (^logBlock)(NSString *,NSString *);
@interface ViewController : UIViewController
@property (nonatomic,copy)logBlock testBlock;
@end
.m
- (void)viewDidLoad {
[super viewDidLoad];
self.testBlock = ^(NSString * str1,NSString *str2){
return [NSString stringWithFormat:@"%@%@",str1,str2];
};
}
// 调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"gggggg----%@",self.testBlock(@"gggg",@"tttt"));
}
block变量定义时为什么用copy?block是放在哪里的?
- block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈(stack)上,可能被随时回收,而不是在堆(heap)上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。通过copy可以把block拷贝(copy)到堆,保证block的声明域外使用。
特别注意:在把block放到集合类当中去的时候,如果直接把生成的block放入到集合类中,是无法在其他地方使用block,必须要对block进行copy。
[array addObject:[[^{
NSLog(@"hello!");
} copy] autorelease]];
Block与变量
1. Block与局部变量
- 官方文档:写到
- Stack (non-static) variables local to the enclosing lexical scope are captured as const variables.
- Their values are taken at the point of the block expression within the program. In nested blocks, the value is captured from the nearest enclosing scope.
- 意思是:在block 使用中,位于一块封闭函数内存(非静态的)栈变量 被捕捉为const变量。
- 它们的值是在程序中的块表达式的点处取的。在嵌套块中,该值从最近的封闭范围捕获。(也就是block代码块上面,最近的值。)
-
// 局部变量,在Block中是只读的。Block定义时会copy变量的值到栈上,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。
- (void)testParameBlock222
{
int number = 100;
void(^TestBlock)(int) = ^(int x){
// number = number+x;
// 这句报错,局部变量没用__block 修饰时,在block中只能使用,不能修改它的值。
NSLog(@"number:%d",number+100);
};
number = 200; // 这里虽然修改的number的值为200。但是不能影响block 中的值。因为在编译block时已经将number 的值只读copy了一份。
TestBlock(100);
}
参考:http://www.jianshu.com/p/a0adbaca7f69 这个文章更助于我们理解。
我们可以反编译看一下block里面匿名函数的实现:
- static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
- int a = __cself->a; // bound by copy
- printf("%d\n",a);
- }
- bound by copy这里的注释表示,block对它引用的局部变量做了只读拷贝,也就是说block引用的是局部变量的副本。所以在执行block语法后,即使改写block中使用的局部变量的值也不会影响block执行时局部变量的值。
Block与局部变量__block
// 在Block中修改局部变量的值,使用__block。
// MARK: __block修饰 修改局部变量的值
- (void)testParameBlock333
{
__block int a = 10;
void (^helloBlock)(void) = ^(){
NSLog(@"%d\n",a);
};
a = 11;
helloBlock();
}
引入__block关键字后,运行结果是11,我们再编译看看block里面匿名函数的实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
printf("%d\n",(a->__forwarding->a));
}
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
我们看到局部变量a变成一个构造体对象,而不再是一个整形变量,结构体里包含它原来的整形变量。bound by ref这个注释也表明此时已变成引用传递。
深入研究Block捕获外部变量和__block实现原理(http://www.halfrost.com/ios_block/)写的有点深,没看完。
2. Block与全局变量
// MARK: Block与全局变量
// 全局变量存储在,因为全局变量或静态变量在内存中的地址是固定的,Block在读取改变量值的时候是直接从其所在的内存读出的,获取得道是最新值,而不是在定义时copy的常量。
int value = 100;
- (void)testParameBlock444
{
void (^TestBlock)(int)=^(int x){
value = value+100;
NSLog(@"看看是不是喽%d",value);
};
TestBlock(100);
NSLog(@"value----%d",value);
}
3. Block与静态变量
// MARK: Block与静态变量
static int number = 100;
- (void)testParameBlock555
{
int (^TestBlock)(int) = ^(int x){
number = number+x;
NSLog(@"静态变量----%d",number);
return number;
};
NSLog(@"用static修饰 使用局部变量的结果:%d",TestBlock(100));
number = 50;
NSLog(@"在外面改变number的值,再次调用block的结果:%d",TestBlock(100));
}
Block变量,被__block修饰的变量称作Block变量。基本类型的Block变量等效于全局变量、或静态变量。
1.__block对象在block中是可以被修改、重新赋值的。
2.__block对象在block中不会被block强引用一次,从而不会出现循环引用问题。
使用了__weak修饰符的对象,作用等同于定义为weak的property。自然不会导致循环引用问题,因为苹果文档已经说的很清楚,当原对象没有任何强引用的时候,弱引用指针也会被设置为nil。
因此,__block和__weak修饰符的区别其实是挺明显的:
1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
3.__block对象可以在block中被重新赋值,__weak不可以。
在block中能否定义新的变量 可以,而且在block内部定义的变量是在【栈区】分配空间的 |
//定义一个外部变量
int sum = 2;
//定义一个有参有返回值的block的别名
typedef int (^myBlock)(int, int);
NSLog(@"sum = %p",&sum);//此时sum在栈区
myBlock b1 = ^(int a, int b){
int sum = 100;
NSLog(@"内部sum = %p",&sum);//此时的地址在栈区
return sum + a + b;
};
NSLog(@"a + b = %d", b1(1,2));
NSLog(@"外部sum = %p",&sum);//此时sum在栈区
为什么系统的block,AFN网络请求的block内使用self不会造成循环引用? 而自定义的block如果直接使用了self,或者成员变量的话,都会形成强引用,造成内存泄漏?
关于这个问题,UIView和AFN还是不一样的。
首先循环引用发生的条件就是持有这个block的对象,被block里边加入的对象持有。当然是强引用。所以UIView的动画block不会造成循环引用的原因就是,这是个类方法,当前控制器不可能强引用一个类,所以循环无法形成。
而AFN无循环是因为绝大部分情况下,你的网络类对象是不会被当前控制器引用的,这时就不会形成引用环。当然我不知道AFN是否做了别的处理,按照这样来说的话,如果你的控制器强引用了这个网络类的对象,而且在block里面引用了当前控制器,也是会发生循环引用的。
其实只要抓住循环引用的本质,就不难理解。所谓循环引用,是因为当前控制器在引用着block,而block又引用着self即当前控制器,这样就造成了循环引用。系统的block或者AFN等block的调用并不在当前控制器中调用,那么这个self就不代表当前控制器,那自然也就没有循环引用的问题。以上引用均指强引用
用UIView啥的因为是类方法,AFNetworking是因为人家大神自己封装了一个completionBlock,不管你传进来是啥,都给你把循环引用打破。看图
如果块是一个单例持有的,块内又使用了ViewController这个类,会引起循环引用。例子:
[[OutsidePacketsSchedule shareInstance] sendParameters:dict requestCmd:@"addCustomEmoReq"responseCmd:@"addCustomEmoRsp"complete:^(idresponse,NSError*error) {
if(!error) {
[weakSelf.viewsetToast:@"添加自定义表情成功"];
}
}];
上例中的单例持有的代码块中要用弱引用,原因是:单例不会被释放掉,它会一直持有block,导致该block所在的ViewController释放不掉。
(3) 如果是方法中的参数是block,不会造成循环引用,因为方法中的block是位于栈内存的,方法返回后,block将会无效。
Cell使用block潜在的循环引用
回到标题说的情况,使用UITableViewCell或者UICollectionViewCell的子类定制cell时,会遇到cell上有个独立的按钮事件需要回调,当使用block来实现这个回调的设计时就会发生一个容易忽略的循环引用,Xcode编译器无法发现这类隐形循环引用,没有警告提醒,如下图没有警告,但循环引用已经产生,当该控制器被pop出去时不会销毁dealloc。
分析其原因在于cell实际是tableView的子视图,每个子视图都是会被其父视图的subviews(NSArray *)属性所强引用,即tableView ~> subviews ~> cell,而cell因为使用block作为回调强引用了block内部的对象,形成了这样的循环引用链条,即 controller ~> tableView ~> cell ~> controller,解决的方法同样是使用弱引用传入block,如下图所示。
__weak typeof(self) weakSelf = self;
cell.cellBlock = ^(id cell) {
[weakSelf iLog];
};
return cell;
值得注意的是,在这个例子中,即使tableView属性的声明为weak,循环引用仍然会产生,原因在于tableView还是controller的view属性的子视图,强引用链接同样存在,因此最好是在block内部切开强引用链条。