我刚刚阅读了有关块的内容,我了解到它们只是将信息封装为普通方法,但具有自己的强引用数据。我想知道积木有什么用处?
3 回答
这是应用于我的项目的块的用途;替换代表和协议(在某些情况下)。
问题
假设您需要从服务器异步加载数据。您可能有一个方法需要 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...
方法的调用者会将他的 AppClientrequestDelegate
属性实例设置为,并实现该方法,然后使用和参数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 请求到同一路径时,它会很糟糕,并尝试将这些请求与协议实现中的请求区分开来。我猜你可以为委托方法和putToPath...
为每个请求指定一个 id 的方法添加另一个参数,但这会很混乱和混乱。
另一个潜在的问题是,如果您在整个应用程序中广泛使用异步加载;这可能会导致大量的委托和协议。
块解决方案
我们扩展我们的方法签名以包含一个块:
- (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
}];
}
结论
您要求很好地使用积木,我相信这是一个很好的选择;它可能不适用于您,但您可以看到它不仅减少了代码量,而且还使其更具可读性和健壮性。
块可以通过多种方式帮助您编写更好的代码。这里有两个。
更可靠的代码
一个优点是更可靠的代码。这是一个具体的例子。
在 iOS 4.0 之前,要为视图设置动画,您必须使用beginAnimations:context:
和commitAnimations
消息,如下所示:
[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 将自动完成消息名称,因此您不必记住它的拼写方式。
更好的代码组织
另一个优点是更好的代码组织。这是一个例子。
假设您想将 a 发送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 的话。
切换到块使我的程序更加模块化和灵活。例如,在大多数情况下,我不再依赖委托,而是传递一个块(它封装了父对象中的变量),它以单向方式完成工作。
一般来说,我认为使用块有助于解耦你的代码(我发现使用它们适用于许多设计模式)。这是一个例子:
/*
* 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();
}
这是一个问题/答案,说明了将委托方法转换为块..