iOS渣逼(3)泥淖虾渣逼看NSURLConnection

NSURLSession

简介

  • 用于替代 NSURLConnection
  • 支持后台运行的网络任务
  • 暂停、停止、重启网络任务,不再需要 NSOperation 封装
  • 请求可以使用同样的配置容器
  • 不同的 session 可以使用不同的私有存储
  • block代理可以同时起作用
  • 直接从文件系统上传、下载

结构图

session.png
  • 为了方便程序员使用,苹果提供了一个全局 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];
}

运行测试:块代码回调结束后,下载的文件会被删除

原因分析:

  1. 一般从网络上下载文件,zip 压缩包会比较多

  2. 如果是 zip 文件,下载完成后需要:

    • 下载压缩包
    • 解压缩(异步执行)到目标文件夹
    • 删除压缩包
  3. 下载任务的特点可以让程序员只关心解压缩的工作

解压缩

  1. 添加第三方框架 SSZipArchive
  2. 添加动态库 libz.dylib
  3. 解压缩
// 目录
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 返回标准配置,具有共享 NSHTTPCookieStorageNSURLCacheNSURLCredentialStorage

    • 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];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容