让我们来谈谈blocks吧.
我喜欢blocks,因为blocks使得Objective-C代码有表达性.blocks也会减少代码量,这也意味着减少了调试还有所要维护的代码量.一旦了解了Objective-C中blocks恶心的格式,任何一个编写高层次代码如Ruby,Python,Javascript的开发者使用blocks都不会感觉到陌生.
我个人认为在一些单独分开的系统里面使用blocks来通讯的话,会有比较好的效果.这些单独分开的系统,可以是一个应用与一个网络服务,又或者是在同一个应用里面逻辑分开的部分.使用blocks,有益于高层次上维护封装的代码,同时又可以让代码有更好的可读性,准确性.
通常,我们会使用delegate
来作为两个对象通讯的桥梁.delegate
设计模式贯穿于Cocoa和Cocoa Touch.在iOS里,application delegate
是我们第一个需要与它打交道的对象.两个对象有不断的,比较广的关系的时候,delegate会一个不错的选择,UITableView delegate
就是一个很好的例子.
在另外一些时候,我们可能只想在对象间传递比较小的信息,而它们相互之间不需要太过了解对方.如果使用delegate的话,我们需要定义一个delegate protocol
, 实现delegate方法,还要添加一个delegate属性.但是通过blocks,我们可以实现相同的功能而不需要protocols的,并且可以使得代码更加的紧凑.
假如我们需要开发一个应用,功能是获取还有展示股票报价.因为我们是优秀的程序员,我们决定分离或者封装获取网络服务的接口,不管我们与网络服务有什么逻辑要实现,我们的应用还是使用同一个接口.
如果你是第一次做这些工作(或者你是PHP程序员),有会想为什么需要这样做.好处就是第一,或者以后你需要换一个网络服务来提供股票报价数据.如果我们直接把网络服务的API写到我们的应用,新的网络服务就会与原来那个不兼容,这时我们就需要对应用进行大规模的修改.但是,网络服务的API抽象分离出来的这种设计,我们只需要修改应用的其中一个地方.这种分离成为Separatoin of Concerns,这棒极了.
假设我们已经创建好了一个Xcode项目.或者你可以从Github下载这个项目.你为什么不先定义一个类包含所有需要与网络服务交互的API呢.
Note:在这个例子里面,我使用了Yahoo!stock非正式的API,这个超出了本文的讨论范围,想了解的话可以在网络上找到相关资料.
// SPFPriceFetcher.h
#import <Foundation/Foundation.h>
typedef void (^SPFQuoteRequestCompleteBlock) (BOOL wasSuccessful, NSDecimalNumber *price);
@interface SPFPriceFetcher : NSObject
- (void)requestQuoteForSymbol:(NSString *)symbol
withCallback:(SPFQuoteRequestCompleteBlock)callback;
@end
十分简单,股票代码,回调block作为一个方法的参数.正如你所看到的,我使用了typedef
来定义一个callback.这跟定义一个protocol十分相像.这不是必须的,你也可以在方法里面定义这个callback.但是在这个情况下,我想这样做有益于代码的可读性,并且在接下来我们会看到这样定义的好处.
这是PriceFetcher
的实现类.
// SPFPriceFetcher.m
#import "SPFPriceFetcher.h"
#import "JCDHTTPConnection.h"
// Yahoo stock quote API
// Example: http://download.finance.yahoo.com/d/quotes.csv?s=GOOG&f=l1
#define kYahooStockQuoteAPIURL @"http://download.finance.yahoo.com/d/quotes.csv"
#define kYahooStockQuoteAPIFormatString @"l1"
@implementation SPFPriceFetcher
- (void)requestQuoteForSymbol:(NSString *)symbol withCallback:(SPFQuoteRequestCompleteBlock)callback
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?s=%@&f=%@",
kYahooStockQuoteAPIURL,
symbol,
kYahooStockQuoteAPIFormatString]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
JCDHTTPConnection *connection = [[JCDHTTPConnection alloc] initWithRequest:request];
[connection executeRequestOnSuccess:
^(NSHTTPURLResponse *response, NSString *bodyString) {
if (response.statusCode == 200) {
NSDecimalNumber *price = [NSDecimalNumber decimalNumberWithString:bodyString];
callback(YES, price);
} else {
callback(NO, nil);
}
} failure:^(NSHTTPURLResponse *response, NSString *bodyString, NSError *error) {
callback(NO, nil);
} didSendData:nil];
}
@end
在blocks里面调用blocks是十分强大,这样的嵌套会显得很干净.我不会详细解读上面的代码.这只是简单的HTTP GET请求.当请求完成时,我们确保执行callback block.在这个callback block里面,我们会传递一个BOOL值来表示我们的请求是否成功返回一些数据.
现在来看看我们的应用是如何调用这些API的.我们的应用只有一个视图控制器(SPFviewcontroller
),视图里面有一个按钮用来触发请求.只有当回调成功的时候,我们才会更新我们的UI.
- (IBAction)getPrice:(id)sender {
SPFQuoteRequestCompleteBlock callback = ^(BOOL wasSuccessful, NSDecimalNumber *price) {
if (wasSuccessful) {
self.priceLabel.text = [NSString stringWithFormat:@"Latest price: $%@", [price stringValue]];
} else {
self.priceLabel.text = @"Unable to fetch price. Try again.";
}
};
[self.quoter requestQuoteForSymbol:self.stockSymbolTextField.text
withCallback:callback];
}
因为我们这个应用的架构,我们可以轻易地修改PriceFetcher
的实现,而不用修改其它地方的代码.另外,因为我们是使用blocks来实现我们的API,这样会我们可以更加灵活的定义我们的callbacks,我们还可以把它们传递,作为实例变量来使用.