Objective-C中的块是否真的有用?它们的用途是什么?

时间:2013-06-29 03:59:05

标签: objective-c objective-c-blocks

我刚刚读过有关块的内容,而且我知道它们只是将信息封装为普通方法,但具有自己强大的引用数据。我想知道块的好用是什么?

3 个答案:

答案 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是一个问题/答案,用于说明将委托方法转换为块...