13

Tôi đã bật Chế độ nền với nhiệm vụ thông báo từ xa để tải xuống tệp nhỏ (100kb) ở chế độ nền khi ứng dụng nhận được thông báo đẩy. Tôi đã cấu hình các phiên tải về sử dụngWeird NSURLSessionDownloadTask hành vi trên di động (không wifi)

NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier]; 
[backgroundConfiguration setAllowsCellularAccess:YES]; 


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

và kích hoạt nó bằng cách sử

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]; 

Bây giờ mọi thứ hoạt động một cách chính xác chỉ khi tôi đang kết nối thông qua Wifi hoặc qua mạng không dây nhưng với iPhone được kết nối bằng cáp để xCode, nếu tôi ngắt kết nối iPhone và nhận được thông báo đẩy qua di động, mã dừng tại số [self.downloadTask resume]; mà không cần gọi URL.

Các lớp xử lý các hoạt động là một NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSURLSessionTaskDelegate và do đó cụ:

- (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 

Tôi đã cố gắng để chèn một dòng debug với một UILocalNotification (đã trình bày 'bây giờ') sau khi [self.downloadTask resume] nhưng được gọi sau 5 phút và cho biết rằng self.downloadTask.state bị 'tạm ngưng'

Hành vi lạ lùng này là gì?

+0

Tôi đang trải qua hành vi tương tự chính xác này trên mạng với IOS 7.0 ATT. Vì không xảy ra khi kết nối với Xcode, tôi lưu nhật ký trên điện thoại để xem khi nào tôi khởi động lại ứng dụng. Tìm thấy rằng NSURLSessionDownloadTasks vẫn còn với State = NSURLSessionTaskStateRunning nhưng hành động như chúng bị đình chỉ. Nếu kết nối với wifi trong khi ứng dụng vẫn ở chế độ nền, những tải xuống bị ngừng hoạt động này hoàn tất thành công. Nếu tôi đưa ứng dụng vào nền trước, chúng vẫn bị trì hoãn. –

+0

Từ câu trả lời của Sani Elfshishawy bên dưới "khi được cắm vào nguồn và trên Wi-Fi" Có vẻ như với tôi rằng việc cắm vào XCODE sẽ thay đổi cắm vào điều kiện nguồn. –

+0

Tôi cũng nhấn cùng một trường hợp. Tôi đang kết hợp iBeacon với tác vụ nền NSURLSession nền để thực hiện một số kiểm tra khi người dùng gần đèn hiệu. Và tôi thấy rằng nhiệm vụ tải xuống không khả dụng khi người dùng sử dụng mạng di động. Các bạn có một số gợi ý không? –

Trả lời

0

gì bạn đang làm trong

  • (void) ứng dụng: (UIApplication *) ứng dụng didReceiveRemoteNotification: (NSDictionary *) UserInfo fetchCompletionHandler: (void (^) (UIBackgroundFetchResult)) completionHandler {}

Bạn có đang gọi trình hoàn tấtHandler ngay trước khi quá trình tải xuống của bạn hoàn tất không? Tôi tin rằng làm điều này không ảnh hưởng đến hoạt động ở chế độ Wifi hoặc khi được kết nối với Xcode. Nhưng bằng cách nào đó khi ở chế độ nền trên Cellular nó làm cho các gian hàng tải xuống cho đến khi bạn truy cập Wifi.

7

Các tài liệu cho NSURLSessionConfiguration Lớp tham khảo ở đây:

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

Says: cho các tài sản tùy tiện:

Thảo luận

Khi cờ này được thiết lập, chuyển nhiều có khả năng xảy ra khi cắm vào nguồn và trên Wi- Fi. Giá trị này là false theo mặc định.

Thuộc tính này chỉ được sử dụng nếu đối tượng cấu hình của phiên là ban đầu được xây dựng bằng cách gọi phương thức backgroundSessionConfiguration: và chỉ cho các tác vụ được bắt đầu trong khi ứng dụng ở nền trước. Nếu tác vụ được khởi động trong khi ứng dụng ở chế độ nền, nhiệm vụ đó là được coi là tùy ý đúng, bất kể giá trị thực tế là của thuộc tính này. Đối với các phiên được tạo dựa trên các cấu hình khác, thuộc tính này bị bỏ qua.

Điều này dường như ngụ ý rằng nếu tải xuống được bắt đầu ở nền, hệ điều hành luôn có quyền quyết định xem có nên tiếp tục tải xuống hay không. Có vẻ như hệ điều hành luôn chờ kết nối wifi trước khi hoàn thành các tác vụ này.

Trải nghiệm của tôi hỗ trợ phỏng đoán này. Tôi thấy rằng tôi có thể gửi một số thông báo để tải xuống trong khi thiết bị đang ở chế độ di động. Họ vẫn bị mắc kẹt. Khi tôi chuyển thiết bị sang wifi, tất cả chúng đều đi qua.

+0

WTF ?! Tại sao Apple đang làm điều này ?! Tất nhiên người dùng thường không có trong WiFi khi nhiệm vụ tải xuống được bắt đầu. Trong ứng dụng của tôi, tôi dựa vào việc tải xuống một lượng dữ liệu rất nhỏ khi ở chế độ nền và trong mạng di động (ví dụ do một ứng dụng đánh thức bởi thay đổi vị trí). Có phải bằng cách nào đó có thể? – blackjacx

+0

Điều này sẽ được đánh dấu là câu trả lời, vị trí của nó trên đó. Tôi đã nhìn thấy một số vấn đề bí ẩn mà tải về kỳ diệu chỉ bắt đầu khi kết nối qua cáp và luôn luôn là do thiết lập này. – Matt

1

Tôi có cùng một vấn đề, cuối cùng tôi đặt

configuration.discretionary = NO; 

mọi thứ hoạt động tốt, cho backgroundConfiguration, discretionary = YES mặc định, có vẻ như nhiệm vụ bắt đầu vào kết nối với WIFI và pin cả. hy vọng hữu ích

0

Cách duy nhất thực sự xung quanh việc này là đổ NSURLSession khi ứng dụng ở chế độ nền và sử dụng ổ cắm CF. Tôi có thể làm thành công các yêu cầu HTTP trên di động trong khi các ứng dụng đang ở chế độ nền nếu tôi sử dụng CFStreamCreatePairWithSocketToHost để mở CFStream

#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 
Các vấn đề liên quan