NSURLSessionDownloadTask 适用于 wifi,而不是 4G

Posted

技术标签:

【中文标题】NSURLSessionDownloadTask 适用于 wifi,而不是 4G【英文标题】:NSURLSessionDownloadTask works on wifi, not on 4G 【发布时间】:2018-02-13 11:58:01 【问题描述】:

我正在开发一个需要定期下载数据的应用,当该应用处于前台或后台时。

我已经使用 NSURLSessionDownloadTask 设置了网络,这样它在除一种情况之外的所有情况下都能正常工作。

当应用在物理设备上运行时,在前台,网络在移动数据上,任务恢复到运行状态,但从不下载任何数据,永远不会获得任何进展,也永远不会挂起。

如果我打开 wifi、从 Xcode 运行(调试或发布)或在模拟器上运行,它工作正常。

我还应该提到它是间歇性的;我昨天可以重现,但今天不行,所以后台网络可能会受到影响。

编辑 - 示例代码

由于应用程序的复杂性和我能给予的时间有限,我不能给出一个完整的应用程序,但我已经包含了来自 AppDelegate 和 JobsManager 类以及整个 BackgroundJobFetcher 类的相关方法。

AppDelegate 方法:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 

// other stuff

    [BackgroundJobFetcher sharedInstance];


-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 
    [[BackgroundJobFetcher sharedInstance] setFetchCompletionHandler:completionHandler];
    [[JobsManager sharedInstance] fetchAllJobs];


- (void) application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler 
    [[BackgroundJobFetcher sharedInstance] setSavedCompletionHandler:completionHandler];

JobsManager 方法:

- (void) initiateProcesses 
    self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:30 repeats:YES block:^(NSTimer * _Nonnull timer) 
        [self fetchAllJobs];
    ];

- (void) fetchAllJobs 
    DefaultsManager *dManager = [DefaultsManager sharedDefaultsManager];
    NSString *apiFunction = @"getAllJobs";
    NSArray* optionsList = @[dManager.CustomerID, dManager.UserID];

    [self callAPIFunction:apiFunction options:optionsList];


- (void) callAPIFunction:(NSString*)apiFunction options:(NSArray*)options 
    NSString *apiBaseURL = @"https://www.*****.com/rest";
    NSString *urlComplete = [NSString stringWithFormat:@"%@/%@",
                             apiBaseURL,
                             apiFunction];

    for (NSString* option in options) 
        urlComplete = [NSString stringWithFormat:@"%@/%@", urlComplete, option];
    

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlComplete]
                                             cachePolicy:NSURLRequestReloadIgnoringCacheData
                                         timeoutInterval:30.0];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
        [[BackgroundJobFetcher sharedInstance] handleRequest:request];
    );

BackgroundJobFetcher.h:

#import <Foundation/Foundation.h>

@interface BackgroundJobFetcher : NSObject

@property (nonatomic, copy) void (^ _Nullable savedCompletionHandler)();
@property (nonatomic, copy) void (^ _Nullable fetchCompletionHandler)(UIBackgroundFetchResult);

+ (BackgroundJobFetcher*_Nonnull)sharedInstance;
- (void) handleRequest:(NSURLRequest*_Nonnull)request;
- (void) getAllTasksWithCompletionHandler:(void(^_Nonnull)(NSArray<__kindof NSURLSessionTask *> * _Nonnull tasks))completionHandler;
- (void) stopAllTasks;

@end

BackgroundJobFetcher.m:

#import "BackgroundJobFetcher.h"
#import "JobsManager.h"
#import "DefaultsManager.h"
#import "AppDelegate.h"

@interface BackgroundJobFetcher() <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate>

@property (nonatomic, strong) NSMutableData* responseData;
@property (nonatomic, retain) NSURLSession *defaultSession;

@end

@implementation BackgroundJobFetcher

+ (BackgroundJobFetcher*)sharedInstance 
    static BackgroundJobFetcher* _sharedInstance = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^
        _sharedInstance = [BackgroundJobFetcher new];
        NSURLSessionConfiguration* sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"MonitorJobFetcher"];
        sessionConfiguration.sessionSendsLaunchEvents = YES;
        sessionConfiguration.discretionary = YES;
        _sharedInstance.defaultSession = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                                       delegate:_sharedInstance
                                                                  delegateQueue:nil];
    );
    return _sharedInstance;


