NSURLConnection与NSURLSession简单使用

从NSURLConnection到NSURLSession,iOS网络请求技术经历了重大变革。NSURLSession以其更强大的功能和灵活性,成为iOS9.0及以后版本中获取远程数据的首选。本文详细解析了NSURLSession的配置、任务类型及其在上传、下载中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

iOS9.0之前,网络请求都是通过NSURLConnection来获取远程服务器数据,iOS9.0之后苹果已弃用了NSURLConnection,使用了NSURLSession来替代之,功能强大的NSURLSession让我们获取数据更加得心应手!

 

NSURLConnection

  • 发送请求方式
    NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pic1.nipic.com/2008-08-14/2008814183939909_2.jpg"]];

// 异步请求方式
    // 第一种
    [NSURLConnection connectionWithRequest:request delegate:self];
    // 第二种
    NSURLConnection* connect = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    [connect start];
    // 第三种 startImmediately设置为YES则不用手动设置start方法,会自动执行请求
    NSURLConnection* connect2 = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
    [connect2 start];
    // 第四种
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        
    }];

// 同步请求方式
    NSURLResponse* response = nil;
    NSError* error = nil;
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
  • GET请求

通过将请求地址URL生成NSURLRequest对象,进而通过NSURLConnection方法实现请求!NSURLRequest不允许设置额外的参数!!

  • POST请求

不仅可以通过将请求地址URL生成NSMutableURLRequest对象实现网络请求!还可以设置一些额外的参数!!

// 设置请求方法、请求体、请求超时时间、请求首部参数等...
    NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://pic1.nipic.com/2008-08-14/2008814183939909_2.jpg"]];
    request.HTTPMethod = @"POST";
    request.timeoutInterval = 60.0;
    NSString* bodyString = [NSString stringWithFormat:@"name=%@&age=%@", @"mjz", @"100"];
    request.HTTPBody = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
    [request setValue:@"iOS 11.0" forHTTPHeaderField:@"User-Agent"];
  • Delegate
// 重定向URL
- (nullable NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(nullable NSURLResponse *)response;
// 接收请求返回数据,可获取到请求数据的一些信息,例如:response.expectedContentLength
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
// 服务器响应的数据
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
// 设置加载进度
- (void)connection:(NSURLConnection *)connection   didSendBodyData:(NSInteger)bytesWritten
                                                 totalBytesWritten:(NSInteger)totalBytesWritten
                                         totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite;
// 缓存响应
- (nullable NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse;
// 获取数据成功
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;

注意细节:

  1. 苹果要求我们更新UI在主线程上执行,所以在请求数据成功后涉及到更新UI务必在主线程上;虽然在主线程上请求,代理回调也是在主线程上,但是可能用户是在子线程上开启的请求,此时就不一定在主线程上了;
  2. 在子线程上开启请求时,会将NSURLConnection对象加入到当前对应的RunLoop中,当我们在子线程中进行网络请求,默认子线程的RunLoop不会自动创建,NSURLConnection对象会被释放,因此我们需要开启子线程中的RunLoop,保证NSURLConnection对象不会被释放;

 

NSURLSession

  • NSURLSession

NSURLSession是用于网络数据请求的应用会话,可通过会话创建下载、上传、获取数据等任务形式,向服务器端请求数据!

NSURLSession分为几种:

  • 单例sharedSession :  有一定的局限性,可快速生成NSURLSession对象开启task任务;
  • 自定义session :  通过自定义配置文件NSURLSessionConfiguration,并设置代理,大多数情况下被使用;
  • 后台session :  也是通过自定义session,只不过主要用于后台上传或下载任务;

 

  • NSURLSessionConfiguration

NSURLSessionConfiguration用于对会话设置数据是否进行缓存或缓存策略,每端口的最大并发HTTP请求数目,以及是否允许蜂窝网络, 请求超时时间,cookies或证书存储策略等...

NSURLSessionConfiguration分为几种:

  • defaultSessionConfiguration :默认标准配置
  • ephemeralSessionConfiguration : 返回一个预设配置,没有磁盘存储的缓存,Cookie或证书。可以用来实现像"无痕浏览"功能的功能;
  • backgroundSessionConfiguration : 创建一个后台会话,甚至可以在应用程序挂起,退出,崩溃的情况下运行上传和下载任务;

 

  • 任务类型

1、NSURLSessionDataTask

// 第一种:没有设置代理,通过回调获取数据
    NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pic1.nipic.com/2008-08-14/2008814183939909_2.jpg"]];
    NSURLSessionDataTask* task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
    }];
    [task resume];

