【问题标题】:Cannot get didReceiveData to be called when using NSURLSession使用 NSURLSession 时无法调用 didReceiveData
【发布时间】: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

【问题讨论】:

    标签: ios objective-c react-native


    【解决方案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 的完成处理程序是不必要的,因为带有完成处理程序的方法是一种方便的方法,它将绕过正常的委托调用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-07
    • 1970-01-01
    • 2016-07-15
    相关资源
    最近更新 更多