简体   繁体   English

从块内部进行回调(目标C)

[英]Callback from inside a block (Objective C)

I have this method with a block in it, I want it to send the userID to another method as soon as it exists. 我有一个带有块的方法,我希望它在存在时立即将userID发送到另一个方法。 userID is a value that is parsed from the internet, so it usually takes about 2 seconds to load up and 'exist'. userID是从Internet解析的值,因此加载和“存在”通常需要2秒钟左右。 Is there any way I can do a 'when userID exists, send it to another method? 有什么方法可以将userID存在时,将其发送给另一种方法?
Here's all my code: 这是我所有的代码:

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

I see that this is the third question you ask related to the same issue, so I guess you're having some trouble understanding blocks. 我看到这是您提出的与同一问题相关的第三个问题,因此我想您在理解块时遇到了一些麻烦。

First you have to understand that the block, in a certain sense, can be seen as a function. 首先,您必须了解该块在某种意义上可以看作是一个函数。 The difference is that, unlike a function, the block has no name, and instead of using function's name you just place the code inline where you need it. 区别在于,与函数不同,该块没有名称,并且无需使用函数名称,而只是将代码内联在需要的位置。

Second thing to understand is that a block is usually used as a callback . 要了解的第二件事是,块通常用作回调 Other callback mechanisms are function pointers and delegates. 其他回调机制是函数指针和委托。 When you pass a block as a parameter to a function you're basically telling the function: "Hey, when certain conditions are met, execute this little code for me, please" 当您将块作为参数传递给函数时,您基本上是在告诉函数: “嘿,当满足某些条件时,请为我执行这个小代码”

Third think to understand is if the block (or any callback) will be called synchronously . 第三想理解的是,该块(或任何回调)是否将被同步调用。 Actually this has nothing to do with the block itself, per se , but rather with the function being called. 其实这无关与块本身, 本身 ,而是与被调用的函数。 If the function is asynchronous, the function will create another thread and return immediately to execute the next line after the one that invoked the asynchronous function. 如果该函数是异步的,则该函数将创建另一个线程并立即返回以执行调用异步函数的那一行之后的下一行。 Meanwhile the new thread will execute some code (the body of the async function) and, eventually execute the block passed as parameter, and finally the thread is killed and doesn't exist any more. 同时,新线程将执行一些代码(异步函数的主体),并最终执行作为参数传递的块,最后该线程被杀死并且不再存在。 ( Note: There's no way to know if a function is synchronous or asynchronous other that reading the documentation for such a function ). 注:除了阅读该函数的文档之外,没有其他方法可以知道该函数是同步的还是异步的 )。

Now let's go back to your code. 现在,让我们回到您的代码。

[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");
}];

Everything that should be executed once the user logged in should be placed inside the block (if the function is synchronous you could place it after the block). 用户登录后应执行的所有操作均应放置在块内(如果功能是同步的,则可以将其放置在块后)。 To send the userID to another class, just call that class' method as you would in any other part of your code. 要将userID发送到另一个类,只需像在代码的其他任何部分中一样调用该类的方法。

In my opinion using a delegate is not necessary (although only you would know, since you're the architect of your app). 我认为没有必要使用委托(尽管只有您会知道,因为您是应用程序的架构师)。

As @santhu said, use either the delegate pattern or notification pattern. 正如@santhu所说,请使用委托模式或通知模式。 It's also a common practice to use both of them. 同时使用它们都是一种常见的做法。 Usually a delegate is the correct approach but sometimes you need a notification. 通常,委托是正确的方法,但有时您需要通知。 Using both covers all your bases. 两者都适用于您的所有基础。

Look them up before deciding which and for full details on how they work, but basically: 在确定它们的工作方式以及有关其工作方式的完整详细信息之前,请先对其进行查找,但基本上是:

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


}];

There is a third option, which is you could use a block callback... this is how the new kids on the block do it... there's no well defined pattern here, blocks are brand new and delegates/notifications are 20 years old. 第三种选择是,您可以使用块回调...这是块上的新孩子执行的操作...这里没有明确定义的模式,块是全新的,并且委托/通知已使用20年。 But here's how I'd use a block to define a callback: 但是这是我使用块定义回调的方法:

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);
  }

  ...
}

I would like to give a hint regarding your comment: 关于您的评论,我想给您一个提示:

