NSURLSessionDownloadTask 适用于 wifi,不适用于 4G
NSURLSessionDownloadTask works on wifi, not on 4G
我正在开发一个需要定期下载数据的应用程序,当该应用程序处于前台或后台时。
我已经使用 NSURLSessionDownloadTask 设置了网络,这样它在所有情况下都能正常工作,但只有一种情况除外。
当应用程序在物理设备上 运行ning 时,在前台并且网络在移动数据上,任务恢复到 运行ning 状态,但从不下载任何数据,从不获取任何进展,从不暂停。
如果我打开 wifi,运行 来自 Xcode(调试或发布)或 运行 在模拟器上,它工作正常。
我还应该提到它是间歇性的;昨天可以重现,今天不行,可能是后台网络受影响
编辑 - 示例代码
由于应用程序的复杂性和我能给它的时间有限,我不能给出一个完整的应用程序,但我已经包含了 AppDelegate 和 JobsManager classes 中的相关方法和整个 BackgroundJobFetcher class.
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
我相信我已经在 this so post 中找到了答案。
我已经删除了设置
sessionConfiguration.discretionary = YES;
虽然它仍在测试中,但现在似乎可以正常工作。
编辑: 测试表明这确实有效:)
我正在开发一个需要定期下载数据的应用程序,当该应用程序处于前台或后台时。
我已经使用 NSURLSessionDownloadTask 设置了网络,这样它在所有情况下都能正常工作,但只有一种情况除外。
当应用程序在物理设备上 运行ning 时,在前台并且网络在移动数据上,任务恢复到 运行ning 状态,但从不下载任何数据,从不获取任何进展,从不暂停。
如果我打开 wifi,运行 来自 Xcode(调试或发布)或 运行 在模拟器上,它工作正常。
我还应该提到它是间歇性的;昨天可以重现,今天不行,可能是后台网络受影响
编辑 - 示例代码
由于应用程序的复杂性和我能给它的时间有限,我不能给出一个完整的应用程序,但我已经包含了 AppDelegate 和 JobsManager classes 中的相关方法和整个 BackgroundJobFetcher class.
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
我相信我已经在 this so post 中找到了答案。
我已经删除了设置
sessionConfiguration.discretionary = YES;
虽然它仍在测试中,但现在似乎可以正常工作。
编辑: 测试表明这确实有效:)