在iOS中如何在不同的线程上执行后台任务

3
如何在不同的线程上执行某些后台操作,如果在主线程上执行,则会阻塞我的应用程序UI。有没有任何想法如何做到这一点?即使在后台打印NSLog也可以。 我想运行以下内容,即使用户按下HOME按钮也可以。 在我的viewController中,我做了这个:
- (IBAction)btnStartClicked:(UIButton *)sender {
    [NSThread detachNewThreadSelector:@selector(StartBGTask) toTarget:self withObject:nil];
     }

-(void)StartBGTask{
    [[[UIApplication sharedApplication] delegate] performSelector:@selector(startThread)];  
  }

我在appDelegate.m文件中有以下方法:

 -(void) startThread {
 @autoreleasepool {
    for (int i = 0; i < 100; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"current progress %d", i);
        });
        [NSThread sleepForTimeInterval:1];
    }
   }
 }

该程序每隔1秒输出从1到100的整数。


你的 for () {} 正在主队列中执行。而且你使用 [NSThread sleepForTimeInterval:1],这不会阻塞你的用户界面吗?你停止了你的主队列。 - Timur Bernikovich
编辑并添加了更多细节,我尝试在不同的线程上执行第二个方法,但当按下主页按钮时,它会停止后台任务。 - Ravi_Parmar
为什么不直接使用计时器呢?你甚至不需要将其放在后台线程上。 - Abizern
正如我下面所说的,我认为他的观点不是打印1到100的数字,这可能只是一些“线程的helloWorld”来检查它是否正常工作。 - Lucas Eduardo
3个回答

8
请将以下内容添加到您的 .h 文件中:
@property (nonatomic, strong) NSTimer *updateTimer;
@property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;

现在请用以下代码替换 btnStartClicked 方法:
-(IBAction)btnStartClicked:(UIButton *)sender {
    self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
                                                        target:self
                                                      selector:@selector(calculateNextNumber)
                                                      userInfo:nil
                                                       repeats:YES];
    self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"Background handler called. Not running background tasks anymore.");
        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
        self.backgroundTask = UIBackgroundTaskInvalid;
    }];
    
}

 -(void)calculateNextNumber{
    @autoreleasepool {
      // this will be executed no matter app is in foreground or background
    }
}

如果需要停止它,请使用此方法:

 - (IBAction)btnStopClicked:(UIButton *)sender {

    [self.updateTimer invalidate];
    self.updateTimer = nil;
    if (self.backgroundTask != UIBackgroundTaskInvalid)
    {
        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
        self.backgroundTask = UIBackgroundTaskInvalid;
    }
    i = 0;
}

6

请查看GCD以获取更多信息。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //code in background
    });

0
一个非常简单的实现你想要的方法:
-(IBAction)btnStartClicked:(UIButton *)sender {
   [self performSelectorInBackground:@selector(codeInBakground) withObject:nil];
}

-(void)codeInBakground
{
   for (int i = 0; i < 100; i++) {
         NSLog(@"current progress %d", i);
        [NSThread sleepForTimeInterval:1]; //the code will print one number in each second, until 100
    }
}

这样做可以避免阻塞主线程和用户界面。


performSelector在ARC下表现不佳,编译器无法确定返回类型。 - Abizern
您能否更具体地说明可能存在的问题,或者提供一些源代码?我之前使用过类似这样的代码,并且没有发现任何问题、漏洞或泄漏。 - Lucas Eduardo
例如:https://dev59.com/X2w05IYBdhLWcg3w0U8O 我看到这个问题是因为我只在使用字符串表示时才使用performSelector。对于其他所有情况,我都使用GCD。其次,在您的情况下,您正在向不需要参数的选择器传递一个空对象。在这种情况下,您可以使用performSelector:而不是performSelector:withObject:。实际上,在这里您甚至不需要performSelector,您可以只使用dispatch_async()并将方法中的内容作为块运行。 - Abizern
首先,我理解你的观点,但是我并没有看到链接响应方面的任何问题。在这种情况下,它没有返回任何东西或类似的内容。关键是performSelectorInBackground比GCD更易于阅读和使用。其次,performSelectorInBackground:不存在,只有performSelectorInBackground:withObject:,这就是为什么在这种情况下我传递了nil的原因。 - Lucas Eduardo
根据我的经验,GCD 更容易阅读。要调用的方法就在那里,写在调用的上下文中,而使用 perform selector 则需要在源代码中查找另一个方法。此外,由于块可以捕获作用域内的变量,所以您不必处理传递参数的麻烦,块可以直接使用它们。在这种情况下,您甚至不需要做任何这样的事情,更好的解决方案是使用定时器。 - Abizern
我认为他的观点不是要打印出1到100的数字,这可能只是一个“线程的helloWorld”用来测试它是否正常工作。而且我确实同意GCD是有经验的程序员更好的方法,主要是因为你所指出的变量作用域,但对于初学者,我仍然更喜欢 performSelectorInBackground:withObject:,因为GCD有这些难看的参数,你必须进行一些研究才能理解它们的作用和你可以做什么。当然,你总是可以将其作为黑盒子使用而不知道发生了什么,但我不喜欢这种方式。 - Lucas Eduardo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接