Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/objective-c/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios AFNetworking 2.0将NSURLSessionDataTask转换为NSURLSessionDownloadTask不需要';t将所有文件数据写入磁盘_Ios_Objective C_Network Programming_Afnetworking 2 - Fatal编程技术网

Ios AFNetworking 2.0将NSURLSessionDataTask转换为NSURLSessionDownloadTask不需要';t将所有文件数据写入磁盘

Ios AFNetworking 2.0将NSURLSessionDataTask转换为NSURLSessionDownloadTask不需要';t将所有文件数据写入磁盘,ios,objective-c,network-programming,afnetworking-2,Ios,Objective C,Network Programming,Afnetworking 2,将NSURLSessionDataTask转换为NSURLSessionDownloadTask时,我们遇到数据丢失。具体来说,对于大于16K的文件,我们将丢失前16K字节(确切地说是16384字节)。写入磁盘的文件短于初始响应的长度 长文章,感谢阅读和任何建议 更新2014-09-30-最终修复 所以我最近又遇到了同样的行为,我决定更深入地研究。事实证明,Matt T(AFNetworking的作者)发布了一个commit,它修改了AFURLSessionManager-respondsToS

将NSURLSessionDataTask转换为NSURLSessionDownloadTask时,我们遇到数据丢失。具体来说,对于大于16K的文件,我们将丢失前16K字节(确切地说是16384字节)。写入磁盘的文件短于初始响应的长度

长文章,感谢阅读和任何建议

更新2014-09-30-最终修复 所以我最近又遇到了同样的行为,我决定更深入地研究。事实证明,Matt T(AFNetworking的作者)发布了一个commit,它修改了
AFURLSessionManager-respondsToSelector
方法,如果任何可选的委托调用未设置为块,它将返回NO。提交就在这里(第1779期):

因此,使用可选委托的方法应该是使用块调用
-setTaskDidReceiveAuthenticationChallengeBlock:
方法(调用您要使用的可选委托的方法),而不是覆盖子类中的
-URLSession:dataTask:didReceiveResponse:completionHandler:
方法。这样做会产生预期的结果

设置: 我们正在编写一个从web服务器下载文件的iOS应用程序。这些文件由一个php脚本保护,该脚本对来自iOS客户端的请求进行身份验证

我们正在使用AFNetworking 2.0+,并正在对发送用户凭据等的API执行初始POST(NSURLSessionDataTask)操作。以下是最初的请求:

NSURLSessionDataTask*task=[self POST:API_FULL_SYNC_GETFILE_路径参数:body success:^(NSURLSessionDataTask*task,id responseObject){..}]

我们有一个自定义类,它继承自
AFHTTPSessionManager
类,其中包含此问题中的所有iOS代码

服务器接收此请求并对用户进行身份验证。POST参数之一是客户端尝试下载的文件。服务器定位文件并将其吐出。为了让事情开始变得简单,我删除了身份验证和一些缓存控制头,但下面是运行的服务器php脚本:

$file_name = $callparams['FILENAME'];
$requested_file = "$sync_data_dir/$file_name";

