三个方法使得iOS程序在关闭或者崩溃状态下能够在后台持续进行一些任务,比如更新程序界面快照,下载文件等

三种方式使得iOS程序即使在关闭或崩溃的情况下也能够在后台持续进行一些任务,比如更新程序界面快照,下载文件等。这三个方法分别是Background Fetch,Remote Notification和NSURLSession的backgroundSessionConfiguration

Background Fetch

开启

首先在info plist文件中开启UIBackgroundModes的Background fetch。或者手动编辑这个值

<key>UIBackgroundModes</key>
<array>
     <string>fetch</string>
</array>

iOS默认不进行background fetch,需要设置一个时间的间隔

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
     //UIApplicationBackgroundFetchIntervalMinimum表示尽可能频繁去获取,如果需要指定至少多少时间更新一次就需要给定一个时间值
     [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
     return YES;
}

最后在App Delegate里实现下面的方法,这个方法只能在30秒内完成。

- (void) application:(UIApplication *)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
     NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
     NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];

     NSURL *url = [[NSURL alloc] initWithString:@"http://yourserver.com/data.json"];
     NSURLSessionDataTask *task = [session dataTaskWithURL:url
               completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

          if (error) {
               completionHandler(UIBackgroundFetchResultFailed);
               return;
          }

          // 解析响应/数据以决定新内容是否可用
          BOOL hasNewData = ...
          if (hasNewData) {
               completionHandler(UIBackgroundFetchResultNewData);
          } else {
               completionHandler(UIBackgroundFetchResultNoData);
          }
     }];

     // 开始任务
     [task resume];
}

测试

  • 通过查看UIApplication的applicationState
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
     NSLog(@"Launched in background %d", UIApplicationStateBackground == application.applicationState);

     return YES;
}

Remote Notification

在普通的远程通知里带上content-available标志就可以在通知用户同时在后台进行更新。通知结构如下

{
     "aps" : {
          "content-available" : 1
     },
     "content-id" : 42
}

接收一条带有content-available的通知会调用下面的方法

- (void)application:(UIApplication *)application
          didReceiveRemoteNotification:(NSDictionary *)userInfo
          fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
     NSLog(@"Remote Notification userInfo is %@", userInfo);

     NSNumber *contentID = userInfo[@"content-id"];
     // 根据 content ID 进行操作
     completionHandler(UIBackgroundFetchResultNewData);
}

利用NSURLSession进行background transfer task

使用[NSURLSessionConfiguration backgroundSessionConfiguration]创建一个后台任务,当应用退出后,崩溃或进程被关掉都还是会运行。

范例,先处理一条远程通知,并将NSURLSessionDownloadTask添加到后台传输服务队列。

- (NSURLSession *)backgroundURLSession
{
     static NSURLSession *session = nil;
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
          NSString *identifier = @"io.objc.backgroundTransferExample";
          NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier];
          session = [NSURLSession sessionWithConfiguration:sessionConfig
               delegate:self
               delegateQueue:[NSOperationQueue mainQueue]];
     });

     return session;
}

- (void) application:(UIApplication *)application
     didReceiveRemoteNotification:(NSDictionary *)userInfo
     fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
     NSLog(@"Received remote notification with userInfo %@", userInfo);

     NSNumber *contentID = userInfo[@"content-id"];
     NSString *downloadURLString = [NSString stringWithFormat:@"http://yourserver.com/downloads/%d.mp3", [contentID intValue]];
     NSURL* downloadURL = [NSURL URLWithString:downloadURLString];

     NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
     NSURLSessionDownloadTask *task = [[self backgroundURLSession] downloadTaskWithRequest:request];
     task.taskDescription = [NSString stringWithFormat:@"Podcast Episode %d", [contentID intValue]];
     //执行resume保证开始了任务
     [task resume];

     completionHandler(UIBackgroundFetchResultNewData);
}

下载完成后调用NSURLSessionDownloadDelegate的委托方法,这些委托方法全部是必须实现的。了解所有类型session task的生命周期可以参考官方文档:https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/URLLoadingSystem/NSURLSessionConcepts/NSURLSessionConcepts.html#//apple_ref/doc/uid/10000165i-CH2-SW42

#Pragma Mark - NSURLSessionDownloadDelegate

- (void) URLSession:(NSURLSession *)session
     downloadTask:(NSURLSessionDownloadTask *)downloadTask
     didFinishDownloadingToURL:(NSURL *)location
{
     NSLog(@"downloadTask:%@ didFinishDownloadingToURL:%@", downloadTask.taskDescription, location);

     // 必须用 NSFileManager 将文件复制到应用的存储中,因为临时文件在方法返回后会被删除
     // ...

     // 通知 UI 刷新
}

- (void) URLSession:(NSURLSession *)session
     downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
     expectedTotalBytes:(int64_t)expectedTotalBytes
{
}

- (void) URLSession:(NSURLSession *)session
     downloadTask:(NSURLSessionDownloadTask *)downloadTask
     didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten
     totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
}

后台的任务完成后如果应用没有在前台运行,需要实现UIApplication的两个delegate让系统唤醒应用

- (void) application:(UIApplication *)application
     handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
     // 你必须重新建立一个后台 seesiong 的参照
     // 否则 NSURLSessionDownloadDelegate 和 NSURLSessionDelegate 方法会因为
     // 没有 对 session 的 delegate 设定而不会被调用。参见上面的 backgroundURLSession
     NSURLSession *backgroundSession = [self backgroundURLSession];

     NSLog(@"Rejoining session with identifier %@ %@", identifier, backgroundSession);

     // 保存 completion handler 以在处理 session 事件后更新 UI
     [self addCompletionHandler:completionHandler forSession:identifier];
}

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
     NSLog(@"Background URL session %@ finished events.\n", session);

     if (session.configuration.identifier) {
          // 调用在 -application:handleEventsForBackgroundURLSession: 中保存的 handler
          [self callCompletionHandlerForSession:session.configuration.identifier];
     }
}

- (void)addCompletionHandler:(CompletionHandlerType)handler forSession:(NSString *)identifier
{
     if ([self.completionHandlerDictionary objectForKey:identifier]) {
          NSLog(@"Error: Got multiple handlers for a single session identifier. This should not happen.\n");
     }

     [self.completionHandlerDictionary setObject:handler forKey:identifier];
}

- (void)callCompletionHandlerForSession: (NSString *)identifier
{
     CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey: identifier];

     if (handler) {
          [self.completionHandlerDictionary removeObjectForKey: identifier];
          NSLog(@"Calling completion handler for session %@", identifier);

          handler();
     }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容