2

我刚刚阅读了有关块的内容,我了解到它们只是将信息封装为普通方法,但具有自己的强引用数据。我想知道积木有什么用处?

4

3 回答 3

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...方法的调用者会将他的 AppClientrequestDelegate属性实例设置为,并实现该方法,然后使用和参数self验证正确的请求,并使用该参数执行某些操作。pathsentDatareceivedData

我们调用者的代码如下所示:

- (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
    }];
}

结论

您要求很好地使用积木,我相信这是一个很好的选择;它可能不适用于您,但您可以看到它不仅减少了代码量,而且还使其更具可读性和健壮性。

于 2013-06-29T05:25:18.240 回答
1

块可以通过多种方式帮助您编写更好的代码。这里有两个。

更可靠的代码

一个优点是更可靠的代码。这是一个具体的例子。

在 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 的话。

于 2013-06-29T04:38:58.670 回答
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();

}

是一个问题/答案,说明了将委托方法转换为块..

于 2013-06-29T04:28:32.673 回答