我刚刚读过有关块的内容,而且我知道它们只是将信息封装为普通方法,但具有自己强大的引用数据。我想知道块的好用是什么?
答案 0 :(得分:11)
这是用于我自己的项目的块的用途;替换代表和协议(在某些情况下)。
问题
假设您需要从服务器异步加载数据。您可能有一个方法需要PUT到一个路径(带有数据),然后最终,当任务完成时,将结果发送给方法调用者。
委托和协议解决方案
以下是我们客户的方法签名,称之为AppClient
:
- (void)putToPath:(NSString *)path withData:(id)data;
我们不能在这个方法的返回中包含数据,因为它是异步的(这意味着它不会等待任务完成其他事情,比如运行下一行代码)。相反,我们构建了一个协议:
@protocol AppClientRequestDelegate
- (void)appClient:(AppClient *)appClient didPutToPath:(NSString *)path withData:(id)sentData andReturnedData:(id)recievedData;
@end
然后你的AppClient
类会创建一个这样的属性:
@property (weak, nonatomic)id<AppClientRequestDelegate> requestDelegate;
putToPath...
方法的调用方将AppClient的requestDelegate
属性实例设置为self
,并实现该方法,然后使用path
验证正确的请求sentData
参数,并使用receivedData
参数执行某些操作。
我们的来电代码如下:
- (void)syncData:(id)data {
[self.appClient putPath:@"/posts/9" withData:data];
}
- (void)appClient:(AppClient *)appClient didPutToPath:(NSString *)path withData:(id)sentData andReturnedData:(id)recievedData {
if (/*path and sentData are right*/) {
// Do something with recievedData
}
}
这一切都很棒,但是当你有一堆PUT请求到同一条路径时,它会很糟糕,并试图区分协议实现中的请求。我猜你可以在委托方法和为每个请求指定id的putToPath...
方法中添加另一个参数,但这会让人感到麻烦和混乱。
另一个潜在的问题是如果你在这个应用程序中广泛使用异步加载;这可能会产生大量的代表和协议。
阻止解决方案
我们将方法签名扩展为包含块:
- (void)putToPath:(NSString *)path withData:(id)data completion:(void (^)(id returnedData))completion;
当然,这种语法非常令人生畏,但它不仅包含协议中的所有信息,而且允许方法的调用者将所有逻辑压缩到一个方法中,从而将该方法中调用的局部变量带入范围块的实现。
我们的来电代码现在看起来像这样:
- (void)syncData:(id)data {
[self.appClient putToPath:@"/posts/9" withData:data completion:^(id returnedData) {
// Do something with returnedData
}];
}
<强>结论强>
你要求使用积木,我相信这是一个非常好的;它可能不适用于您,但您可以看到它不仅减少了代码质量,还使其更具可读性和健壮性。
答案 1 :(得分:1)
Blocks可以帮助您以多种方式编写更好的代码。这是两个。
一个优点是更可靠的代码。这是一个具体的例子。
在iOS 4.0之前,为了制作视图动画,您必须使用beginAnimations:context:
和commitAnimations
消息,like this:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelay:1.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
self.basketTop.frame = basketTopFrame;
self.basketBottom.frame = basketBottomFrame;
[UIView commitAnimations];
请注意,您必须记得拨打commitAnimations
的电话,否则您的应用会出现行为异常。编译器不会警告您忘记拨打commitAnimations
。
在iOS 4.0中,Apple添加了块,他们添加了使用块为视图设置动画的新方法。例如:
[UIView animateWithDuration:0.5 delay:1 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.basketTop.frame = basketTopFrame;
self.basketBottom.frame = basketBottomFrame;
} completion:nil];
这里的优点是不可能忘记提交动画。如果忘记将}
放在块的末尾或]
放在方法的末尾,编译器会给出语法错误。并且Xcode会自动填写消息名称,因此您不必记住它是如何拼写的。
另一个优点是更好的代码组织。这是一个例子。
假设您要将UIImage
发送到服务器。将图像转换为PNG数据可能需要一些时间,因此您不希望在执行此操作时阻止主线程。你想在后台,在另一个线程上做。在iOS 4.0之前,您可能决定使用NSOperationQueue
。首先,您需要创建NSOperation
的子类来完成工作 1 :
@interface SendImageToServerOperation : NSOperation
@property (nonatomic, retain) UIImage *image;
@property (nonatomic, retain) NSURL *serverURL;
@end
@implementation SendImageToServerOperation
- (void)main {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.serverURL];
request.HTTPBody =UIImagePNGRepresentation(self.image);
NSURLResponse *response;
NSError *error;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// Handle response/error here?
}
@end
然后,要实际执行此操作,您需要创建一个操作并将其放在队列中:
- (void)sendImage:(UIImage *)image toServerURL:(NSURL *)serverURL {
SendImageToServerOperation *operation = [SendImageToServerOperation new];
operation.image = image;
operation.serverURL = serverURL;
[backgroundQueue addOperation:operation];
}
代码分散开来。从iOS 4.0开始,您可以使用块(以及新的GCD框架 2 )将它们组合在一起:
- (void)sendImage:(UIImage *)image toServerURL:(NSURL *)serverURL {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:serverURL];
request.HTTPBody =UIImagePNGRepresentation(image);
NSURLResponse *response;
NSError *error;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// Handle response/error here?
});
}
您不必创建新类甚至是单独的函数。您甚至不必创建任何额外的对象。您可以将代码放在最容易理解和维护的地方。
脚注1.这不一定是将数据上传到服务器的最佳方式。我选择了一种简单的教育方式。但是,想要在后台线程上创建PNG数据是切合实际的。
脚注2. NSBlockOperation
类(从iOS 4.0开始)允许您直接使用NSOperationQueue
块,如果您更喜欢使用GCD。
答案 2 :(得分:0)
切换到块使我的程序更加模块化和灵活。例如,在大多数情况下,我停止依赖委托,而是传递一个块(它将变量封装在父对象中),它以单向方式完成工作。
一般来说,我认为使用块有助于解耦代码(我发现它们适用于许多设计模式)。这是一个例子:
/*
* here block is basically is clean up code that is supposed to execute
* after this method finishes its work
*/
-(void)doSomeProcess:(void(^)(void))block {
// do stuff
// ..
// and when you're done
block();
}
// caller 1
[self doSomeProcess:^{
// block body:
// dismiss UI
}];
// caller 2
[self doSomeProcess:^{
// another block body:
// do business logic clean up
// dismiss UI
}];
并且许多对象或调用者可以调用doSomeProcess
方法,但每个方法或调用者都有自己的清理工作。
另一个例子:这是另一个例子(我确实只是这样做,所以我想我可以与你分享)..用KIF看看这个单元测试:
[sr performFilterAttachmentWithBlock:^(NSArray *fileBucketResults){
for (NSMutableDictionary* fileBucketResult in fileBucketResults) {
[elementsToAdd addObject:fileBucketResult];
[rowsToAdd addObject:[NSIndexPath indexPathForRow:cellIndex inSection:0]];
cellIndex++;
}
// note this notification
[[NSNotificationCenter defaultCenter]
postNotificationName:(NSString *)kFileBucketSubviewFetchedResultsFromDB
object:fileBucketResults];
} withQuery:query sortBy:(NSString *)kFileBucketSortBySenderName];
在KIF单元测试中,有一些单元测试依赖于发送通知..在使用块之前(以及使用委托时)..我不得不在我的实际代码中混合测试代码(即这个通知实际放置了在我的主要代码中)..但现在感谢块..我可以把我所有的测试代码放在一个块中,而这又放在我的单元测试文件中(即不与主代码混合).. =更清洁码! :)
另一个例子:它提供了一种很好的方法来隐藏一些非常具体的实用程序/辅助函数,这可以减少命名空间的混乱并使代码整体更清晰。例如:
// without blocks
-(void)someMethod {
// call this private method that does some helper stuff
[self helperMethod];
// we call the helper method several times in this f'n
[self helperMethod];
}
-(void)helperMethod {
// this method is only useful for 'some method'
// although it's only visible within this class.. it's still
// an extra method.. also nothing makes it obvious that
// this method is only applicable to 'someMethod'
..
}
// With blocks
-(void)someMethod {
void(^helperMethod)(void) = ^{
// helper block body
// this block is only visible to 'some method'
// so it's obvious it's only applicable to it
}
// call helper method..
helperMethod();
// .. as many times as you like
helperMethod();
}
here是一个问题/答案,用于说明将委托方法转换为块...