// 第二种:设置代理请求数据
    NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pic1.nipic.com/2008-08-14/2008814183939909_2.jpg"]];
    NSURLSessionConfiguration* configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession* urlSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue currentQueue]];
    NSURLSessionDataTask* task = [urlSession dataTaskWithRequest:request];
    [task resume];

// 请求完成时都会调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    // 将数据Data转为NSDictionary
    NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:self.mu_data options:NSJSONReadingMutableLeaves error:nil];
    if (self.mu_data) { // 在主线程更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = [UIImage imageWithData:self.mu_data];
        });
    }
}

// 允许服务器请求并接收服务器数据:NSURLSessionResponseAllow
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    if (completionHandler) {
        completionHandler(NSURLSessionResponseAllow);
    }
}

// 服务器返回数据拼接
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data {
    // 通过mu_data进行数据拼接
    if (!self.mu_data) {
        self.mu_data = [NSMutableData data];
    }
    [self.mu_data appendData:data];
}
  1. 当开启一个回调的data task,即使设置了代理也不会执行代理方法;若没有设置回调而是设置了代理,则请求过程会调用代理方法;
  2. didCompleteWithError这个方法会在获取数据、下载、上传等任务请求完成时都会进行回调;
  3. 在服务器传输数据给客户端期间, 代理会周期性地收到URLSession:​data​Task:​did​Receive​Data:​数据回调;
  4. 对于一个data task来说, session会调用代理的URLSession:​data​Task:​did​Receive​Response:​completion​Handler:​方法,决定是否允许继续接收服务器数据(NSURLSessionResponseAllow),还是要将一个data dask转换成download task(NSURLSessionResponseBecomeDownload),亦或者是取消(NSURLSessionResponseCancel);

2、NSURLSessionUploadTask

Content-type:multipart/formdata, boundary=boundary

--boundary
Content-disposition: form-data; name="name"

mjz

--boundary
Content-disposition: form-data; name: "pic", filename: "mjz.jpg"
Content-type: image/jpg

<mjz.jpg>

--boundary--

/**************************以上是上传文件时需遵守的格式****************************/

// 上传视频内容
- (void)uploadVideo {
    NSMutableDictionary* param = [NSMutableDictionary dictionary];
    param[@"hphm"] = @"粤B12345";
    param[@"channelid"] = @"4";
    NSMutableString* string = [NSMutableString string];
    [string appendString:@"{\"file1\":{\"time\":\""];
    [string appendString:[NSString stringWithFormat:@"%@", @"2019-01-01 00:00:00"]];
    [string appendString:@"\",\"address\":\""];
    [string appendString:[NSString stringWithFormat:@"%@", @"广东省广州市白云区"]];
    [string appendString:@"\",\"lng\":\""];
    [string appendString:[NSString stringWithFormat:@"%f", 113.3736180000]];
    [string appendString:@"\",\"lat\":\""];
    [string appendString:[NSString stringWithFormat:@"%f", 23.0982070000]];
    [string appendString:@"\",\"seq\":\"1\"}}"];
    param[@"mediainfo"] = string;
    
    NSURL* url = [NSURL URLWithString:@"http://xxxxxxxxx/upload/uploadMediaFile"];
    // test.mp4是放在工程下的视频
    NSString* filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"mp4"];
    NSMutableURLRequest* mu_req = [self requestWithURL:url andFilenName:@"file1" andLocalFilePath:filePath params:param];
    NSURLSessionDataTask* task = [[NSURLSession sharedSession] uploadTaskWithStreamedRequest:mu_req];
    [task resume];
}

