使用 NSURLSession 时无法调用 didReceiveData

Posted

技术标签:

【中文标题】使用 NSURLSession 时无法调用 didReceiveData【英文标题】:Cannot get didReceiveData to be called when using NSURLSession 【发布时间】:2016-02-06 12:04:52 【问题描述】:

我正在从这个 React Native 模块 react-native-file-upload 中获取一些代码。我正在尝试将其从使用 NSURLConnection(已弃用)更新为使用 NSURLSession 并报告上传进度。

我能够毫不费力地将代码从 NSURLConnection 切换到 NSURLSession,但我正在努力让 didReceiveData 委托被调用。为什么不调用 didReceiveData 委托?

这是我修改后的 FileUpload.m

#import <Foundation/Foundation.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <UIKit/UIKit.h>
#import <Photos/Photos.h>

#import "RCTBridgeModule.h"
#import "RCTLog.h"

@interface FileUpload : NSObject <RCTBridgeModule, NSURLSessionDataDelegate, NSURLSessionDelegate, NSURLSessionTaskDelegate>
@end

@implementation FileUpload

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(upload:(NSDictionary *)obj callback:(RCTResponseSenderBlock)callback)

  NSString *uploadUrl = obj[@"uploadUrl"];
  NSDictionary *headers = obj[@"headers"];
  NSDictionary *fields = obj[@"fields"];
  NSArray *files = obj[@"files"];
  NSString *method = obj[@"method"];

  if ([method isEqualToString:@"POST"] || [method isEqualToString:@"PUT"]) 
   else 
    method = @"POST";
  

  NSURL *url = [NSURL URLWithString:uploadUrl];
  NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
  [req setHTTPMethod:method];

  // set headers
  NSString *formBoundaryString = [self generateBoundaryString];
  NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", formBoundaryString];
  [req setValue:contentType forHTTPHeaderField:@"Content-Type"];
  for (NSString *key in headers) 
    id val = [headers objectForKey:key];
    if ([val respondsToSelector:@selector(stringValue)]) 
      val = [val stringValue];
    
    if (![val isKindOfClass:[NSString class]]) 
      continue;
    
    [req setValue:val forHTTPHeaderField:key];
  


  NSData *formBoundaryData = [[NSString stringWithFormat:@"--%@\r\n", formBoundaryString] dataUsingEncoding:NSUTF8StringEncoding];
  NSMutableData* reqBody = [NSMutableData data];

  // add fields
  for (NSString *key in fields) 
    id val = [fields objectForKey:key];
    if ([val respondsToSelector:@selector(stringValue)]) 
      val = [val stringValue];
    
    if (![val isKindOfClass:[NSString class]]) 
      continue;
    

    [reqBody appendData:formBoundaryData];
    [reqBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
    [reqBody appendData:[val dataUsingEncoding:NSUTF8StringEncoding]];
    [reqBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
  

  // add files
  for (NSDictionary *file in files) 
    NSString *name = file[@"name"];
    NSString *filename = file[@"filename"];
    NSString *filepath = file[@"filepath"];
    NSString *filetype = file[@"filetype"];

    NSData *fileData = nil;

    NSLog(@"filepath: %@", filepath);
    if ([filepath hasPrefix:@"assets-library:"]) 
      NSURL *assetUrl = [[NSURL alloc] initWithString:filepath];

      __block NSData * tempData = nil;

      PHFetchResult *result = [PHAsset fetchAssetsWithALAssetURLs:@[assetUrl] options:nil];
      PHAsset *asset = result.firstObject;

      if (asset)
      
        PHCachingImageManager *imageManager = [[PHCachingImageManager alloc] init];

        // Request an image for the asset from the PHCachingImageManager.
        [imageManager requestImageForAsset:asset targetSize:CGSizeMake(100.0f, 100.0f) contentMode:PHImageContentModeAspectFill options:nil resultHandler:^(UIImage *image, NSDictionary *info)
         
           NSLog(@"IMAGE: %@", image);
           tempData = UIImagePNGRepresentation(image);
         ];
      
      fileData = tempData;
     else if ([filepath hasPrefix:@"data:"] || [filepath hasPrefix:@"file:"]) 
      NSURL *fileUrl = [[NSURL alloc] initWithString:filepath];
      fileData = [NSData dataWithContentsOfURL: fileUrl];
     else 
      fileData = [NSData dataWithContentsOfFile:filepath];
    

    [reqBody appendData:formBoundaryData];
    [reqBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name.length ? name : filename, filename] dataUsingEncoding:NSUTF8StringEncoding]];

    if (filetype) 
      [reqBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n", filetype] dataUsingEncoding:NSUTF8StringEncoding]];
     else 
      [reqBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n", [self mimeTypeForPath:filename]] dataUsingEncoding:NSUTF8StringEncoding]];
    

    [reqBody appendData:[[NSString stringWithFormat:@"Content-Length: %ld\r\n\r\n", (long)[fileData length]] dataUsingEncoding:NSUTF8StringEncoding]];
    [reqBody appendData:fileData];
    [reqBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
  

  // add end boundary
  NSData* end = [[NSString stringWithFormat:@"--%@--\r\n", formBoundaryString] dataUsingEncoding:NSUTF8StringEncoding];
  [reqBody appendData:end];

  // send request
  [req setHTTPBody:reqBody];

  NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
  NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:(id)self delegateQueue:[NSOperationQueue mainQueue]];
  NSURLSessionDataTask *task = [session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
    NSLog(@"response status code: %ld", (long)[httpResponse statusCode]);
    callback(@[[NSNull null], [NSString stringWithFormat:@"response status code: %ld", (long)[httpResponse statusCode]]]);
  ];

  [task resume];


- (NSString *)generateBoundaryString

  NSString *uuid = [[NSUUID UUID] UUIDString];
  return [NSString stringWithFormat:@"----%@", uuid];


- (NSString *)mimeTypeForPath:(NSString *)filepath

  NSString *fileExtension = [filepath pathExtension];
  NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
  NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);

  if (contentType) 
    return contentType;
  
  return @"application/octet-stream";



- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler 
  completionHandler(NSURLSessionResponseAllow);
  NSLog(@"didReceiveResponse");


- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data 
  NSLog(@"didReceiveData");


@end

【问题讨论】:

【参考方案1】:

对于带有 NSURLSession 的委托模式,我认为您应该通过以下方式创建 NSURLSession:

[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                              delegate:self
                         delegateQueue:[NSOperationQueue mainQueue]];

但不是:

[NSURLSession sharedSession];

我还注意到您的 NSURLSessionDataDelegate、NSURLSessionDelegate、NSURLSessionTaskDelegate 与 UIViewController 一起使用;但是,您的 NSURLSession 和委托方法是在 FileUpload.m 中实现的。尝试编辑这一行:

@interface FileUpload : NSObject <RCTBridgeModule>

到:

@interface FileUpload : NSObject <RCTBridgeModule, NSURLSessionDataDelegate, NSURLSessionDelegate, NSURLSessionTaskDelegate>

然后创建没有完成处理程序的 NSURLSessionDataTask:

NSURLSessionDataTask *task = [session dataTaskWithRequest:req];

然后看看有什么不同。

【讨论】:

谢谢,我把代码改成了: NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:(id)self delegateQueue:[NSOperationQueue mainQueue]];但 didReceiveData 委托仍未被调用。有什么想法吗? @TomKrones 欢迎您。我认为 NSURLSessionDataDelegate、NSURLSessionDelegate、NSURLSessionTaskDelegate 应该与 FileUpload.m 一起使用,请参考修改后的答案。 感谢您帮助解决此问题。我更改了@interface,我认为我不需要 FileUpload.h 文件?我尝试在有和没有标题的情况下运行它,但它仍然没有调用委托。还有其他想法吗? @TomKrones 你有没有打印出“didReceiveResponse”,你的状态码是什么?上传过程还需要任何身份验证吗?多部分表单上传是一项困难的工作。如果这是您第一次实现 NSURLSession。我建议你先创建一个空的新项目来练习下载。他们在获得经验后实施上传过程。 @TomKrones 经过一次又一次的审查 :) 我认为 NSURLSessionDataTask 的完成处理程序是不必要的,因为带有完成处理程序的方法是一种方便的方法,它将绕过正常的委托调用。

以上是关于使用 NSURLSession 时无法调用 didReceiveData的主要内容,如果未能解决你的问题,请参考以下文章

NSURLSession:无法与后台传输服务通信

在后台使用 NSURLSession 逐个下载 100 个文件的列表

NSURLSession 重启请求

NSURLSession dataTaskWithRequest 未被调用

尝试使用 NSURLSession 加载网页

具有无效简历数据的 NSURLSession