Xcode 6.3.1 ARC 启用,适用于 iOS 8.3
我需要帮助理解我在应用程序进入后台后尝试在应用程序中维护单例共享计时器时遇到的奇怪行为。
以前我不关心这个 NSTimer,因为它是使用后台位置服务在后台更新用户位置的。
但是,我想解决用户在后台拒绝位置更新或一起拒绝位置更新的情况。对于我的应用程序来说,位置和计时器是齐头并进的。
我在以下线程和网站帖子中研究了相当多的信息......
Apple 的 - 后台执行 https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html#//apple_ref/doc/uid/TP40007072-CH4-SW3
http://www.devfright.com/ios-7-background-app-refresh-tutorial/ 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 http://www.infragistics.com/community/blogs/stevez/archive/2013/01/24/ios-tips-and-tricks-working-in-the-background.aspx
如何让我的应用程序在后台运行 NSTimer? https://stackoverflow.com/questions/9220494/how-do-i-make-my-app-run-an-nstimer-in-the-background
如何在后台线程上创建 NSTimer? https://stackoverflow.com/questions/8304702/how-do-i-create-a-nstimer-on-a-background-thread
当应用程序在后台时安排 NSTimer? https://stackoverflow.com/questions/8415870/scheduled-nstimer-when-app-is-in-background
http://www.raywenderlich.com/92428/background-modes-ios-swift-tutorial http://www.raywenderlich.com/92428/background-modes-ios-swift-tutorial
iPhone - 后台投票事件 https://stackoverflow.com/questions/4656214/iphone-backgrounding-to-poll-for-events
根据这些信息,我能够推导出 (2) 种方法,使计时器保持运行约 8 小时,而 iOS 不会在分配的 180 秒后终止应用程序。 Apple 文档承诺的执行时间(3 分钟)。这是代码:
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)
<...
- (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;
}
}
...>
AppDelegate.m(方法#2)
<...
- (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++;
}
}
...>
我已经模拟了 (2) 方法一夜约 8 小时。 (28,800 秒)通过调整 (backgroundTimerCurrentValue >= 500) 的 if 语句。但为了重新创建此问题帖子的结果,我使用了 500 秒。这显然超过了 180 秒。以下是两种方法的结果:
... self.someTimer 迭代 500 次后,即 500 秒。
现在,这很棒,但是在使用 iPhone 5s 与 Xcode 断开连接等进行测试后,发生了一些奇怪的事情......我制作了一个单一视图应用程序来检查标签中 self.someTimer 的值。
长达 180 秒。标签会更新为正确的计时器值。 180秒后。有时,应用程序似乎仍然可以从打开的应用程序的幻灯片中选择,但是当我点击屏幕将应用程序置于前台时,应用程序会快速重新启动。
这种行为是苹果所承诺的,即 180 秒后。并且没有有效的 endBackgroundTask: 方法。方法部分是我认为无效的 UIBackgroundTaskIdentifier 类型,如方法#2 和 nil 对于方法#1。
所以我的问题如下:
1)当iPhone插入运行Xcode的Mac时与断开连接时有什么不同,系统是否允许某些条件发生,例如这种看似无休止的后台执行?
2)我总是可以通过位置服务来启动/停止它们,以便计时器值从上次位置更新以来过期的时间累积中继续更新,但是有人开发了这段代码来实际在断开连接的iPhone上运行吗?
3)如果问题2是这样,那么当我向App Store提交我的应用程序时,这样的东西是否合法并且会被接受?
在此先感谢您的帮助。干杯!