从块内回调(Objective C)

时间:2014-03-22 19:19:22

标签: ios objective-c asynchronous objective-c-blocks

我有这个方法,其中有一个块,我希望它尽快将userID发送到另一个方法。 userID是一个从互联网上解析的值,因此加载和“存在”通常需要大约2秒钟。有什么方法我可以做'当userID存在时,把它发送给另一种方法吗? 这是我的所有代码:

- (void)parseForUserID {
    //Get the Data you need to parse for (i.e. user main page returned as a block of NSData.
    TClient *client = [[TClient alloc] init];
    [client loginToMistarWithPin:@"20014204" password:@"yuiop" success:^{
        [client getUserID:^(NSString *result) {
            NSString *userIDWithHTML = [self userIDRegex:result];
            NSString *userID = [self onlyNumbersRegex:userIDWithHTML];

            //if userID exists, send it to another method in a different class

        }];
    } failure:^{
        NSLog(@"login failed from controller");
    }];
}

3 个答案:

答案 0 :(得分:3)

我看到这是你提出的与同一问题相关的第三个问题,所以我猜你在理解块时遇到了一些麻烦。

首先,您必须了解在某种意义上,块可以被视为一个函数。区别在于,与函数不同,该块没有名称,而是使用函数的名称,而只需将代码内联放在您需要的位置。

要理解的第二件事是块通常用作回调。其他回调机制是函数指针和委托。当你将一个块作为参数传递给一个函数时,你基本上都在告诉函数:“嘿,当满足某些条件时,请为我执行这个小代码,”

第三个想到要理解的是块(或任何回调)是否会被称为同步。实际上这与块本身无关,本身,而是与被调用的函数有关。如果该函数是异步的,则该函数将创建另一个线程并立即返回以执行调用异步函数的线程之后的下一行。同时新线程将执行一些代码(异步函数的主体),并最终执行作为参数传递的块,最后线程被终止并且不再存在。 (注意:除了阅读此类函数的文档之外,无法知道函数是同步函数还是异步函数

现在让我们回到你的代码。

[client loginToMistarWithPin:@"20014204" password:@"yuiop" success:^{
    [client getUserID:^(NSString *result) {
        NSString *userIDWithHTML = [self userIDRegex:result];
        NSString *userID = [self onlyNumbersRegex:userIDWithHTML];

        // PLACE HERE THE CODE TO EXECUTE WHEN SUCCESSFULLY LOGGED IN
        [anotherClassInstance someMethod:userID];

    }];
} failure:^{
    NSLog(@"login failed from controller");
}];

用户登录后应该执行的所有操作都应放在块内(如果函数是同步的,则可以将其放在块之后)。要将userID发送到另一个类,只需像在代码的任何其他部分中那样调用该类的方法。

在我看来,使用代理是没有必要的(虽然只有你知道,因为你是你的应用程序的架构师。)

答案 1 :(得分:0)

正如@santhu所说,使用委托模式或通知模式。使用两者也是一种常见做法。通常委托是正确的方法,但有时您需要通知。使用两者都涵盖了所有基础。

在决定它们之前查找它们以及它们如何工作的完整细节,但基本上是:

[client getUserID:^(NSString *result) {
    NSString *userIDWithHTML = [self userIDRegex:result];
    NSString *userID = [self onlyNumbersRegex:userIDWithHTML];

    // delegate pattern:
    if ([self userIdIsValid:userID]) {
      if (self.delegate && [self.delegate respondsToSelector:@selector(foundValidUserID:)]) {
        [self.delegate foundValidUserID:userID];
      }
    } else {
      if (self.delegate && [self.delegate respondsToSelector:@selector(foundInvalidUserID:)]) {
        [self.delegate foundInvalidUserID:userID];
      }
    }

    // notification pattern:
    if ([self userIdIsValid:userID]) {
      [[NSNotificationCenter defaultCenter] postNotificationName:MyFoundValidUserIDNotification object:self userInfo:@{@"userID": userID}];
      }
    } else {
      [[NSNotificationCenter defaultCenter] postNotificationName:MyFoundInvalidUserIDNotification object:self userInfo:@{@"userID": userID}];
    }


}];

还有第三个选项,你可以使用一个块回调...这就是块上的新孩子们做的事情......这里没有明确定义的模式,块是全新的,代表/通知已有20年历史。但这是我如何使用块来定义回调:

typedef void (^UserIdCallbackBlock)(NSString *userID);

- (void)parseForUserIDOnSuccess:(UserIdCallbackBlock)successCallback onFailure:(UserIdCallbackBlock)failureCallback {
  ...

  NSString *userID = [self onlyNumbersRegex:userIDWithHTML];

  if ([self userIdIsValid:userID]) {
    successCallback(userID);
  } else {
    failureCallback(userID);
  }

  ...
}

答案 2 :(得分:0)

