【问题标题】:Ensuring the codes in different sections run in the background queue in iOS确保不同部分的代码在 iOS 的后台队列中运行
【发布时间】:2013-02-09 12:39:41
【问题描述】:

我是 iOS 多线程的新手。我需要做三件事:从 api 获取信息,解析信息并保存到我的数据库。我将这三样东西放在不同的文件中(getAPI、parseAPI 和 savetoDB)。 getAPI 将调用 parseAPI,它会反过来调用 savetoDB。我希望他们三个都在后台线程中工作。

我的问题是,当我调用 getAPI 时,parseAPI 和 savetoDB 也会在后台线程中运行吗?如何确保它们三个都在后台运行? savetoDB后​​如何将调用返回到主线程?

例子:

dispatch_queue_t backgroundQueue;
backgroundQueue = dispatch_queue_create("lakesh", NULL);  
- (void)startprocess {    
    dispatch_async(backgroundQueue, ^(void) {
        [self getAPI];
    });    
}

需要一些指导..谢谢...

【问题讨论】:

    标签: ios objective-c multithreading grand-central-dispatch


    【解决方案1】:

    如果您在后台线程上发出函数,则所有执行都将在该线程上继续,直到它完成或您在主线程上回调另一个函数。一开始我也和你一样担心,所以我给自己做了以下宏:

    /// Stick this in code you want to assert if run on the main UI thread.
    #define DONT_BLOCK_UI() \
        NSAssert(![NSThread isMainThread], @"Don't block the UI thread please!")
    
    /// Stick this in code you want to assert if run on a background thread.
    #define BLOCK_UI() \
        NSAssert([NSThread isMainThread], @"You aren't running in the UI thread!")
    

    正如您从 cmets 所看到的,我倾向于在方法的开头使用这些宏,以确保我不会在错误的线程中错误地使用这些宏。我已将这些宏和更多随机内容放在https://github.com/gradha/ELHASO-iOS-snippets,您可能会发现它们很有用。

    关于您关于返回主线程的问题,由于您使用的是 GCD,因此最好在您的 savetoDB 末尾使用您要在其中运行的代码调用 dispatch_get_main_queue()。如果savetoDB 是一个库函数,它的入口点应该允许在一切完成后传入您想要在主线程上运行的成功块。这是 https://github.com/AFNetworking/AFNetworking 等库使用的模式。请注意他们的示例如何提供一个 API,其中的东西在后台运行,然后您的代码被回调(通常在主线程中)。

    【讨论】:

      【解决方案2】:

      是的,parseAPIsavetoDB 将在您创建的新队列中运行。如果您需要在操作完成后修改 UI,该代码必须在主线程中运行。为此,请获取对主队列的引用并向其发送一些代码。例如:

      - (void)startprocess {    
          dispatch_async(backgroundQueue, ^(void) {
              [self getAPI];
              dispatch_async(dispatch_get_main_queue(), ^{
                   // Refresh the UI with the new information
              });
          });    
      }
      

      完成后不要忘记dispatch_release您的新队列。

      Cocoa 本身在框架的许多部分中使用的另一种模式是在后台操作结束时调用的 API 函数的签名中添加回调块。 This Stack Overflow thread 解释了如何做到这一点。

      【讨论】:

        【解决方案3】:

        当然,如果getAPI 调用parseAPIparseAPI 的代码将在与getAPI 执行的线程相同的线程上执行,因此在您的示例中在后台队列中。

        要在最后将回调返回到主线程,请使用与 Apple 使用 completionBlock 相同的技术,您可以在多个 Apple API 上看到:只需传递一个块(例如 dispatch_block_tvoid(^)(NSError*) 或其他适合您的需要)作为getAPI: 方法的参数,该方法将其传递给parseAPI:,后者又将其传递给savetoDB:,最后savetoDB: 可以简单地使用dipatch_async(dispatch_get_main_queue, completionBlock); 调用此代码块(从方法传递到方法)在主线程上。

        注意:对于您的 getAPI,您可以使用 Apple 的 sendAsynchronousRequest:queue:completionHandler: 方法,该方法将在后台自动执行请求,然后在指定的 NSOperationQueue 上调用完成块(NSOperationQueue 在内部使用 GCD 的 dispatch_queue)。有关更多信息,请参阅有关 NSOperationQueue、GCD 和 Concurrency Programming Guide 的文档以及 Apple 文档中的所有详细指南。


        -(void)getAPI:( void(^)(NSError*) )completionBlock
        {
          NSURLRequest* req = ...
          NSOperationQueue* queue = [[NSOperationQueue alloc] init]; // the completionHandler will execute on this background queue once the network request is done
          [NSURLConnection sendAsynchronousRequest:req queue:queue completionHandler:^(NSURLResponse* resp, NSData* data, NSError* error)
           {
             if (error) {
               // Error occurred, call completionBlock with error on main thread
               dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(error); });
             } else {
               [... parseAPI:data completion:completionBlock];
             }
           }];
        }
        
        -(void)parseAPI:(NSData*)dataToParse completion:( void(^)(NSError*) )completionBlock
        {
           ... parse datatToParse ...
        
           if (parsingError) {
             dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(error); });
           } else {
             [... savetoDB:dataToSave completion:completionBlock];
           }
        }
        
        -(void)savetoDB:(id)dataToSave completion:( void(^)(NSError*) )completionBlock
        {
           ... save to your DB ...
        
           // Then call the completionBlock on main queue / main thread
           dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(dbError); }); // dbError may be nil if no error occurred of course, that will tell you everything worked fine
        }
        
        -(void)test
        {
          [... getAPI:^(NSError* err)
           {
              // this code will be called on the main queue (main thread)
              // err will be nil if everythg went OK and vontain the error otherwise
           }];
        }
        

        【讨论】:

        • 非常感谢...保存到数据库后,我想传回一些变量,以便我可以根据变量采取行动。我该怎么做?
        • 与我在示例中使用 NSError 的方式完全相同(这就是为什么我实际上在示例代码中添加了它,以向您展示如何将一些变量传递给您的完成块,例如 NSError我的示例,但您可以在块中添加任何其他变量以满足您的需要)。如果您不熟悉块,请阅读 Apple 文档中的 de Blocks Programming Guide 了解更多信息。
        • 我理解您给出的示例中的要点.. 但我的意思是我在 saveDB 中有一些局部变量,例如,我想将其传递回主线程.. 我可以这样做吗? dispatch_async(dispatch_get_main_queue(), processStatus);?
        • 我不明白你在这里的困难,它真的和我的例子一样......假设你需要在saveDB方法的末尾传递一个NSDictionary和一个int ,只需在完成块的签名中添加这些参数!例如。它将变为saveDB:(id)dataToSave completion:( void(^)(NSError*, NSDictionary*, int) )completion,当您调用完成块时,您只需传递您想要返回的值,例如completion(nil, someDict, 5)。就像我在上面的示例中使用 NSError 所做的一样,您对要返回的任何其他值执行相同操作
        猜你喜欢
        • 1970-01-01
        • 2013-07-14
        • 1970-01-01
        • 2013-06-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-24
        • 2016-04-04
        相关资源
        最近更新 更多