iOS block探究(一): 基础详解
2017-03-21 18:24
281 查看
你要知道的block都在这里
转载请注明出处 http://blog.csdn.net/u014205968/article/details/64463116本文大纲
block基础语法
block基础使用
block常见问题
block进阶: 深入代码理解
block基础语法
block作为C语言的扩展,正在OC中发挥着举足轻重的作用,我们经常使用
block块作为回调函数,这样做可以大大简化编程方式,多线程的核心也是
block,因此,学会使用
block并深入理解
block有助于我们写出更好的代码。
block基础知识
先来看一下定义block的基础语法
^ returnType (parameter1, parameter2, parameter3, ...) { //block code }
block的标志就是
^,所有的
block必须以
^开头
returnType表示返回值类型,如果没有返回值一般使用
void表示
再来看一下定义
block变量的基础语法
returnType (^blockName) (parameter1, parameter2, ...);
必须包含blockName并且以
^开头,是
block的标志
参数列表可以和声明函数一样,只写出形参类型不需写出形参名称
接下来看一个具体的栗子:
int main(int argc, const char * argv[]) { @autoreleasepool { void (^printBlock)(void) = ^ void(void) { NSLog(@"Hello World"); }; //调用block,与C语言调用函数一致 printBlock(); } return 0; }
上述代码展示了一个无参数、无返回值的
block,定义
block变量的时候不能省略返回值类型、
block名称以及形参列表,如果没有参数则用
void占位或者不写,这样就能够定义一个
block变量。
定义
block的时候如果返回值为
void可以省略,如果没有形参可以使用
void占位或者整个形参列表都省略不写,因此上述代码可以简化为如下:
int main(int argc, const char * argv[]) { @autoreleasepool { void (^printBlock)() = ^ { NSLog(@"Hello World"); }; //调用block,与C语言调用函数一致 printBlock(); } return 0; }
再来看看有参数列表有返回值的情况
int main(int argc, const char * argv[]) { @autoreleasepool { //定义一个有参数无返回值的block void (^printBlock)(NSString *) = ^(NSString *content) { NSLog(@"Block Say: %@", content); }; printBlock(@"Jiaming Chen is a good guy."); //定义一个有多个参数且有返回值的block NSInteger (^addBlock)(NSInteger, NSInteger) = ^ NSInteger (NSInteger a1, NSInteger a2) { return a1 + a2; }; NSInteger a = addBlock(12, 13); NSLog(@"%ld", a); } return 0; }
上述代码定义了两个
block,一个
block有一个参数但是无返回值,因此可以省略返回值类型不写,一个
block有多个参数和返回值,不能省略。
block捕获变量
直接看代码int main(int argc, const char * argv[]) { @autoreleasepool { NSUInteger age = 22; void (^printBlock)() = ^ { NSLog(@"My Age is %ld", age); //age = 33; }; //输出 Age is 22 NSLog(@"Age is %ld", age); age = 100; //输出 Age is 100 NSLog(@"Age is %ld", age); //输出 My Age is 22 printBlock(); } return 0; }
从输出结果可以看出,执行
block之前进行的变量值修改并没有影响到
block块内的代码,这是由于在定义
block块的时候编译器已经在编译期将外部变量值赋给了
block内部变量(称为“值捕获”),在这时候进行了一次值拷贝,而不是在运行时赋值,因此外部变量的修改不会影响到内部
block的输出。
如果捕获是一个指针类型的变量则外部的修改会影响到内部,就和函数传递形参是一样的道理,这个时候
block内部或持有这个对象,并增加引用计数,在
block结束释放后,也会释放持有的对象,减少引用计数,这里需要注意循环引用的问题,在后文中会讲解。
上述
block块内注释了一段给
age重新赋值的代码,因为在
block内部不允许修改捕获的变量。
int main(int argc, const char * argv[]) { @autoreleasepool { //定义一个NSMutableString类型的变量 NSMutableString *content = [[NSMutableString alloc] initWithString:@"Jiaming Chen"]; void (^printBlock)() = ^ { NSLog(@"%@", content); }; //输出 Jiaming Chen NSLog(@"%@", content); [content appendString:@" is a good guy"]; //输出 Jiaming Chen is a good guy printBlock(); } return 0; }
__block的使用
如果希望block捕获的变量在外部修改后也可以影响
block内部,或是想在
block内部修改捕获的变量,可以使用
__block关键字定义变量。
int main(int argc, const char * argv[]) { @autoreleasepool { //使用__block关键字定义age __block NSUInteger age = 22; void (^printBlock)() = ^ { NSLog(@"My Age is %ld", age); age = 200; }; //输出 Age is 22 NSLog(@"Age is %ld", age); age = 100; //输出 Age is 100 NSLog(@"Age is %ld", age); //输出 My Age is 100 printBlock(); //输出 Age is 200 NSLog(@"Age is %ld", age); } return 0; }
上述代码使用
__block定义变量
age,这样定义以后编译器会在
block定义的时候捕获变量的引用而不是拷贝一个值(具体实现细节在iOS block探究(二): 深入理解中有详细介绍)这样,外部变量的修改就会影响到
block内部。
block作为参数传递
在实际应用中很少有上述那样的用法,更多的是定义一个block块然后作为参数传递。
//使用typedef定义一个无返回值、有一个NSInteger类型的形参的block类型,该block名字为 CJMNumberOperationBlock typedef void (^CJMNumberOperationBlock)(NSInteger); //numberOperator函数,参数为一个numberArray数组和一个CJMNumberOperationBlock块类型 void numberOperator(NSArray *numberArray, CJMNumberOperationBlock operationBlock) { NSUInteger count = [numberArray count]; for (NSUInteger i = 0; i < count; i++) { operationBlock([numberArray[i] integerValue]); } } int main(int argc, const char * argv[]) { @autoreleasepool { //定义一个数组 NSArray *numberArray = @[@1, @2, @3]; //直接调用numberOperator函数,实现一个匿名的block传入成为“内联块”(inline block),在swift中成为“尾随闭包”(Trailing closure) //输出 pow(number, 2) = 1 // pow(number, 2) = 4 // pow(number, 2) = 9 numberOperator(numberArray, ^(NSInteger number) { NSLog(@"pow(number, 2) = %ld", number * number); }); //定义一个CJMNumberOperationBlock CJMNumberOperationBlock operationBlock = ^(NSInteger number) { NSLog(@"add(number, number) = %ld", number + number); }; //将上述定义的CJMNumberOperationBlock参数传入 //输出 add(number, number) = 2 // add(number, number) = 3 // add(number, number) = 6 numberOperator(numberArray, operationBlock); } return 0; }
使用
typedef定义块变量类型的语法为
typdef returnType (^blockTypeName)(parameter1, parameter2,..);
定义了块变量类型就能够重复定义块变量,上述代码也是一种常见的使用
block的方式。也可以作为回调函数,在代码逻辑处理完成之后执行
block块,这也是常用做法。
在实际代码中,经常将一些处理封装在
block中,或使用
delegate方式进行处理,这样有利于代码解耦,逻辑更清晰,具体的栗子本文不再赘述,可以多看开源代码来学习。
block常见问题
使用block最常见的问题就是循环引用问题,循环引用也可能发生在delegate或
NSTimer中,具体可以自行查阅。
前文讲过如果
block内部访问了外部变量会进行值捕获,
block同样是一个对象,也有引用计数,如过一个对象持有了一个
block而
block内部也捕获了这个对象,那么就会产生循环引用。
举个栗子:
typedef void (^CJMInfoOperationBlock)(); @interface Person : NSObject @property (nonatomic, copy) NSString* cjmName; @property (nonatomic, assign) NSUInteger cjmAge; @property (nonatomic, copy) CJMInfoOperationBlock infoOperationBlock; - (void)setFixBlock; @end @implementation Person @synthesize cjmName = _cjmName; @synthesize cjmAge = _cjmAge; @synthesize infoOperationBlock = _infoOperationBlock; - (void)setFixBlock { self.infoOperationBlock = ^ { NSLog(@"My name is %@ and my age is %ld", self.cjmName, self.cjmAge); }; } @end
首先声明,上述代码应该不会在实际中应用,太懒了不想用Xcode写代码,就没有用
UIKit,只用了
Foundation框架,
setFixBlock函数就可以想象成在一个
UIViewController类中手动传递
block变量。
上述代码首先自定义了一个
block变量类型,然后
Person类持有了一个
block变量,在函数
setFixBlock中使用了
self变量,这样就会捕获
self,
self也是一个对象,因此
block也会持有一个
self如果调用了
setFixBlock函数就会造成循环引用。编译器也会检查出来并给出警告。
上述代码的修改方法如下:
- (void)setFixBlock { __weak typeof(self) weakSelf = self; self.infoOperationBlock = ^ { __strong typeof(self) strongSelf = weakSelf; NSLog(@"My name is %@ and my age is %ld", strongSelf.cjmName, strongSelf.cjmAge); }; }
首先定义一个
__weak修饰的
weakSelf变量(在iOS @property探究(一): 基础详解一文中有详细介绍
weak修饰符与此处含义一致),使用
__weak修饰表示弱引用, 定义
weakSelf不会增加
self的引用计数,因此在
block内部使用这个
weakSelf就不会产生循环引用,因此在所有可能产生循环引用的地方都这么做,就能有效避免循环引用的产生。
那么为什么在
block内部又使用
__strong修饰符去定义了一个
strongSelf变量呢?其实这个
strongSelf定义或者不定义都无所谓,不会产生问题,因为在ARC环境下,
block会被自动拷贝到堆,不存在栈堆了,这样
block被传递到其他地方的时候也不会释放
self对象。
block进阶: 深入代码理解
三种block类型深入代码理解block
由于篇幅有限,如果对进阶内容有兴趣可以查看另一篇文章iOS block探究(二): 深入代码
备注
由于作者水平有限,难免出现纰漏,如有问题还请不吝赐教。相关文章推荐
- iOS @property探究(一): 基础详解
- iOS开发-block详解与使用
- iOS开发基础:NSLog输出格式详解
- iOS Block 写法详解
- iOS--block底层代码探究
- IOS之Foundation之探究学习Swift实用基础整理<一>
- iOS NSOperation之详解1(NSInvocationOperation,NSBlockOperation,设置依赖)
- iOS中block的探究
- iOS开发详解之几种基础动画篇
- iOS中block实现的底层探究
- iOS中block实现的探究
- iOS中block实现的探究
- iOS中block实现的探究
- ios block探究及block野指针异常报错分析解决
- iOS 多线程详解 NSThread, NSOperationQueue(NSInvocationOperation, NSBlockOperation), GCD
- iOS基础篇——详解三字符组(三字符序列)
- iOS中Block介绍(一)基础
- iOS中block实现的底层探究
- iOS中Block介绍(一)基础
- iOS中block实现的探究