block介绍
- 基本概念
- 作用:用来保存一段代码块
- 标志符:
^
- 可以有返回值
- 可以有形参
- 类似函数的调用方式
- block的定义
- 无返回值、无形参的block的定义
// 定义block变量 void (^myBlock) () = ^()// 无形参这里的括号可以省略 { // 代码逻辑 }; // 简写形式 void (^myBlock) () = ^ { // 代码逻辑 }; // 调用block myBlock();
- 有返回值、有形参的block定义
// 定义block变量 int (^sumBlock) (int,int) = ^int (int a,int b)// 这里的返回值类型可以省略 { return a+b; }; // 简写形式 int (^sumBlock) (int,int) = ^(int a,int b) { return a+b; }; // 调用block int num = sumBlock(1,3);
- 无返回值、无形参的block的定义
- block可以
访问外部
变量int a = 2; // 定义block变量 void (^myBlock); // 给block变量赋值 myBlock = ^ { NSLog(@"a = %d",a); }; // 调用block myBlock();
block可以
修改外部
变量- block代码块内可以
直接修改全局
变量 - block直接修改不是代码块内部的
局部
变量(直接给局部变量赋值
),必须
在变量前加上__block
关键字__block int a = 2; // 定义block变量 void (^myBlock); // 给block变量赋值 myBlock = ^ { a = 3; }; // 调用block myBlock();
进一步分析
void testObject1() { NSMutableString *string = [NSMutableString string]; // 遍历所有的键值对 void (^myblock)() = ^{ [string appendFormat:@"\t%@", @"key"]; [string appendString:@" : "]; [string appendFormat:@"%@,\n", @"obj"]; NSLog(@"%@",string); // 在block中不能直接操作局部变量对应的那块内存空间 // 上面string是追加内存空间,并没有修改 // string = [NSMutableString string]; }; myblock(); } void testObject2() { Person *p = [[Person alloc] init]; // 遍历所有的键值对 void (^myblock)() = ^{ // 这样是可以的 p.name = @"bruce"; p.age = 18; NSLog(@"name---%@,age---%zd",p.name,p.age); // 在block中不能直接操作局部变量对应的那块内存空间 // 如果要想直接修改必须显示添加__block关键字 // p = [[Person alloc] init]; }; myblock(); }
- block代码块内可以
typedef定义block类型
typedef int (^MyBlock)(int,int); // 备注:有时将block作为方法的参数,那么在定义block时加上参数名这样在使用方法时会自动生成对应参数名, // 如果不写就得让用户自己添加参数名,体验不是很好 typedef int (^MyBlock)(int a,int b); // 以后就可以利用MyBlock变量类型来定义block变量了 MyBlock sumBlock = ^int (int a,int b)// 返回值类型可以省略 { return a+b; }; // 简写如下 MyBlock sumBlock = ^(int a,int b) { return a+b; }; // 调用block int num = sumBlock(1,2);
block类型参数示例
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations // 无形参 completion:(void (^)(BOOL finished))completion;// 有形参 // 有返回值有形参 - (NSUInteger)indexOfObjectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate; NSArray *array = @[@11,@23,@55]; // 有形参有返回值的block的调用方式演示 [array indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { // 代码逻辑 return YES; }];
- 在说清楚block在内存中的存储,首先的先介绍下
栈内存
和堆内存
的概念栈内存
- 栈内存空间是由
系统
来管理的 - 对象的地址是存储在栈中
- 程序的执行都是在栈内存中执行的,执行完毕后就会被系统释放
- 在OC中,栈内存中不存在什么强引用和弱引用的概念
- 栈内存空间是由
堆内存
- 由
程序员
来管理的 - 对象本身是在
堆
中存储的
- 由
- 为什么block属性要用
copy策略
(这个只是MRC
遗留的问题,在ARC
中使用copy或者strong都行
,但是为了让调用者知道内存策略,建议
还是写copy
)?- block
默认
创建出来就存储在栈内存
中 - 如果不采取copy策略那么程序执行完毕后block那块代码就会被销毁,那么下次要是再想用就没有了
- 采取copy策略将block的代码块拷贝一份放到
堆内存
中,这样只要引用block的对象不死,那么这个block代码块就不会死
- block
随之而来会产生一个问题就是block的
循环引用
,这里先图形说明一下:循环引用
解决
循环引用
- ARC环境
- 使用
__weak
关键字,iOS5.0
以后才有 - 使用
__unsafe_unretained
关键字,兼容4.x之前的版本
- 使用
- MRC环境
- 使用
__block
关键字
- 使用
- ARC环境
在block中修改外部变量的本质
- 全局变量在block代码块中是可以被直接修改,因为全局变量的地址在程序中是固定的,如下图的验证过程
- 首先定义了一个Son类(随便取的名字)
- 在Car中定义一个Son类型的全局变量,在类方法test2中初始化该全局变量
- 在Person中通过extern关键字引用该全局变量,然后在类方法test2中修改这个全局变量
- 方法执行过程,可以发现全局变量的地址一直没发生过改变
- 局部变量在block中是不能被直接修改的,要在block内部修改局部变量必须使用关键字
__block
或者static
修饰局部变量,才能被修改 - 使用
__block
关键字修饰的局部变量,会在block的代码块中将变量的引用计数器+1,相当于添加了一个强指针指向这个局部变量,这样即使外部的局部变量被销毁了,也不会影响block代码块内部修改这个局部变量,这就是为什么加上__block
后就可以修改局部变量了 总结
:全局变量、用static修饰的局部变量,还有使用__block
修饰的局部变量可以在block被修改,最根本的原因就是在block代码块执行的时候可以访问到这些变量
- 全局变量在block代码块中是可以被直接修改,因为全局变量的地址在程序中是固定的,如下图的验证过程
block用法总结
作为局部变量
// 格式 returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...}; // 示例 int (^sumBlock) (int,int) = ^int (int a,int b)// 这里的返回值类型可以省略 { return a+b; }; // 简写,要能看得懂,建议写上返回类型 int (^sumBlock) (int,int) = ^(int a,int b) { return a+b; };
作为属性
// 格式 @property (nonatomic, copy) returnType (^blockName)(parameterTypes); // 示例 @property (nonatomic, copy) int (^sumBlock)(int,int);
作为方法参数
// 格式,建议写上参数名,这样在自动生成时也会生成参数名,方便使用者 - (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName; // 示例,建议带参数名 - (void)someMethodThatTakesABlock:(int (^)(int a,int b))sumBlock; // As an argument to a method call [someObject someMethodThatTakesABlock:^int (int a,int b) {...}];
使用
typedef
定义block// 定义blokc类型时,建议写上参数名,这样在自动生成时也会生成参数名,方便使用者 typedef returnType (^BlockTypeName)(parameterTypes); BlockTypeName blockName = ^returnType(parameters) {...}; // 示例 typedef int (^OperationBlock)(int a,int b); OperationBlock sumBlock = ^int(int a,int b) {...};
使用block的
注意点
- 调用
空
block是会报错
的,所以调用block方法必须
先判断block是否为空
- 调用