- (void) handleRequest:(NSURLRequest*)request 
    NSURLSessionDownloadTask* downloadTask = [self.defaultSession downloadTaskWithRequest:request];
    [downloadTask resume];


- (void) getAllTasksWithCompletionHandler:(void(^)(NSArray<__kindof NSURLSessionTask *> * _Nonnull tasks))completionHandler 
    [self.defaultSession getAllTasksWithCompletionHandler:completionHandler];


- (void) stopAllTasks 
    [self.defaultSession getAllTasksWithCompletionHandler:^(NSArray<__kindof NSURLSessionTask *> * _Nonnull tasks) 
        for (NSURLSessionTask* task in tasks) 
            [task cancel];
        
    ];


#pragma mark NSURLSessionDelegate methods

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error 
    DLog(@"%@", error.localizedDescription);


- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session 
    if (self.savedCompletionHandler) 
        self.savedCompletionHandler();
        self.savedCompletionHandler = nil;
    


- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler 
    DefaultsManager* dManager = [DefaultsManager sharedDefaultsManager];
    NSURLCredential *credential = [NSURLCredential credentialWithUser:dManager.Email
                                                             password:dManager.Password
                                                          persistence:NSURLCredentialPersistenceForSession];
    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, credential);


#pragma mark NSURLSessionTaskDelegate methods

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data 
    if (!self.responseData) 
        self.responseData = [NSMutableData dataWithData:data];
     else 
        [self.responseData appendData:data];
    


- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error 
    NSString* urlString = task.originalRequest.URL.absoluteString;
    if (error) 
        DLog(@"error: %@", error.localizedDescription);
        [[JobsManager sharedInstance] requestFailedWithError:error fromURL:urlString];
     else 
        [[JobsManager sharedInstance] jobsFetched:self.responseData];
    

    self.responseData = nil;

    if (self.fetchCompletionHandler) 
        self.fetchCompletionHandler(UIBackgroundFetchResultNewData);
        self.fetchCompletionHandler = nil;
    


- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler 
    DefaultsManager* dManager = [DefaultsManager sharedDefaultsManager];
    NSURLCredential *credential = [NSURLCredential credentialWithUser:dManager.Email
                                                             password:dManager.Password
                                                          persistence:NSURLCredentialPersistenceForSession];
    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, credential);


- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler 
    completionHandler(request);


- (void)URLSession:(NSURLSession *)session taskIsWaitingForConnectivity:(NSURLSessionTask *)task 
    DLog(@"URL: %@", task.originalRequest.URL.absoluteString);
    DLog(@"task.taskIdentifier: %lu", (unsigned long)task.taskIdentifier);


- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask

    [downloadTask resume];


- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics 
    DLog(@"URL: %@", task.originalRequest.URL.absoluteString);
    DLog(@"task.taskIdentifier: %lu", (unsigned long)task.taskIdentifier);
    DLog(@"metrics: %@", metrics);


#pragma mark NSURLSessionDownloadDelegate methods

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location 
    self.responseData = [[NSData dataWithContentsOfURL:location] mutableCopy];


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

    DLog(@"downloadTask.taskIdentifier: %lu", (unsigned long)downloadTask.taskIdentifier);
    DLog(@"fileOffset: %lld", fileOffset);
    DLog(@"expectedTotalBytes: %lld", expectedTotalBytes);


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

    DLog(@"downloadTask.taskIdentifier: %lu", (unsigned long)downloadTask.taskIdentifier);
    DLog(@"bytesWritten: %lld", bytesWritten);
    DLog(@"totalBytesWritten: %lld", totalBytesWritten);


@end

【问题讨论】:

【参考方案1】:

相信我已经在this so post找到了答案。

我已删除设置

sessionConfiguration.discretionary = YES;

它现在似乎可以工作了,尽管它仍在测试中。

edit: 测试表明这确实有效:)

【讨论】:

以上是关于NSURLSessionDownloadTask 适用于 wifi,而不是 4G的主要内容,如果未能解决你的问题,请参考以下文章

NSURLSessionDownloadTask 进度回调不顺畅?

在 NSMutableDictionary 中设置 NSURLSessionDownloadTask

NSUrlSessionDownloadTask - 进入后台时出现didCompleteWithError

iOS开发之网络编程--2NSURLSessionDownloadTask文件下载

NSURLSessionDownloadTask 在挂起时继续下载

在下载字节时从 NSURLSessionDownloadTask 访问字节