NSURLSession
简介
- 用于替代
NSURLConnection
- 支持后台运行的网络任务
- 暂停、停止、重启网络任务,不再需要
NSOperation
封装 - 请求可以使用同样的
配置容器
- 不同的
session
可以使用不同的私有存储 -
block
和代理
可以同时起作用 - 直接从文件系统上传、下载
结构图
- 为了方便程序员使用,苹果提供了一个全局
session
- 所有的
任务(Task)
都是由Session
发起的 - 所有的任务默认是
挂起
的,需要Resume
- 使用
URLSession
后,NSURLRequest
通常只用于指定HTTP
请求方法,而其他的额外信息都可以通过NSURLSessionConfiguration
设置
常见代码
代码初体验
- (void)sessionDemo1 {
// 1. url
NSURL *url = [NSURL URLWithString:@"http://localhost/demo.json"];
// 2. session
NSURLSession *session = [NSURLSession sharedSession];
// 3. 数据任务
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@ %@ %@", result, response, [NSThread currentThread]);
}];
// 4. 继续任务
[task resume];
}
小结
- 为了方便程序员使用,苹果提供了一个全局
session
[NSURLSession sharedSession]
- 所有的
任务(Task)
都是由Session
发起的 - 所有的任务默认是
挂起
的,需要Resume
-
session
的回调是异步的
简化代码
- (void)sessionDemo2 {
// 1. url
NSURL *url = [NSURL URLWithString:@"http://localhost/demo.json"];
// 2. 数据任务
[self taskWithURL:url];
}
- (void)taskWithURL:(NSURL *)url {
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@ %@ %@", result, response, [NSThread currentThread]);
}] resume];
}
-
数据任务
适合于小的数据访问,包括:JSON
XML
PList
HTML
图像
下载进度跟进
要跟进下载进度,不能使用全局 session
- 代理模式是
一对一
的
定义全局 session
// MARK: - 懒加载
- (NSURLSession *)session {
if (_session == nil) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return _session;
}
实现代理方法
/// 下载完成,必须实现
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSLog(@"%@ %@", location, [NSThread currentThread]);
}
/// 进度方法,iOS 7.0 必须实现
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;
NSLog(@"%@ %f", [NSThread currentThread], progress);
}
/// 续传方法,iOS 7.0 必须实现
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
NSLog(@"%s", __FUNCTION__);
}
调整下载代码
- (void)download {
NSLog(@"开始");
// 1. url
NSURL *url = [NSURL URLWithString:@"http://localhost/321.zip"];
// 2. 下载
[[self.session downloadTaskWithURL:url] resume];
}
代理的工作对列
nil
- 异步回调 和 实例化一个 操作队列的效果是一样的!-
[[NSOperationQueue alloc] init]
- 异步回调- 注意:下载本身的线程"只有一条"
- 代理回调可以在"多个线程"回调!
-
[NSOperationQueue mainQueue]
- 主队列回调- 下载本身是异步执行的,这一点和
NSURLConnection
是一样的 - 指定代理执行的队列,不会影响到下载本身的执行
-
NSURLSession
即使在主线程回调也不会造成阻塞
- 下载本身是异步执行的,这一点和
-
如何选择队列
- 网络访问结束后,如果不需要做复杂的操作,可以指定主队列,这样不用考虑线程间通讯
下载错误监听
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSLog(@"%@ %@", task, error);
}
NSURLSessionDownloadDelegate
继承自NSURLSessionTaskDelegate
因此可以直接实现NSURLSessionTaskDelegate
的代理方法
断点续传
暂停继续
- 定义属性
/// 下载任务
@property (nonatomic, strong) NSURLSessionDownloadTask *task;
/// 续传数据
@property (nonatomic, strong) NSData *resumeData;
- 代码实现
- (IBAction)start {
if (self.task != nil) {
NSLog(@"已经存在下载任务 %@", self.task);
if (self.task.state == NSURLSessionTaskStateSuspended) {
[self.task resume];
} else {
[self.task suspend];
}
return;
}
NSURL *url = [NSURL URLWithString:@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.2.dmg"];
NSLog(@"start");
// 直接建立下载任务并且启动,后续的进度监听又代理负责
self.task = [self.session downloadTaskWithURL:url];
[self.task resume];
}
下载结束处理
按照 suggestedFilename
保存文件
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSLog(@"%@", location);
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
[[NSFileManager defaultManager] copyItemAtPath:location.path toPath:filePath error:NULL];
}
使用 MD5
URL 保存文件
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSLog(@"%@", location);
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:downloadTask.response.URL.absoluteString.md5String];
NSLog(@"%@", filePath.fileMD5Hash);
[[NSFileManager defaultManager] copyItemAtPath:location.path toPath:filePath error:NULL];
}
基本功能实现
- 定义属性
/// 下载任务
@property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask;
/// 续传数据
@property (nonatomic, strong) NSData *resumeData;
- 开始下载
- (IBAction)start {
if (self.downloadTask != nil) {
NSLog(@"正在玩命下载中...");
return;
}
NSURL *url = [NSURL URLWithString:@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.2.dmg"];
self.downloadTask = [self.session downloadTaskWithURL:url];
[self.downloadTask resume];
}
- 暂停
- (IBAction)pause {
NSLog(@"暂停");
[self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
NSLog(@"pause %tu", resumeData.length);
self.resumeData = resumeData;
self.downloadTask = nil;
}];
}
- 继续
- (IBAction)resume {
if (self.resumeData == nil) {
NSLog(@"没有续传数据");
return;
}
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
self.resumeData = nil;
[self.downloadTask resume];
}
方法整合
- (IBAction)start {
if (self.task != nil || self.resumeData != nil) {
NSLog(@"已经存在下载任务 %@", self.task);
if (self.resumeData != nil) {
self.task = [self.session downloadTaskWithResumeData:self.resumeData];
[self.task resume];
self.resumeData = nil;
} else {
NSLog(@"取消");
[self.task cancelByProducingResumeData:^(NSData *resumeData) {
NSLog(@"%tu", resumeData.length);
self.resumeData = resumeData;
self.task = nil;
}];
}
return;
}
NSURL *url = [NSURL URLWithString:@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.2.dmg"];
NSLog(@"start");
// 直接建立下载任务并且启动,后续的进度监听又代理负责
self.task = [self.session downloadTaskWithURL:url];
[self.task resume];
}
续传数据
- 将续传数据写入磁盘,会发现是一个 plist
- 优化续传代码
/// 返回下载文件 URL 的续传数据路径
///
/// @param url 下载文件的 URL
///
/// @return 续传数据路径
- (NSString *)resumePath:(NSURL *)url {
NSString *resumePath = [NSTemporaryDirectory() stringByAppendingPathComponent:url.absoluteString.md5String];
return [resumePath stringByAppendingPathExtension:@"~resume"];
}
/// 加载 URL 对应的续传数据
///
/// @param url 下载文件的 url
///
/// @return 如果存在返回上次续传数据
- (NSData *)loadResumeData:(NSURL *)url {
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:[self resumePath:url]];
// 提取缓存路径名
NSString *tmpFileName = dict[@"NSURLSessionResumeInfoLocalPath"];
// 重新生成缓存路径名
NSString *fileName = [NSTemporaryDirectory() stringByAppendingPathComponent:[tmpFileName lastPathComponent]];
dict[@"NSURLSessionResumeInfoLocalPath"] = fileName;
// 删除续传文件
[[NSFileManager defaultManager] removeItemAtPath:[self resumePath:url] error:NULL];
// PList 序列化
return [NSPropertyListSerialization dataWithPropertyList:dict format:NSPropertyListXMLFormat_v1_0 options:0 error:NULL];
}
下载管理器
单例实现
-
.h
定义全局访问方法
+ (instancetype)sharedDownloadManager;
-
.m
实现
@interface DownloadManager() <NSURLSessionDownloadDelegate>
@property (nonatomic, strong) NSURLSession *session;
@end
@implementation DownloadManager
+ (instancetype)sharedDownloadManager {
static DownloadManager *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
// 实例化网络会话
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
instance.session = [NSURLSession sessionWithConfiguration:config delegate:instance delegateQueue:nil];
});
return instance;
}
@end
实现代理方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
#warning 错误回调
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
#warning 完成回调
NSLog(@"%@", location);
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;
NSLog(@"%f %@", progress, [NSThread currentThread]);
#warning 进度回调
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
NSLog(@"%s", __FUNCTION__);
}
下载方法实现
- 定义接口方法
/// 下载指定 URL
///
/// @param url URL
/// @param progress 进度回调
/// @param finished 完成回调
- (void)downloadWithURL:(NSURL *)url progress:(void (^)(float progress))progress finished:(void (^)(NSURL *location, NSError *error))finished;
- 下载方法实现
- (void)downloadWithURL:(NSURL *)url progress:(void (^)(float))progress finished:(void (^)(NSURL *, NSError *))finished {
// 1. 创建下载任务
NSURLSessionDownloadTask *task = [self.session downloadTaskWithURL:url];
// 2. 启动下载任务
[task resume];
}
替换下载方法
- (IBAction)start {
// if (self.downloadTask != nil) {
// NSLog(@"正在玩命下载中...");
// return;
// } else if (self.resumeData != nil) {
// NSLog(@"继续下载");
//
// [self resume];
// return;
// }
NSURL *url = [NSURL URLWithString:@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.2.dmg"];
[[DownloadManager sharedDownloadManager] downloadWithURL:url progress:^(float progress) {
NSLog(@"%f", progress);
} finished:^(NSURL *location, NSError *error) {
NSLog(@"%@ %@", location, error);
}];
}
问题:如何识别
下载任务
以及与其绑定的回调代码
- 解决办法——自定义
下载任务回调
类
@interface SessionTaskCallBack : NSObject
/// 进度回调,可选
@property (nonatomic, copy) void (^progressBlock)(float progress);
/// 完成回调
@property (nonatomic, copy) void (^finishedBlock)(NSURL *location, NSError *error);
/// 续传数据
@property (nonatomic, strong) NSData *resumeData;
@end
@implementation SessionTaskCallBack
@end
- 定义缓冲池
/// 下载任务缓冲池
@property (nonatomic, strong) NSMutableDictionary *downloadTaskCache;
/// 下载任务回调缓冲池
@property (nonatomic, strong) NSMutableDictionary *downloadTaskCallBackCache;
- 实现缓冲池
- (NSMutableDictionary *)downloadTaskCache {
if (_downloadTaskCache == nil) {
_downloadTaskCache = [NSMutableDictionary dictionary];
}
return _downloadTaskCache;
}
- (NSMutableDictionary *)downloadTaskCallBackCache {
if (_downloadTaskCallBackCache == nil) {
_downloadTaskCallBackCache = [NSMutableDictionary dictionary];
}
return _downloadTaskCallBackCache;
}
- 修改下载方法,添加缓冲池
- (void)downloadWithURL:(NSURL *)url progress:(void (^)(float))progress finished:(void (^)(NSURL *, NSError *))finished {
// 0. 判断任务是否存在
if (self.downloadTaskCache[url] != nil) {
NSLog(@"下载任务已经存在");
return;
}
// 1. 创建下载任务
NSURLSessionDownloadTask *task = [self.session downloadTaskWithURL:url];
// 2. 将下载任务添加到缓冲池
[self.downloadTaskCache setObject:task forKey:url];
// 3. 创建下载任务回调类
SessionTaskCallBack *callBack = [[SessionTaskCallBack alloc] init];
// 2.1 记录进度回调
callBack.progressBlock = progress;
// 2.2 记录完成回调
callBack.finishedBlock = finished;
// 将任务回调添加到缓冲池
[self.downloadTaskCallBackCache setObject:callBack forKey:task];
// 4. 启动下载任务
[task resume];
}
- 完成回调
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
// 从任务回调缓冲池获取回调对象
SessionTaskCallBack *callBack = self.downloadTaskCallBackCache[downloadTask];
if (callBack == nil) {
NSLog(@"没有任务");
return;
}
if (callBack.finishedBlock != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
callBack.finishedBlock(location, nil);
});
}
}
- 错误回调
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// 从任务回调缓冲池获取回调对象
SessionTaskCallBack *callBack = self.downloadTaskCallBackCache[task];
if (callBack == nil) {
NSLog(@"没有任务");
return;
}
if (callBack.finishedBlock != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error != nil) {
callBack.finishedBlock(nil, error);
}
// 删除缓存
[self.downloadTaskCallBackCache removeObjectForKey:task];
[self.downloadTaskCache enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (obj == task) {
NSLog(@"%@", self.downloadTaskCache);
[self.downloadTaskCache removeObjectForKey:key];
*stop = YES;
}
}];
});
}
}
- 进度回调
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
// 从任务回调缓冲池获取回调对象
SessionTaskCallBack *callBack = self.downloadTaskCallBackCache[downloadTask];
if (callBack == nil) {
NSLog(@"没有任务");
return;
}
if (callBack.progressBlock != nil) {
float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;
callBack.progressBlock(progress);
}
}
暂停&继续
- (void)pause:(NSURL *)url {
NSURLSessionTask *task = self.downloadTaskCache[url];
if (task.state == NSURLSessionTaskStateRunning) {
[task suspend];
}
}
- (void)resume:(NSURL *)url {
NSURLSessionTask *task = self.downloadTaskCache[url];
if (task.state == NSURLSessionTaskStateSuspended) {
[task resume];
}
}
下载 & 解压缩
基本代码
- (void)download {
NSLog(@"开始");
// 1. url
NSURL *url = [NSURL URLWithString:@"http://localhost/321.zip"];
// 2. 下载
[[[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSLog(@"%@ %@", location, [NSThread currentThread]);
}] resume];
}
运行测试:块代码回调结束后,下载的文件会被删除
原因分析:
一般从网络上下载文件,
zip
压缩包会比较多-
如果是 zip 文件,下载完成后需要:
- 下载压缩包
- 解压缩(异步执行)到目标文件夹
- 删除压缩包
下载任务的特点可以让程序员只关心解压缩的工作
解压缩
- 添加第三方框架
SSZipArchive
- 添加动态库
libz.dylib
- 解压缩
// 目录
NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
[SSZipArchive unzipFileAtPath:location.path toDestination:cacheDir];
提示:解压缩只需要指定目标目录即可,因为压缩包中可能会包含多个文件
WebDav演练
WebDav 配置
- WebDav 服务器是基于 Apache 的,可以当作文件服务器使用
代码实现
session 懒加载
// MARK: 懒加载
- (NSURLSession *)session {
if (_session == nil) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return _session;
}
授权字符串
/**
授权字符串格式
BASIC (用户名:密码).base64
*/
- (NSString *)authString {
NSString *str = @"admin:123456";
return [@"BASIC " stringByAppendingString:[self base64Encode:str]];
}
/// BASE 64 编码
- (NSString *)base64Encode:(NSString *)str {
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
return [data base64EncodedStringWithOptions:0];
}
WebDav 上传
// MARK: 上传
- (void)webdavUpload {
// 1. url - 保存到服务器上的路径
NSURL *url = [NSURL URLWithString:@"http://localhost/uploads/123.png"];
// 2. request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"PUT";
[request setValue:[self authString] forHTTPHeaderField:@"Authorization"];
// 3. uploadtask
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"001.png" withExtension:nil];
[[self.session uploadTaskWithRequest:request fromFile:fileURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@ %@", result, response);
}] resume];
}
WebDav 删除
// MARK: 删除
- (void)webdavDelete {
// 1. url - 保存到服务器上的路径
NSURL *url = [NSURL URLWithString:@"http://localhost/uploads/123.png"];
// 2. request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"DELETE";
[request setValue:[self authString] forHTTPHeaderField:@"Authorization"];
// 3. 数据任务
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@ - %@", result, response);
}] resume];
}
WebDav GET & HEAD
GET
&HEAD
不会修改服务器,因此不需要授权
/// MARK:
- (void)webdavGet {
// 1. url - 保存到服务器上的路径
NSURL *url = [NSURL URLWithString:@"http://localhost/uploads/123.png"];
// 2. 下载任务
[[self.session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSData *data = [NSData dataWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
self.iconImage.image = [UIImage imageWithData:data];
});
}] resume];
}
上传进度
/// MARK: - NSURLSessionTaskDelegate
/// 上传进度
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
float progress = (float)totalBytesSent / totalBytesExpectedToSend;
NSLog(@"%f %@", progress, [NSThread currentThread]);
}
Configuration
NSURLSessionConfiguration
用于设置全局的网络会话属性,包括:身份验证,超时时长,缓存策略,Cookie 等
构造方法
-
NSURLSessionConfiguration
有三个类构造方法,是为不同的用例设计的defaultSessionConfiguration
返回标准配置,具有共享NSHTTPCookieStorage
,NSURLCache
和NSURLCredentialStorage
ephemeralSessionConfiguration
返回一个预设配置,没有持久性存储的缓存,Cookie或证书。这对于实现像秘密浏览
功能的功能来说,是很理想的backgroundSessionConfiguration
,独特之处在于,会创建一个后台会话。后台会话不同于常规的,普通的会话,它甚至可以在应用程序挂起,退出,崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程提供上下文
常用 HTTPAdditionalHeaders
configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json",
@"Accept-Language": @"en",
@"Authorization": authString,
@"User-Agent": userAgentString};
除了以上字段外,
range
用于断点续传
常用属性
属性 | 描述 |
---|---|
HTTPAdditionalHeaders | HTTP 头字段 |
timeoutIntervalForRequest | 超时时长 |
timeoutIntervalForResource | 整个资源请求时长 |
requestCachePolicy | 缓存策略 |
allowsCellularAccess | 允许蜂窝访问 |
HTTPMaximumConnectionsPerHost | 对于一个host的最大并发连接数,默认数值是 4,MAC 下的默认数值是 6 |
NSURLSession注意事项
一旦指定了 session 的代理,session会对代理进行强引用,如果不主动取消 session,会造成内存泄漏!
解决方案
- 解决方法1:在任务完成后取消 session
- 缺点:session一旦被取消就无法再次使用
- 解决方法2:在视图将要消失的时候取消 session
- 优点:只需要一个全局的session统一管理
- 解决方法3:建立一个网络会话管理类,单独处理所有的网络请求,
POST 上传
formData 和 upload 方法
以下代码从
POST 增强
复制
#define boundary @"itcast-upload"
- (void)uploadFile:(NSString *)fieldName dataDict:(NSDictionary *)dataDict params:(NSDictionary *)params {
// 1. url - 负责上传文件的脚本
NSURL *url = [NSURL URLWithString:@"http://localhost/post/upload-m.php"];
// 2. request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
NSString *typeValue = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
[request setValue:typeValue forHTTPHeaderField:@"Content-Type"];
NSData *data = [self formData:fieldName dataDict:dataDict params:params];
// 3. 上传
[[[NSURLSession sharedSession] uploadTaskWithRequest:request fromData:data completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"%@ %@", response, [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]);
}] resume];
}
/// 生成 formData 二进制数据
///
/// @param fieldName 服务器字段名
/// @param dataDict 上传文件数据字典 "保存在服务器文件名": 二进制数据
/// @param params 提交参数字典
///
/// @return formData 二进制数据
- (NSData *)formData:(NSString *)fieldName dataDict:(NSDictionary *)dataDict params:(NSDictionary *)params {
NSMutableData *dataM = [NSMutableData data];
// 1. 生成文件数据
[dataDict enumerateKeysAndObjectsUsingBlock:^(NSString *fileName, NSData *fileData, BOOL *stop) {
NSMutableString *strM = [NSMutableString string];
[strM appendFormat:@"--%@\r\n", boundary];
[strM appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName, fileName];
[strM appendString:@"Content-Type: application/octet-stream\r\n\r\n"];
[dataM appendData:[strM dataUsingEncoding:NSUTF8StringEncoding]];
[dataM appendData:fileData];
[dataM appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}];
// 2. 生成参数数据
[params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
NSMutableString *strM = [NSMutableString string];
[strM appendFormat:@"--%@\r\n", boundary];
[strM appendFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key];
[strM appendString:value];
[strM appendString:@"\r\n"];
[dataM appendData:[strM dataUsingEncoding:NSUTF8StringEncoding]];
}];
// 3. 结尾字符串
NSString *tail = [NSString stringWithFormat:@"--%@--", boundary];
[dataM appendData:[tail dataUsingEncoding:NSUTF8StringEncoding]];
return dataM.copy;
}
测试代码
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 1. 上传文件数据
NSURL *fileURL1 = [[NSBundle mainBundle] URLForResource:@"001.png" withExtension:nil];
NSData *data1 = [NSData dataWithContentsOfURL:fileURL1];
NSURL *fileURL2 = [[NSBundle mainBundle] URLForResource:@"demo.jpg" withExtension:nil];
NSData *data2 = [NSData dataWithContentsOfURL:fileURL2];
// 如何传递参数 - 用字典传递参数
NSDictionary *dataDict = @{@"001.png": data1, @"002.jpg": data2};
// 2. 字符串参数
NSDictionary *params = @{@"status": @"how are you"};
// 3. 上传文件并且提交参数
[self uploadFile:@"userfile[]" dataDict:dataDict params:params];
}