for code readability, it's not that I just have one more task to do, the thing I put inside this block will also have a block and another block and another. 出于代码可读性的考虑,这并不是说我还有一个任务要做,我放在此代码块中的内容还将包含一个代码块和另一个代码块。

This is a typical asynchronous pattern - called "continuation". 这是典型的异步模式-称为“连续”。

Given, that you should also implement proper error handling and that you should also provide a means to cancel that whole "chain" of asynchronous tasks at any point, the typical solutions with NSOperationQueues and NSOperations , dispatch_queue and blocks, NSNotifications or delegates will inevitable become unduly elaborate, complex and difficult to comprehend by others. 给定的是,您还应该实施适当的错误处理 ,还应该提供一种在任何时候取消异步任务的整个“链”的方法,使用NSOperationQueuesNSOperations ,dispatch_queue和块,NSNotifications或委托的典型解决方案将不可避免地成为过于复杂,复杂且难以被他人理解。 (There's already an answer here that demonstrates this grandiose ;) ) (这里已经有一个答案可以证明这个宏伟;))

So, whenever problems become more complex and the "built-in frameworks" don't provide a comfortable solution, third party libraries come into play to help you. 因此,只要问题变得更加复杂并且“内置框架”无法提供舒适的解决方案,第三方库就会为您提供帮助。

But first, lets have a non-trivial example, based on your comment: 但是首先,根据您的评论,让我们举一个简单的例子:

it's not that I just have one more task to do, the thing I put inside this block will also have a block and another block and another 这并不是说我还有一个任务要做,我放在这个区块中的东西也会有一个区块,另一个区块和另一个

OK, lets suppose your objective is actually: 好的,假设您的目标实际上是:

  1. Asynchronously perform a Login for a web service. 异步执行Web服务的登录。
  2. Then, if that succeeded, asynchronously fetch a list of objects as JSON . 然后,如果成功,则以JSON异步方式获取对象列表。
  3. Then, if that succeeded, parse the JSON response. 然后,如果成功,则解析JSON响应。
  4. Then, if that succeeded, insert the objects into a managed object context and asynchronously save the chain of managed object contexts and make it persistent. 然后,如果成功,则将对象插入托管对象上下文中,并异步保存托管对象上下文链并使其持久化。
  5. When this all above succeeded, update the UI on the main thread 当以上所有操作成功后,请更新主线程上的UI
  6. If anything fails, report the error of the task that failed 如果出现任何故障,报告失败任务的错误

I will show how a solution utilizing a library implementing "promises" (see wiki Future and promises ) may look like: 我将展示一个使用实现“ promises”的库的解决方案(请参阅Wiki Future and promises )看起来如何:

Without further ado, and without thorough explanation what that "Promise" is about, suppose we have a method defined in your View Controller, which is declared: 事不宜迟,也没有详尽的解释,“ Promise”是什么意思,假设我们在View Controller中定义了一个方法,该方法声明为:

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

Note : The above method is asynchronous and it is functional equivalent to the form: 注意 :上面的方法是异步的 ,其功能等效于以下形式:

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

then suppose we have another method in your View Controller, fetching objects from a remote server (asynchronous as well): 然后假设您的View Controller中有另一个方法,可以从远程服务器(也可以是异步的)中获取对象:

- (RXPromise*) fetchObjects;

Then, suppose we have a class CoreDataStack which consists of a "root context" saving to the persistent store having a child managed object context, the "main context", which is associated to the main thread. 然后,假设我们有一个类CoreDataStack ,该类由“根上下文”组成,该“根上下文”保存到具有子托管对象上下文(即“主上下文”)的持久性存储中,该子托管对象上下文与主线程关联。

The class CoreDataStack defines this method, which saves a chain of managed object contexts, which is basically setup: childContext -> main_context -> root_context: CoreDataStack定义了此方法,该方法保存了一系列托管对象上下文,该对象基本上是已设置的:childContext-> main_context-> root_context:

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

Then, the whole task as stated in the steps 1. through 5. can be expressed as follows: 然后,步骤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);
});

Disclaimer: I'm the author of the library RXPromise available at GitHub. 免责声明:我是GitHub上提供的RXPromise库的作者。 There are a few more Objective-C libraries which implement Promises. 还有一些实现Promises的Objective-C库。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM