【问题标题】:Are blocks in Objective-C are really useful? What can their utility be? [closed]Objective-C 中的块真的有用吗?它们的效用是什么? [关闭]
【发布时间】:2013-07-22 14:06:15
【问题描述】:

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

【问题讨论】:

  • 继续阅读,相信你会找到你要的例子。
  • 它们当然非常适合制造混乱。
  • 如果有的话,它们封装了行为并且更类似于函数而不是方法。他们没有对对象变量的内在访问(除非作为参数传递)
  • 此问题“暂停”。简短的回答是:它们就像函数指针,但与函数指针相比​​,它们提供的限制少得多/灵活性大得多。他们还可以执行引用计数或复制其状态以重新定位到其他上下文。所以查看它的简单方法(如果你知道 C)是想象如果使用函数指针而不是块,你的程序会是什么样子(以及它们将如何失败,例如使用 ref 计数)。结果:您最终会编写大量实用程序类并失去类型安全性。

标签: objective-c objective-c-blocks


【解决方案1】:

这是一个应用于我的项目的块的用途;替换委托和协议(在某些情况下)。

问题

假设您需要从服务器异步加载数据。您可能有一个方法需要 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,并实现该方法,然后使用pathsentData 参数验证正确的请求,然后执行一些操作-or-other 与 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
    }];
}

结论

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

【讨论】:

    【解决方案2】:

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

    更可靠的代码

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

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

    【讨论】:

      【解决方案3】:

      切换到块使我的程序更加模块化和灵活。例如,在大多数情况下,我不再依赖委托,而是传递一个块(它封装了父对象中的变量),它以单向方式完成工作。

      总的来说,我认为使用块有助于解耦代码(我发现使用它们适用于许多设计模式)。这是一个例子:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-12-26
        • 1970-01-01
        • 2011-03-28
        • 2011-06-25
        • 2020-05-17
        • 2011-07-02
        • 1970-01-01
        • 2012-08-08
        相关资源
        最近更新 更多