你要知道的block都在这里
转载请注明出处 http://www.jianshu.com/p/184b04c1f454
本文大纲
- 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探究(二): 深入代码
备注
由于作者水平有限,难免出现纰漏,如有问题还请不吝赐教。