- (NSMutableURLRequest *)requestWithURL:(NSURL *)url andFilenName:(NSString *)fileName andLocalFilePath:(NSString *)localFilePath params:(NSDictionary *)param{
    
    //post请求
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:1 timeoutInterval:2.0f];
    request.HTTPMethod = @"POST";//设置请求方法是POST,不写默认为GET
    
    //拼接请求体数据(1-7步)
    NSMutableData *requestMutableData = [NSMutableData data];
    
    NSString* boundary = [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];

    //1.\r\n--Boundary+72D4CD655314C423\r\n   // 分割符,以“--”开头,后面的字随便写,只要不写中文即可
    
    NSMutableString *myString = [NSMutableString string];
    
    // 拼接字段参数
    NSArray *keys = [param allKeys];
    for (NSString *key in keys) {
        [myString appendString:[NSString stringWithFormat:@"\r\n--%@\r\n",boundary]];
        [myString appendFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key];
        [myString appendFormat:@"%@\r\n",[param objectForKey:key]];
    }
    
    [myString appendString:[NSString stringWithFormat:@"\r\n--%@\r\n",boundary]];

    //2. Content-Disposition: form-data; name="file1"; filename="test.mp4"\r\n
    [myString appendString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"file1\"; filename=\"%@\"\r\n",fileName]];
    //3. Content-Type:video/mp4 \r\n  // 视频为mp4
    [myString appendString:[NSString stringWithFormat:@"Content-Type:video/mp4\r\n"]];
    
    //4. Content-Transfer-Encoding: binary\r\n\r\n  // 编码方式
    [myString appendString:@"Content-Transfer-Encoding: binary\r\n\r\n"];
    
    //5. 转换成为NSData类型后再拼接视频数据
    [requestMutableData appendData:[myString dataUsingEncoding:NSUTF8StringEncoding]];
    
    //6.文件数据部分
    NSURL *filePathUrl = [NSURL fileURLWithPath:localFilePath];
    [requestMutableData appendData:[NSData dataWithContentsOfURL:filePathUrl]];
    
    //7. \r\n--Boundary+72D4CD655314C423--\r\n  // 分隔符后面以"--"结尾,表明结束
    [requestMutableData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
   
    //设置请求体
    request.HTTPBody = requestMutableData;
    
    //设置请求头
    NSString *headString = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
    [request setValue:headString forHTTPHeaderField:@"Content-Type"];
    
    return request;
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
    NSLog(@"上传进度:%f", 1.0 * totalBytesSent / totalBytesExpectedToSend);
}
  1.  上传数据需遵循上面的格式才能上传成功;
  2. 上传数据去服务器期间, 代理会周期性收到URLSession:​task:​did​Send​Body​Data:​total​Bytes​Sent:​total​Bytes​Expected​To​Send:回调,可获取当前上传进度;

3、NSURLSessionDownloadTask

- (void)viewDidLoad {
    [super viewDidLoad];

    NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pic1.nipic.com/2008-08-14/2008814183939909_2.jpg"]];
    NSURLSessionConfiguration* configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession* urlSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue currentQueue]];
    NSURLSessionDownloadTask* task = [urlSession downloadTaskWithRequest:request];
    [task resume];
}


// 下载完成时调用
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
    
    // 将数据进行存储
    NSString* filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"mjz.jpg"];
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePath] error:nil];
    
}

// 下载过程中回调,获取下载进度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    CGFloat progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
    NSLog(@"下载进度:%f", progress);
}
  1. 通过didWriteData:方法获取请求数据下载过程;
  2. didFinishDownloadingToURL:下载完成时,会生成一个临时文件路径location,我们需要对临时文件进行持久性存储;

文件断点下载

1、NSURLSessionDownloadTask实现

@interface ViewController ()<NSURLSessionDelegate, NSURLSessionDownloadDelegate>


@property (strong, nonatomic) NSURLSession* session;
@property (strong, nonatomic) NSURLSessionDownloadTask* downloadTask;

@property (weak, nonatomic) IBOutlet UIProgressView *progressView;

@property (assign, nonatomic) BOOL isResume;

@property (strong, nonatomic) NSData* resumeData;


@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSURLSessionConfiguration* configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    self.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:@"http://XXXXX/02.qsv"]];
    [self.downloadTask resume];
}

- (void)downloadData {
    if (self.isResume) { // 继续下载
        self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
    } else { // 暂停下载
        self.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:@"http://XXXXXX/02.qsv"]];
    }
    
    [self.downloadTask resume];
}

- (IBAction)suspendTask:(id)sender {
    
    self.isResume = NO;
    
    __block typeof(self) weakSelf = self;
    [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        weakSelf.resumeData = resumeData;
        weakSelf.downloadTask = nil;
    }];
    
}

- (IBAction)resumeTask:(id)sender {
    
    self.isResume = YES;
    
    [self downloadData];
    
}

- (IBAction)cancelTask:(id)sender {
    
    self.isResume = NO;
    [self.downloadTask cancel];
    
    self.progressView.progress = 0.0;
    
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location  {
    
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    
    CGFloat progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
    NSLog(@"下载进度:%f", progress);
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.progressView.progress = progress;
    });
    
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes {
    NSLog(@"继续下载调用方法");
}

