奇怪的NSURLSessionDownloadTask行为通过蜂窝(不是wifi)

时间:2014-07-09 23:16:45

标签: ios background nsurlsession cellular-network nsurlsessiondownloadtask

我已启用具有远程通知任务的后台模式,以便在应用收到推送通知时在后台下载小文件(100kb)。 我已使用

配置了下载会话
NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier];
[backgroundConfiguration setAllowsCellularAccess:YES];


self.backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfiguration
                                                       delegate:self
                                                  delegateQueue:[NSOperationQueue mainQueue]];

并使用

激活它
 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[hostComponents URL]];

[request setAllowsCellularAccess:YES];


NSMutableData *bodyMutableData = [NSMutableData data];
[bodyMutableData appendData:[params dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[bodyMutableData copy]];


_downloadTask =  [self.backgroundSession downloadTaskWithRequest:request];

[self.downloadTask resume];

现在一切正常,只有我通过Wifi或通过Cellular连接但iPhone连接到xCode的电缆,如果我断开iPhone并通过蜂窝接收推送通知,代码停在{{1没有调用URL的行。

处理这些操作的类是NSURLSessionDataDelegate,NSURLSessionDownloadDelegate,NSURLSessionTaskDelegate,因此实现:

[self.downloadTask resume];

我尝试在 - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location 之后插入带有UILocalNotification的调试行(现在'现在'),但是在5分钟后调用,并说{ {1}}暂停'

这种奇怪的行为到底是什么原因?

4 个答案:

答案 0 :(得分:9)

此处提供NSURLSessionConfiguration类参考的文档:

https://developer.apple.com/Library/ios/documentation/Foundation/Reference/NSURLSessionConfiguration_class/Reference/Reference.html#//apple_ref/occ/instp/NSURLSessionConfiguration/discretionary

说:对于自由裁量的财产:

  

<强>讨论

     

设置此标志后,插入时更有可能发生传输   进入电源和Wi-Fi。默认情况下,此值为false。

     

此属性仅在会话的配置对象使用时使用   最初通过调用backgroundSessionConfiguration构造:   方法,仅适用于应用程序位于前台时启动的任务。   如果在应用程序处于后台时启动任务,那么该任务就是   无论实际如何,都视为自由裁量权   此属性的值。对于基于其他人创建的会话   配置,此属性将被忽略。

这似乎意味着如果在后台启动下载,操作系统始终可以自行决定是否以及何时继续下载。在完成这些任务之前,操作系统似乎总是在等待wifi连接。

我的经验支持这个猜想。我发现当设备处于蜂窝状态时,我可以发送几个下载通知。他们仍然卡住了。当我将设备切换到wifi时,它们都会通过。

答案 1 :(得分:2)

我遇到了同样的问题,最后我设置了

configuration.discretionary = NO;

一切正常, 对于backgroundConfigurationdiscretionary = YES默认情况下,任务似乎与WIFI和电池一起开始。希望有用

答案 2 :(得分:0)

你在做什么

  • (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void(^)(UIBackgroundFetchResult))completionHandler {}

您是否在下载完成之前立即调用completionHandler?我相信这样做不会影响Wifi模式或连接到Xcode时的操作。但不知何故,当在Cellular的背景下,它会使下载停止,直到你去Wifi。

答案 3 :(得分:0)

唯一真正的解决方法是在应用程序在后台并使用CF套接字时转储NSURLSession。如果我使用CFStreamCreatePairWithSocketToHost打开CFStream,我可以在应用程序处于后台时通过蜂窝成功执行HTTP请求

#import "Communicator.h"

@implementation Communicator {
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;

    NSInputStream *inputStream;
    NSOutputStream *outputStream;
    CompletionBlock _complete;
}

- (void)setupWithCallBack:(CompletionBlock) completionBlock {
    _complete = completionBlock;
    NSURL *url = [NSURL URLWithString:_host];

    //NSLog(@"Setting up connection to %@ : %i", [url absoluteString], _port);

    CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)[url host], _port, &readStream, &writeStream);

    if(!CFWriteStreamOpen(writeStream)) {
        NSLog(@"Error, writeStream not open");

        return;
    }
    [self open]; 

    //NSLog(@"Status of outputStream: %lu", (unsigned long)[outputStream streamStatus]);

    return;
}

- (void)open {
    //NSLog(@"Opening streams.");

    inputStream = (__bridge NSInputStream *)readStream;
    outputStream = (__bridge NSOutputStream *)writeStream;

    [inputStream setDelegate:self];
    [outputStream setDelegate:self];

    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    [inputStream open];
    [outputStream open];
}

- (void)close {
    //NSLog(@"Closing streams.");

    [inputStream close];
    [outputStream close];

    [inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    [inputStream setDelegate:nil];
    [outputStream setDelegate:nil];

    inputStream = nil;
    outputStream = nil;
}

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event {
    //NSLog(@"Stream triggered.");

    switch(event) {
        case NSStreamEventHasSpaceAvailable: {
            if(stream == outputStream) {
                if (_complete) {
                    CompletionBlock copyComplete = [_complete copy];
                    _complete = nil;
                    copyComplete();
                }
            }
            break;
        }
        case NSStreamEventHasBytesAvailable: {
            if(stream == inputStream) {
                //NSLog(@"inputStream is ready.");

                uint8_t buf[1024];
                NSInteger len = 0;

                len = [inputStream read:buf maxLength:1024];

                if(len > 0) {
                    NSMutableData* data=[[NSMutableData alloc] initWithLength:0];

                    [data appendBytes: (const void *)buf length:len];

                    NSString *s = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];

                    [self readIn:s];

                }
            } 
            break;
        }
        default: {
            //NSLog(@"Stream is sending an Event: %lu", (unsigned long)event);

            break;
        }
    }
}

- (void)readIn:(NSString *)s {
    //NSLog(@"reading : %@",s);
}

- (void)writeOut:(NSString *)s{
    uint8_t *buf = (uint8_t *)[s UTF8String];

    [outputStream write:buf maxLength:strlen((char *)buf)];

    NSLog(@"Writing out the following:");
    NSLog(@"%@", s);
}

@end