后台的NSTimer行为(addTimer:,beginBackgroundTaskWithExpirationHandler:)

时间:2015-06-23 22:56:05

标签: ios iphone xcode background nstimer

Xcode 6.3.1 ARC Enabled,适用于iOS 8.3

我需要帮助理解我在应用程序进入后台后在我的应用程序中维护单例共享计时器时遇到的奇怪行为。

之前我并不关心这个NSTimer,因为它使用后台位置服务在后台更新了用户位置。

但是,我想解决用户在后台或位置更新时拒绝位置更新的情况。位置和计时器与我的应用程序齐头并进。

我在以下主题和网站帖子中处理了相当多的信息.​​.....

Apple's - Background Execution

http://www.devfright.com/ios-7-background-app-refresh-tutorial/

http://www.infragistics.com/community/blogs/stevez/archive/2013/01/24/ios-tips-and-tricks-working-in-the-background.aspx

How do I make my App run an NSTimer in the background?

How do I create a NSTimer on a background thread?

Scheduled NSTimer when app is in background?

http://www.raywenderlich.com/92428/background-modes-ios-swift-tutorial

iPhone - Backgrounding to poll for events

根据这些信息,我能够推导出(2)保持计时器运行约8小时的方法,而没有iOS在分配180秒后终止应用程序。 (3分钟)执行时间由Apple文档承诺。这是代码:

AppDelegate.h (方法#1或#2不变)

#import <UIKit/UIKit.h>

@interface MFAppDelegate : UIResponder <UIApplicationDelegate>
{
    UIBackgroundTaskIdentifier bgTask;
    NSInteger backgroundTimerCurrentValue;
}

@property (retain, nonatomic) NSTimer *someTimer;

@end

AppDelegate.m (方法#1)

&LT; ...

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

NSLog(@"applicationDidEnterBackground");

backgroundTimerCurrentValue = 0;

[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];

self.someTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:true];

[[NSRunLoop currentRunLoop] addTimer:self.someTimer forMode:NSRunLoopCommonModes];

}

- (void)timerMethod
{
    NSTimeInterval backgroundTimeRemaining = [[UIApplication sharedApplication] backgroundTimeRemaining];

    if (backgroundTimeRemaining == DBL_MAX)
    {
        NSLog(@"Background Time Remaining = Undetermined");
    }
    else
    {
        NSLog(@"Background Time Remaining = %0.2f sec.", backgroundTimeRemaining);
    }

    if (!someBackgroundTaskStopCondition)
    {
        [self endBackgroundTask];
    }
    else
    {
        nil;
    }
}

...&GT;

AppDelegate.m (方法#2)

&LT; ...

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

    NSLog(@"applicationDidEnterBackground");

    backgroundTimerCurrentValue = 0;

    bgTask = UIBackgroundTaskInvalid;

    self.backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{
        [application endBackgroundTask:bgTask];
    }];

    self.someTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:true];

}

- (void)timerMethod
{
    NSTimeInterval backgroundTimeRemaining = [[UIApplication sharedApplication] backgroundTimeRemaining];

    if (backgroundTimeRemaining == DBL_MAX)
    {
        NSLog(@"Background Time Remaining = Undetermined");
    }
    else
    {
        NSLog(@"Background Time Remaining = %0.2f sec.", backgroundTimeRemaining);
    }

    if (backgroundTimerCurrentValue >= 500)
    {
        backgroundTimerCurrentValue = 0;
        [self.someTimer invalidate];
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    }
    else
    {
        NSLog(@"backgroundTimerCurrentValue: %ld", backgroundTimerCurrentValue);
        backgroundTimerCurrentValue++;
    }
}

...&GT;

我已经将(2)方法模拟过夜~8小时。 (28,800秒)通过调整(backgroundTimerCurrentValue&gt; = 500)的if语句。但是为了重新创建这个问题的结果,我使用了500秒。这显然超过180秒。这些是两种方法的结果:

enter image description here

...在self.someTimer的500次迭代之后,即500秒

enter image description here

现在,这很棒,但是在用iPhone 5s与Xcode断开连接等测试之后发生了一些奇怪的事情...... 我制作了一个单一的视图应用来检查self.someTimer的值在标签中。

最多180秒。标签使用正确的计时器值更新。 180秒后有时候,应用程序似乎仍然可以从打开的应用程序的幻灯片中选择,但是当我点击屏幕将应用程序置于前台时,应用程序会快速重新启动。

此行为是Apple承诺的,即180秒后。并没有有效的endBackgroundTask:方法。方法部分是我认为无效的类型UIBackgroundTaskIdentifier,如方法#2和nil方法#1。

所以我的问题如下:

1)有什么不同于iPhone从断开连接时插入运行Xcode的Mac时,系统是否允许某些条件发生,例如看似无休止的后台执行?

2)我总是可以使用位置服务来启动/停止它们,以便定时器值从上次位置更新后的一些累积时间内继续更新,但是有人开发此代码实际上是在断开连接的iPhone上运行?

3)如果在问题2中如此,那么这是合法的,并且在我为App Store提交应用程序时会被接受吗?

提前感谢您的帮助。干杯!

1 个答案:

答案 0 :(得分:3)

是的,通过调试器运行应用程序将使其保持活动状态,否则它将在3分钟后终止。所以,你显然不能在生产应用程序中依赖该功能。

不,尝试让应用程序保持活跃超过这段时间是不合法的(除非您已请求正确的后台操作,因为您的应用程序具有合法且迫切需要后台操作,例如它是音乐播放器,VOIP,导航应用程序等)。 Apple可能会拒绝任何不符合App Store Review Guidelines第2.16节的应用程序。

有关有效后台操作的更全面讨论,请参阅适用于iOS的应用程序编程指南Background Execution章节。