原理:

  • 当用户暂停下载时, 调用cancel​By​Producing​Resume​Data: 将当前已经下载好的resumeData数据保存;
  • 如果用户想要恢复下载, 把刚刚的resumeData以参数的形式传给download​Task​With​Resume​Data:​ 方法创建新的task继续下载;
  • 当用户退出程序时,由于下载的数据存储于临时文件夹中,可能会被销毁,所以实现断点下载有时候这种方式不太靠谱;

2、NSURLSessionDataTask实现

#import "ViewController.h"

#define saveFileString [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"mjz.mov"]
#define kDownLoadLength @"kDownLoadLength"
#define kCurrentLength @"kCurrentLength"

@interface ViewController ()<NSURLSessionDelegate, NSURLSessionDataDelegate>


@property (strong, nonatomic) NSURLSession* session;
@property (strong, nonatomic) NSURLSessionDataTask* dataTask;
@property (strong, nonatomic) NSOutputStream* outputStream;

@property (weak, nonatomic) IBOutlet UIProgressView *progressView;

@property (assign, nonatomic) BOOL isResume; // 标记是否恢复下载

@property (assign, nonatomic) NSInteger currentLength; // 当前已下载大小

@property (assign, nonatomic) NSInteger totalSizeLength; // 总共需下载长度

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSURLSessionConfiguration* configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    
//  是否已经下载过数据
    NSNumber* currentLengthNum = [[NSUserDefaults standardUserDefaults] objectForKey:kCurrentLength];
    if ([currentLengthNum integerValue] == 0) {
        self.dataTask = [self.session dataTaskWithURL:[NSURL URLWithString:@"http://10.1.2.9/CxyAPIServerWithAuth/02.qsv"]];
        
    } else {
        self.currentLength = [currentLengthNum integerValue];
        NSMutableURLRequest* mu_req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://10.1.2.9/CxyAPIServerWithAuth/02.qsv"]];

        // 重新下载时主要设置的重点代码
        NSString *range =[NSString stringWithFormat:@"bytes=%zd-",self.currentLength];
        [mu_req setValue:range forHTTPHeaderField:@"Range"];
        self.dataTask = [self.session dataTaskWithRequest:mu_req];
    }
    
    [self.dataTask resume];
}

- (IBAction)suspendTask:(id)sender {
    
    self.isResume = NO;
    
    [self.dataTask suspend];
}

- (IBAction)resumeTask:(id)sender {
    
    self.isResume = YES;
    
    [self.dataTask resume];
}

- (IBAction)cancelTask:(id)sender {
    
    self.isResume = NO;
    [self.dataTask cancel];
    self.dataTask = nil;
    [self.outputStream close];
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    
    // 总大小,当前response.expectedContentLength是剩余还未下载的数据
    NSInteger totalLength = response.expectedContentLength + self.currentLength;
    self.totalSizeLength = totalLength;
    if (self.currentLength==0) {
        [[NSUserDefaults standardUserDefaults] setObject:@(totalLength) forKey:kDownLoadLength];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
    
    self.outputStream = [NSOutputStream outputStreamToFileAtPath:saveFileString append:YES];
    [self.outputStream open];
    
    completionHandler(NSURLSessionResponseAllow);
    
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data {
    
    CGFloat progress = 1.0 * self.currentLength / self.totalSizeLength;
    NSLog(@"下载进度:%f", progress);
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.progressView.progress = progress;
    });
    
    self.currentLength += data.length;
    [self.outputStream write:data.bytes maxLength:data.length];
    
    // 设置当前已下载的大小
    [[NSUserDefaults standardUserDefaults] setObject:@(self.currentLength) forKey:kCurrentLength];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

@end

原理:(代码很长,原理很简单)

  • 通过didReceiveData:代理方法保存当前文件下载的进度currentLength;
  • 恢复下载时,通过设置请求首部的@"Range"字段,假设当前已下载currentLength为100,那么请求首部将从100开始获取还未下载的数据,我们只需将获取的新数据拼接在之前已下载的数据后面即可;

总结

  1. NSURLSession请求回调的时候在子线程执行,若涉及到更新UI,我们需要切换到主线程执行;
  2. NSURLConnection在子线程不会开启请求任务,因为子线程的Runloop默认不执行;
  3. NSURLSession还有很多强大的地方本文还未实现,以后还会进行更多的补充;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值