版权声明:原创做品,谢绝转载!不然将追究法律责任。html
一个Objective-c类定义了一个对象结合数据相关的行为。有时候,这使得他有意义的表达单个任务或者单元的行为。而不是集合的方法。ios
blocks是语言的特性,咱们能够在C C++ 和Objective-c看到,这容许你建立不一样的代码片断,这代码片断能够经过在方法或者函数里调用若是他们有值。blocks是Objective-c的对象,意味着他们能够被添加到集合像数组和字典里。他们也有能力扑捉封闭范围值。web
这章咱们阐述怎么声明和引用blocks的语法,和展现怎么用块语法来简化咱们一般的任务例如集合的枚举。更多信息参考: Blocks Programming Topics.数组
blocks的语法:安全
用(^)定义blocks字面语法像这样:服务器
^{并发
NSLog(@"This is a block");app
}异步
就像函数和方法定义那样,{}表示函数的开头和结尾。这个例子中block没有返回值也没有参数。async
一样的你也能够用函数指针来引用一个C函数,你能够声明一个变量来跟踪block像这样:
void (^simpleBlock)(void);
若是你不习惯处理C函数指针,语法可能彷佛有点不寻常,这个例子叫作simpleBlock声明了一个变量来引用一个块不带任何参数不返回值。这意味着块能够指定给变量像这样:
simpleBlock = ^{
NSLog(@"This is a block");
};
这就像其余任何变量的赋值,所以语句必须以分号结束。你能够把变量声明和赋值放在一块儿:
void (^simpleBlock)(void) = ^{
NSLog(@"This is a block");
};
一旦你已经声明和赋值一个block变量,你能够调用block:
simpleBlock();
注意:注意你声明的块变量没有赋值为nil你的应用会崩溃。
块带着参数和返回值:
块也能够带参数和返回值就像方法和函数同样。
举个例子把两个值相乘的结果返回给一个块变量:
double (^multiplyTwoValues)(double, double);
相应的字面块语法像这样:
^ (double firstValue, double secondValue) {
return firstValue * secondValue;
}
当块调用的时候这个firstValue 和secondValue引用的值。就像任何函数定义同样。这个例子中这个返回值类型是块语句返回值类型推断的。
你也能够显示的声明返回的类型在^和()以前像这样:
^ double (double firstValue, double secondValue) {
return firstValue * secondValue;
}
一旦你声明和定义了block,你就能够调用他就像调用函数同样:
double (^multiplyTwoValues)(double, double) =
^(double firstValue, double secondValue) {
return firstValue * secondValue;
};
double result = multiplyTwoValues(2,4);
NSLog(@"The result is %f", result);
块能够从封闭范围内扑捉值:
一个block也能够有能力扑捉值从封闭的范围内,就像包含执行代码同样。若是你定义了一个字面block在方法里面例如,在方法范围内他能够扑捉任何值。就像这样:
- (void)testMethod {
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
};
testBlock();
}
在这个例子中,anInteger是定义在block的外面,可是当block定义的时候值被扑捉。
一旦值被扑捉,除非你特别说明。这意味着在你定义block和调用他之间若是你改变外部变量值。就像这样:
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
};
anInteger = 84;
testBlock();
这个值被block扑捉没有收到影响。这意味着打印的值是
Integer is: 42
这意味着block不能改变原始值即便被扑捉的(他被扑捉的是不可修改的变量)
用_block共享存储
若是你须要改变block扑捉的值,你能够用_block存储类型的修饰符在变量声明以前。这意味着变量存活在存储共享的词法做用域之间的原始变量和任何块中声明的范围。你能够重写以前的代码像这样:
__block int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
};
anInteger = 84;
testBlock();
由于anInteger被定义为一个__block变量,他存储被共享在block声明。这意味着输出值会这样:
Integer is: 84
这也意味着block能够修改原始值像这样:
__block int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
anInteger = 100;
};
testBlock();
NSLog(@"Value of original variable is now: %i", anInteger);
这时输出窗口会这样:
Integer is: 42
Value of original variable is now: 100
你能够把blocks做为方法或者函数的参数传递:
以前在这章的每一个例子定义过block后立刻被调用。事实上,这是经常使用的在其余地方调用块函数或者方法。你能够在后台用GCD调用block,例如,或者定义一个block表明一个任务被调用屡次,例如当枚举集合。并发性或者枚举性在后面章节介绍。
blocks能够被用来回调,定义代码被执行在任务完成时候。例如你的应用可能响应用户建立对象的动做来执行一个完成的任务。例如请求信息从服务器。由于这个任务可能用很长时间,当任务发生的时候你可让一个指示器来表示正在请求数据,一旦任务完成的时候你隐藏指示器。
你也可能完成这个任务用代理:你须要建立一个合适的协议,实现必须的方法,设置你的对象做为任务的委托,而后你等着代理方法被调用一旦你的对象完成了方法。
blocks可使这变的更简单,由于你能够定义回调行为当你发起任务时候像这样:
- (IBAction)fetchRemoteInformation:(id)sender {
[self showProgressIndicator];
XYZWebTask *task = ...
[task beginTaskWithCallbackBlock:^{
[self hideProgressIndicator];
}];
}
这个例子调用一个方法来显示指示器,而后建立任务而且告诉他执行。这个回调block指定代码被执行一旦这个任务完成的时候;这个例子,他只调用一个方法来隐藏指示器。注意这个回调block为了可以调用隐藏指示器方法当调用时候给self捕获值。重要的是要照顾好self当他被捕获的时候,由于可能形成强引用就像在以后描述的这样:“Avoid Strong Reference Cycles when Capturing self.”
根据代码的可读性,这个block能够很容易的看到这个任务完成以前和以后将要发生什么,避免须要追踪代理方法来发现将要发生什么、
在这个例子中定义这个beginTaskWithCallbackBlock:方法像这样:
- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock;
这个(void (^)(void))指示这个方法参数block没有返回值和参数。这个方法实现能够调用block像以前那样。
- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock {
...
callbackBlock();
}
有一些方法参数但愿一个block有一个或者多个参数被指定:
- (void)doSomethingWithBlock:(void (^)(double, double))block {
...
block(21.0, 2.0);
}
一个block应该一直做为方法的最后一个参数
最好的练习是用一个block参数在一个方法里面。若是方法也须要不是block的参数,那么这个block参数应该放在方法的最后一个参数:
- (void)beginTaskWithName:(NSString *)name completion:(void(^)(void))callback;
当指定块为内联时候更容易被阅读像这样:
[self beginTaskWithName:@"MyTask" completion:^{
NSLog(@"The task is complete");
}];
用typedef指令来简化block语法定义:
若是你须要定义不止一块具备相同签名的block,你可能喜欢定义你本身的类型签名,例如你给一个没有参数和返回值的block定义一个类型像这样:
typedef void (^XYZSimpleBlock)(void);
你能够用你的自定义类型做为方法参数或者建立一个block变量:
XYZSimpleBlock anotherBlock = ^{
...
};
- (void)beginFetchWithCallbackBlock:(XYZSimpleBlock)callbackBlock {
...
callbackBlock();
}
自定义类型很是有用当你处理blocks然而这些blocks有blocks的参数或者返回值。考虑下面例子:
void (^(^complexBlock)(void (^)(void)))(void) = ^ (void (^aBlock)(void)) {
...
return ^{
...
};
};
这个complexBlock 变量引用一个blocks做为参数还返回一个block做为返回值.
用自定义类型来重写代码让你的代码变的可读性强:
XYZSimpleBlock (^betterBlock)(XYZSimpleBlock) = ^ (XYZSimpleBlock aBlock) {
...
return ^{
...
};
};
咱们能够给blocks声明成属性来跟踪他:
定义属性的语法来跟踪block就像定义block变量:
@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end
注意:咱们在定义blocks属性时候他的关键字应该是copy。由于一个block须要被拷贝来跟踪他捕获原始范围之外的状态。当用ARC的时候咱们须要担忧,由于这是自动发生的。可是最好仍是给属性指定关键字来代表他的行为。更多信息参考Blocks Programming Topics.
一个block属性被设置或者引用像其余任何block变量:
self.blockProperty = ^{
...
};
self.blockProperty();
也能够给block属性重定义像这样:
typedef void (^XYZSimpleBlock)(void);
@interface XYZObject : NSObject
@property (copy) XYZSimpleBlock blockProperty;
@end
当捕获self的时候当心强引用循环:
若是你须要捕获self在block中,例如定义一个block回调,你更应该考虑的是他的内存泄漏。
blocks维持着强引用向任何捕获的对象,包括self,这意味着很容易引发强引用循环,例如一个对象维持着一个copy属性给一个捕获self的block
@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
@implementation XYZBlockKeeper
- (void)configureBlock {
self.block = ^{
[self doSomething]; // capturing a strong reference to self
// creates a strong reference cycle
};
}
...
@end
若是你这样定义例子编译器会警告你,可是一个更复杂的例子可能包括多个对象之间的强引用来建立循环,使它更难诊断。
为了不这个问题,咱们声明一个弱引用类型给self,像这样:
- (void)configureBlock {
XYZBlockKeeper * __weak weakSelf = self;
self.block = ^{
[weakSelf doSomething]; // capture the weak reference
// to avoid the reference cycle
}
}
给self声明弱引用类型。这个block没有维持一个强引用给XYZBlockKeeper对象。若是这个对象在这个block以前被销毁,这个weakSelf指针将要被指为nil。
blocks能够简化枚举
除了通常的完成处理程序,许多的cocoa或者cocoaTouchAPI用blocks来简化常见的任务,就像集合枚举。这个NSArray类,例如提供了3个基本的block方法包括这个:
- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
这个方法有一个简单的block参数来遍历数组的每一个元素:
NSArray *array = ...
[array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
NSLog(@"Object at index %lu is %@", idx, obj);
}];
这个block本身有三个参数。前两个是当前对象和他在数组的索引。第三个参数是一个指向布尔类型变量的指针能够中止这个枚举像这样:
[array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
if (...) {
*stop = YES;
}
}];
咱们也能够自定义枚举用enumerateObjectsWithOptions:usingBlock:方法指定这个NSEnumerationReverse参数选项,例如将集合反向遍历。
这个在block里的代码是一个处理器密集的安全并发的,你能够用NSEnumerationConcurrent 选项。
[array enumerateObjectsWithOptions:NSEnumerationConcurrent
usingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
...
}];
这个标识符代表这个枚举块可能在多个线程里面调用,若是这个block代码是特别的密集处理器将提升潜在的性能。注意这个枚举顺序是未定义的当用这个选项的时候:
这个NSDictionay来提供block的基本方法包括:
NSDictionary *dictionary = ...
[dictionary enumerateKeysAndObjectsUsingBlock:^ (id key, id obj, BOOL *stop) {
NSLog(@"key: %@, value: %@", key, obj);
}];
和传统的循环变量相比他能够很方面的变量字典的键值。
块能够简化并发任务:
一个block是一个工做单元。组合可执行代码从周围范围捕获可选状态。这使异步调用更完美在OSX和IOS用一个有效的并发选项。而不是必定指出怎么使用低级的机制像线程。你能够简单的定义你的任务使用块而后让系统执行这些任务做为处理器资源变的可用。
OSX和IOS提供了不少的并发技术包括两个任务调度机制: Operation queues and Grand Central Dispatch。这些机制解决的是一个队列的任务怎么等待被调用。你能够添加blocks到你的队列顺序当你须要他们被调用。而且系统为调用出列当处理器时间和资源可用。
一系列队列只容许一个任务被执行在同一时间下一个在队列里面的任务直到前面一个完成才会调用。当前的队列调用他尽量调用的任务不用等待前面的任务完成。
使用块操做和运行队列:
一个操做队列在cocoa或者cocoaTouch相似与任务调度。你能够建立一个NSOperation实例来封装一个工做单元以及必要的数据,而后放到一个NSOperationQueue 里面来执行。
尽管你能够建立一个自定义的NSOperation子类来实现复杂的任务,你也可能用NSBlockOperation 来建立一个操做用block像这样:
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
...
}];
你可能执行一个手动的线程可是这些线程常常被添加一个已经存在的队列或者你本身建立的队列,等待被执行:线程
// schedule task on main queue:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];
// schedule task on background queue:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
若是你用一个线程队列,你须要配置优先级或者线程之间的依赖,就像指定一个线程不能被执行直到其余的线程完成。你能够用一个观察者来观察线程的状态。他使你很容易来更新一个进度指示器例如当任务完成了。
更多信息关于线程和线程队列的参考“Operation Queues”.
在GCD里面用block来调度队列:
若是你须要安排任意的代码快来执行,你能够直接用GCD控制的dispatch queues。
队列调度使他更容易执行同步或者异步并执行他们的任务在一个先进先出的顺序。
你也能够建立你本身的队列调度或者用GCD提供的队列。你须要安排你的任务来并发执行。例如你能够获得一个已经存在的队列用dispatch_get_global_queue()方法而且指定这个队列的优先级像这样:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
分派block到队列,你能够用dispatch_async() 或者dispatch_sync()方法。这个dispatch_async()方法马上返回,无需等待被调用。
dispatch_async(queue, ^{
NSLog(@"Block for asynchronous execution");
});
这个dispatch_sync()方法直到block完成了才能调用。你可使用他在一种状况下,并发块须要等待另外一个任务在主线程继续以前完成。
更多信息参考队列调度和GCD“Dispatch Queues”.