我想对你的评论作出暗示:

  

为了代码可读性,并不是说我只有一个任务要做,我放在这个块里面的东西也会有一个块和另一个块和另一个块。

这是一种典型的异步模式 - 称为" continuation"。

鉴于,您还应该实施正确的错误处理,并且您还应该提供一种方法取消整个"链"任何时候的异步任务,NSOperationQueuesNSOperations,dispatch_queue和块,NSNotifications或代理的典型解决方案将不可避免地变得过于复杂且难以被其他人理解。 (这里已经有了答案,证明了这个宏伟的;))

因此,每当问题变得更加复杂时,内置框架就会出现问题。不提供舒适的解决方案,第三方图书馆可以帮助您。

但首先,根据您的评论,让我们有一个非常重要的例子:

  

并不是说我还有一项任务要做,我放在这个区块内的东西也会有一个区块和另一个区块而另一个区域

好的,我们假设您的目标实际上是:

  1. 异步执行Web服务登录。
  2. 然后,如果成功,则异步获取对象列表JSON
  3. 然后,如果成功,则解析JSON响应。
  4. 然后,如果成功,则将对象插入到托管对象上下文中,并异步保存托管对象上下文链并使其持久化。
  5. 如果上述所有内容都成功,请更新主线程上的UI
  6. 如果任何失败,请报告失败任务的错误
  7. 我将展示如何利用图书馆实施"承诺" (参见wiki Future and promises)可能看起来像:

    没有进一步的麻烦,没有彻底的解释,"承诺"是的,假设我们在View Controller中定义了一个方法,该方法被声明为:

    - (RXPromise*) loginToMistarWithPin:(NSString*)pin 
                               password:(NSString*)password;  
    
      

    注意:上述方法是异步,它的功能与以下形式相同:

            typedef void (^completion_t)(id result, NSError*error);
            - (void) loginToMistarWithPin:(NSString*)pin 
                                 password:(NSString*)password 
                               completion:(completion_t)completion;
    

    然后假设我们在View Controller中有另一个方法,从远程服务器获取对象(也是异步的):

    - (RXPromise*) fetchObjects;
    

    然后,假设我们有一个类CoreDataStack,它包含一个"根上下文"保存到具有子托管对象上下文的持久性存储,"主上下文",与主线程关联。

    CoreDataStack定义了这个方法,它保存了一系列托管对象上下文,基本上是设置的:childContext - > main_context - > root_context:

    - (RXPromise*) saveWithChildContext:(NSManagedObjectContext*)childContext;
    

    然后,步骤1到5中所述的整个任务可表示如下:

    [client loginToMistarWithPin:@"20014204" password:@"yuiop"]
    .then(^id(id result){
        // login succeed, ignore result which is @"OK"
    
        // Now fetch the objects with an asynchronous network request, 
        // returning JSON data as a NSData object when it succeeds:
        return [client fetchAllUsers];
    }, nil)
    .then(^id(NSData* json){
        // The network request succeeded, and we obtain the JSON as NSData.
        // Parse it and get a Foundation representation:
        NSError* error;
        id jsonArray = [NSJSONSerialization JSONObjectWithData:json 
                                                       options:0 
                                                         error:&error];
        if (jsonArray) {
            return jsonArray;  // handler succeeded
        }
        else {
            return error;      // handler failed
        }        
    }) 
    .then(^id(NSArray* objects){
        // Parsing succeeded. Parameter objects is an array containing 
        // NSDictionaries representing a type "object".
    
        // Save into Core Data:
        // Create a managed object context, which is a child of the 
        // "main context" of a Core Data stack:
        NSManagedObjectContext* moc = [[NSManagedObjectContext alloc]
                          initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        moc.parentContext = self.coreDataStack.managedObjectContext;
        // Create managed objects and initialize them with the given 
        // NSDictionary:
        for (NSDictionary* object in objects) {
            // note: `createWithParameters:inManagedObjectContext` executes on
            // the context's queue
            [Object createWithParameters:object inManagedObjectContext:moc];
        }
        // Finally, asynchronously save into the persistent store and
        // return the result (a RXPromise):
        return [self.coreDataStack saveWithChildContext:moc]; 
    }, nil)
    .thenOn(dispatch_get_main_queue(), ^id(id result){
        // Saving to the backing store succeeded. Now, we possibly want to 
        // update some UI on the main thread. We are executing on the main
        // thread already (see thenOn(dispatch_get_main_queue())
        ...
        [self.tableView reloadData];
        return nil;
    }, nil)
    .then(nil, ^id(NSError* error){
        // If something went wrong in any of the above four steps, the error 
        // will be propagated down and "cought" in this error handler:
        NSLog(@"Error: %@", error);
    });
    

    免责声明:我是GitHub上可用的库RXPromise的作者。还有一些Objective-C库实现了Promises。