@apache_setenv('no-gzip', 1);
@ini_set('zlib.output_compression', 'Off');
set_time_limit(0);`

$file_size = filesize($requested_file);
header("Content-Type: application/gzip");
header("Content-Transfer-Encoding: Binary");
header("Content-Length: {$file_size}");
header("Content-Disposition: attachment; filename=\"{$file_name}\"");

$read_bytes = readfile($requested_file);
这些文件总是.gz文件

回到客户端,将收到响应,并调用
NSURLSessionDataDelegate
-URLSession:dataTask:didReceiverResponse:completionHandler:
方法。我们检测MIME类型并将任务切换到下载任务:

-(void)URLSession:(NSURLSession *)session
         dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    [super URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];

    /*
     This transforms a data task into a download taks for certain API calls. Check the headers to determine what to do
     */
    if ([response.MIMEType isEqualToString:@"application/gzip"]) {
        // Convert to download task
        completionHandler(NSURLSessionResponseBecomeDownload);
        return;
    }
    // continue as-is
    completionHandler(NSURLSessionResponseAllow);

}
调用
-URLSession:dataTask:didBecomeDownloadTask:
方法。我们使用此方法使用id关联数据任务和下载任务。这样做是为了在数据任务完成处理程序中跟踪下载任务的结果。对于这个问题来说不是非常重要,但下面是代码:

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
    [super URLSession:session dataTask:dataTask didBecomeDownloadTask:downloadTask];

    // Relate the data task with the download task.
    if (!_downloadTaskIdToDownloadIdTaskMap) {
        _downloadTaskIdToDownloadIdTaskMap = [NSMutableDictionary dictionary];
    }
    [_downloadTaskIdToDownloadIdTaskMap setObject:@(dataTask.taskIdentifier) forKey:@(downloadTask.taskIdentifier)];
}
出现问题的地方: 在
-URLSession:downloadTask:didfishdownloadingtourl:
方法中,写入的临时文件的大小小于内容长度

我们发现: A) 如果我们实现了
NSURLSessionTaskDelegate
类的
URLSession:dataTask:didReceiveData:
方法,那么对于我们尝试下载的每个文件,我们只观察到一个调用。如果该文件大于16384字节,则生成的临时文件将缩短该长度。在这个方法中放入一个日志条目,我们可以看到数据参数的长度是16384字节(对于大于该长度的文件)

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    [super URLSession:session dataTask:dataTask didReceiveData:data];

    NSMutableDictionary *dataTaskDetails = [_dataTaskDetails objectForKey:@(dataTask.taskIdentifier)];
    NSString *fileName  = dataTaskDetails[@"FILENAME"];

    DDLogDebug(@"Data recieved for file '%@'. Data length %d",fileName,data.length);
}
B) 将日志条目放入
NSURLSessionDownloadDelegate
类的
URLSession:downloadTask:didWriteData:TotalBytesWrite:totalBytesExpectedToWrite:
方法中,我们观察到每个尝试下载的文件都有一个或多个对此方法的调用。如果文件是16K,我们会接到更多的呼叫。以下是该方法:

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    [super URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];

    id dataTaskId = [_downloadTaskIdToDownloadIdTaskMap objectForKey:@(downloadTask.taskIdentifier)];
    NSMutableDictionary *dataTaskDetails = [_dataTaskDetails objectForKey:dataTaskId];
    NSString *fileName  = dataTaskDetails[@"FILENAME"];
    DDLogDebug(@"File '%@': Wrote %lld bytes. Total %lld of %lld bytes written.",fileName,bytesWritten,totalBytesWritten,totalBytesExpectedToWrite);
}
例如,下面是单个文件“members.json.gz”的控制台输出。我添加了一些评论来强调这条重要的路线

[2014-02-24 00:54:16:290][main][I][APIClient.m:syncFullGetFile:withSyncToken:andUserName:andPassword:andCompletedBlock:][Line: 184] API Client requesting file 'members.json.gz' for session with token 'MToxMzkzMjIxMjM4'. <-- This is the initial request for the file.
[2014-02-24 00:54:17:448][NSOperationQueue 0x14eb6380][?][APIClient.m:URLSession:dataTask:didReceiveData:][Line: 542] Data recieved for file 'members.json.gz'. Data length 16384 <-- Initial response, seems to fire BEFORE the conversion to a download task.
[2014-02-24 00:54:17:487][NSOperationQueue 0x14eb6380][?][APIClient.m:URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:][Line: 521] File 'members.json.gz': Wrote 16384 bytes. Total 16384 of 92447 bytes written. <-- Now the data task is a download task.
[2014-02-24 00:54:17:517][NSOperationQueue 0x14eb6380][?][APIClient.m:URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:][Line: 521] File 'members.json.gz': Wrote 16384 bytes. Total 32768 of 92447 bytes written.
[2014-02-24 00:54:17:533][NSOperationQueue 0x14eb6380][?][APIClient.m:URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:][Line: 521] File 'members.json.gz': Wrote 16384 bytes. Total 49152 of 92447 bytes written.
[2014-02-24 00:54:17:550][NSOperationQueue 0x14eb6380][?][APIClient.m:URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:][Line: 521] File 'members.json.gz': Wrote 16384 bytes. Total 65536 of 92447 bytes written.
[2014-02-24 00:54:17:568][NSOperationQueue 0x14eb6380][?][APIClient.m:URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:][Line: 521] File 'members.json.gz': Wrote 10527 bytes. Total 76063 of 92447 bytes written. <-- Total is short by same 16384 - same number as the initial response.
[2014-02-24 00:54:17:573][NSOperationQueue 0x14eb6380][?][APIClient.m:URLSession:downloadTask:didFinishDownloadingToURL:][Line: 472] Temp file size for 'members.json.gz' is 76063
[2014-02-24 00:54:17:573][NSOperationQueue 0x14eb6380][?][APIClient.m:URLSession:downloadTask:didFinishDownloadingToURL:][Line: 485] File 'members.json.gz' downloaded. Reported 92447 of 92447 bytes received.
[2014-02-24 00:54:17:574][NSOperationQueue 0x14eb6380][?][APIClient.m:URLSession:downloadTask:didFinishDownloadingToURL:][Line: 490] File size after move for 'members.json.gz' is 76063
[2014-02-24 00:54:17:574][NSOperationQueue 0x14eb6380][E][APIClient.m:URLSession:downloadTask:didFinishDownloadingToURL:][Line: 497] Expected size of file 'members.json.gz' is 92447 but size on disk is 76063. Temp file size is 0.

[2014-02-24 00:54:16:290][main][I][APIClient.m:syncFullGetFile:withSyncToken:andUserName:andPassword:andCompletedBlock:[Line:184]API客户端请求文件'members.json.gz'用于带有标记'MToxMzkzMjIxMjM4'的会话 我可以将
NSURLSessionDataTask
转换为
NSURLSessionBackgroundTask
,并且(a)文件大小正确,并且(b)我没有看到对
didReceiveData
的任何调用

我注意到您正在调用这些不同委托方法的
super
实例。这有点奇怪。我想知道您的
super
实现的
didReceiveResponse
是否正在调用完成处理程序本身,导致您调用此完成处理程序两次。值得注意的是,如果我故意调用处理程序两次,一次是使用
NSURLSessionResponseAllow
调用,然后再次使用
NSURLSessionResponseBecomeDownload
调用,我可以重现您的问题


确保只调用一次完成处理程序,并非常小心这些
super
方法中的内容(或者干脆删除对它们的引用)。

。干得好。请注意,AFNetworking文档指出,如果您将
AFURLSessionManager
类划分为子类并覆盖任何特定列出的委托方法,则必须首先调用超级实现。版本2.1.0中的超级实现在默认情况下会调用传递
NSURLSessionResponseAllow
的完成处理程序。我删除了对super的调用,代码运行正常。@EricRisler是的,或者查看AFNetworking源代码,看起来您可以调用
setDataTaskDidReceiveResponseBlock
,并定义如何响应那里的完成处理程序。正确。这是一种不太明显的实现行为,因为开发人员可以使用块或委托,也可以同时使用两者。如果在这种情况下不使用块,就会出现这种“奇怪”的行为。现在我只是不给super打电话,给他添加评论