码迷,mamicode.com
首页 > Web开发 > 详细

NSURLSession(Get & Post,JSON、XML数据解析,文件上传下载)

时间:2016-06-21 07:17:15      阅读:391      评论:0      收藏:0      [点我收藏+]

标签:

NSURLSession(Get & Post,JSON、XML数据解析,文件上传下载)

一、NSURLSession概述

NSURLSession是iOS7中新的网络接口,支持后台网络操作,除非用户强行关闭。

NSURLSession使用步骤:
1. 新建NSURLSessionConfiguration,用于NSURLSession的配置
2. 新建NSURLSession
3. 新建NSURLSessionTask对象
4. 执行task

其中NSURLSessionConfiguration可以不创建,NSURLSession使用单例对象sharedSession也能满足大多数开发需求。

NSURLSessionTask是一个抽象类,不能用它直接创建对象,而应该使用它的子类:
1. NSURLSessionDataTask 处理一般的NSData数据对象,比如通过GET或POST方式从服务器获取JSON或XML返回等等,但不支持后台获取
2. NSURLSessionUploadTask 用于上传文件,支持后台上传
3. NSURLSessionDownloadTask 用于下载文件,支持后台下载

每个Task都有下面三个方法:
- (void)suspend; //暂停任务
- (void)resume; //启动任务,或唤醒suspend状体的任务
- (void)cancel; //取消任务

二、简单的Get & Post 请求

2.1 Get & Post 概述

GET和POST是两种最常用的与服务器进行交互的HTTP方法。

GET
1. GET的语义是获取指定URL的资源
2. 将数据按照variable=value的形式,添加到action所指向的URL后面,并且两者使用”?”连接,各变量之间使用”&”连接
3. 貌似不安全,因为在传输过程中,数据被放在请求的URL中
4. 传输的数据量小,主要受URL长度限制

POST
1. POST的语义是向指定URL的资源添加数据
2. 将数据放在数据体中,按照变量和值相对应的方式,传递到action所指向URL
3. 所有数据对用户来说不可见
4. 可以传输大量数据,上传文件只能使用Post

对于URL来说,Get是不安全的,Post的安全的,因为Get请求能直接从URL中获取用户信息;但对于服务器来说,Post是不安全的,Get是安全的。因为Post发送给服务器的数据体可以使用工具查看内容(比如firebug)。

2.2 简单的Get请求(DataTask)

假如有一个登录程序,使用get请求的方法登录,则代码如下所示:

- (void)getLogin
{
    NSString *urlStr = [NSString stringWithFormat:@"http://localhost/login.php?username=%@&password=%@", self.username.text, self.password.text];
    NSURL *url = [NSURL URLWithString:urlStr];

    NSURLSession *session = [NSURLSession sharedSession];

    NSURLSession *session = [NSURLSession sharedSession];

    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlStr] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@", str);

        //JSON数据反序列化
        NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];

        //LoginUser--用户模型类
        LoginUser *user = [[LoginUser alloc] init];
        [user setValuesForKeysWithDictionary:dict];

        NSLog(@"%@ %@", user.userId, user.userName);
    }];

    //NSURLSession中的任务默认都是挂起的,执行这条语句之后任务才会执行
    [dataTask resume];

    NSLog(@"%@----come here", [NSThread currentThread]);

}

其中,come here先于str打印。

2.3 简单的Post请求(DataTask)

- (void)postLogin
{
    NSURL *url = [NSURL URLWithString:@"http://localhost/login.php"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    NSString *bodyStr = [NSString stringWithFormat:@"username=%@&password=%@", self.username.text, self.password.text];
    request.HTTPBody = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];

    NSURLSession *session = [NSURLSession sharedSession];

    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@----%@", [NSThread currentThread], str);

        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.label.text = str;
        }];

    }];

    NSLog(@"come here");

    [dataTask resume];

}

三、XML数据解析(DataTask)

JSON序列化、反序列化的方法在前面或后面的代码中会提到,这里提一下解析XML数据的方法。

假如有XML文件如下:

<note>
    <Email>
        <to>George</to>
        <from>John</from>
        <heading>Reminder</heading>
        <body>Don‘t forget the meeting!</body>
    </Email>

    <Email>
        <to>Steven</to>
        <from>Alice</from>
        <heading>Appointment</heading>
        <body>How about to watch a movie</body>
    </Email>

    <Email>
        <to>李四</to>
        <from>王五</from>
        <heading>李四有个儿子</heading>
        <body>李四要带他儿子去见隔壁老王</body>
    </Email>
</note>

则,解析上面的XML数据的代码如下:

@interface ViewController () <NSXMLParserDelegate>
//存储每一个Email节点的内容
@property (nonatomic, strong) NSMutableArray *dataList;
//获取节点数据
@property (nonatomic, strong) NSMutableString *elementContent;
//把每一个Email节点的数据转化为Email对象
@property (nonatomic, strong) Email *email;

- (IBAction)parserXMLFile:(id)sender;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
}

- (IBAction)parserXMLFile:(id)sender
{
    NSURL *url = [NSURL URLWithString:@"http://localhost/xmlExample.xml"];

    NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:10.0f];

    NSURLSession *session = [NSURLSession sharedSession];

    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];

        parser.delegate = self;

        [parser parse];

    }];

    [dataTask resume];

}

- (void)parserDidStartDocument:(NSXMLParser *)parser
{
    NSLog(@"开始解析xml文件,进行初始化工作");

    if (!_dataList) {
        _dataList = [[NSMutableArray alloc] init];
    }else{
        [_dataList removeAllObjects];
    }

    if (!_elementContent) {
        _elementContent = [[NSMutableString alloc] init];
    }else{
        [_elementContent setString:@""];
    }

}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
    NSLog(@"开始解析节点%@", elementName);

    //把之前的数据清空
    [self.elementContent setString:@""];

    if ([elementName isEqualToString:@"Email"]) {
        _email = [[Email alloc] init];
    }

}

//这个方法可能会调用多次
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    NSLog(@"查找到内容: %@", string);
    [self.elementContent appendString:string];
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    NSLog(@"节点%@解析结束", elementName);

    if ([elementName isEqualToString:@"Email"]) {
        [self.dataList addObject:self.email];

    }else if (![elementName isEqualToString:@"note"]) {
        [self.email setValue:self.elementContent forKey:elementName];
    }

}

- (void)parserDidEndDocument:(NSXMLParser *)parser
{
    NSLog(@"xml文件解析结束");

    [self.dataList enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        Email *email = obj;
        NSLog(@"%d-%@", idx, email);
    }];

}

- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
    NSLog(@"解析文件出现错误: %@", parseError);
}

@end

其中,Email类的代码如下:

@interface Email : NSObject

@property (nonatomic, copy) NSString *to;
@property (nonatomic, copy) NSString *from;
@property (nonatomic, copy) NSString *heading;
@property (nonatomic, copy) NSString *body;
@property (nonatomic, copy) NSString *emailId;

@end

@implementation Email

//打印Email时就会调用这个方法
- (NSString *)description
{
    NSMutableString *string = [NSMutableString stringWithString:@"\n(\n"];

    [string appendFormat:@"to: %@\nfrom: %@\nheading: %@\nbody: %@", self.to, self.from, self.heading, self.body];

    [string appendString:@"\n)"];

    return string;
}

@end

四、上传数据

4.1 上传JSON数据(DataTask)

    NSURL *url = [NSURL URLWithString:@"http://localhost/postjson.php"];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:2.0f];
    request.HTTPMethod = @"POST";

    Contact *contact = [[Contact alloc] init];
    contact.name = @"steven";
    contact.phone = @"13750406504";
    contact.identity = @"steven-13750406504";

    id obj = [contact dictionaryWithValuesForKeys:@[@"name", @"phone", @"identity"]];

    //也可以上传一组数组
//    NSDictionary *dict1 = @{
//                            @"name" : @"Alice",
//                            @"phone" : @"13588888888",
//                            @"identity" : @"Alice-13588888888"
//                            };
//    
//    NSDictionary *dict2 = @{
//                            @"name" : @"Steven",
//                            @"phone" : @"13500000000",
//                            @"identity" : @"Steven-13500000000"
//                            };
//    
//    NSArray *array = @[dict1, dict2];

    //序列化对象数据
    request.HTTPBody = [NSJSONSerialization dataWithJSONObject:obj options:0 error:NULL];

    NSURLSession *session = [NSURLSession sharedSession];

    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@", result);

    }];

    [dataTask resume];

4.2 上传文件(UploadTask)

上传一个文件有它固定的格式,这里使用multipart/form-data的方式上传,上传文件时需要把上传的格式用NSMutableString拼接好。先贴代码:

static NSString *boundaryStr = @"--";
static NSString *randomIDStr;
static NSString *uploadID;

@implementation UploadFile

- (instancetype)init
{
    if (self = [super init]) {
        //随意写一个
        randomIDStr = @"steven";
        //后端开发人员会提供
        uploadID = @"uploadFile";
    }
    return self;
}

//格式固定,不可随意更改,少一个字母都会出错
- (NSString *)topStringWithMimeType:(NSString *)mimeType uploadFileName:(NSString *)uploadFileName
{
    NSMutableString *strM = [NSMutableString string];

    [strM appendFormat:@"%@%@\n", boundaryStr, randomIDStr];
    [strM appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\n", uploadID, uploadFileName];
    [strM appendFormat:@"Content-Type: %@\n\n", mimeType];

    NSLog(@"%@", strM);
    return [strM copy];
}

- (NSString *)bottomString
{
    NSMutableString *strM = [NSMutableString string];

    [strM appendFormat:@"%@%@\n", boundaryStr, randomIDStr];
    //appednString,不是appendFormat!
    [strM appendString:@"Content-Disposition: form-data; name=\"submit\"\n\n"];
    [strM appendString:@"Submit\n"];
    [strM appendFormat:@"%@%@--\n", boundaryStr, randomIDStr];

    NSLog(@"%@", strM);
    return [strM copy];
}

- (void)uploadFileWithURL:(NSURL *)url data:(NSData *)data
{
    // 1> 数据体
    //第一个参数是上传的文件的类型,这里提前设置为image,实际开发中需要另行判断
    //第二个参数指文件上传到服务器保存时用什么名字保存
    NSString *topStr = [self topStringWithMimeType:@"image/jpg" uploadFileName:@"girl2.jpg"];
    NSString *bottomStr = [self bottomString];

    NSMutableData *dataM = [NSMutableData data];
    [dataM appendData:[topStr dataUsingEncoding:NSUTF8StringEncoding]];
    [dataM appendData:data];
    [dataM appendData:[bottomStr dataUsingEncoding:NSUTF8StringEncoding]];

    // Request
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:2.0f];

    request.HTTPBody = dataM;

    // 2> 设置Request的头属性
    request.HTTPMethod = @"POST";

    // 3> 设置Content-Length
    NSString *strLength = [NSString stringWithFormat:@"%ld", (long)dataM.length];
    [request setValue:strLength forHTTPHeaderField:@"Content-Length"];

    // 4> 设置Content-Type
    NSString *strContentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", randomIDStr];
    [request setValue:strContentType forHTTPHeaderField:@"Content-Type"];

    NSURLSession *session = [NSURLSession sharedSession];

    //girl2.png是要上传的文件,也可以把文件转为data类型再上传
    NSString *path = [[NSBundle mainBundle] pathForResource:@"girl2.jpg" ofType:nil];

    NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromFile:[NSURL URLWithString:path] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@", result);

    }];

    [uploadTask resume];
}

五、文件下载(DownloadTask)

5.1 简单的文件下载

    NSURL *url = [NSURL URLWithString:@"http://localhost/uploads/Google.mkv"];

    NSURLSession *session = [NSURLSession sharedSession];

    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {

        NSFileManager *fm = [[NSFileManager alloc] init];

        [fm moveItemAtPath:location.path toPath:self.cacheFile error:nil];

        NSLog(@"文件下载成功");
    }];

    [downloadTask resume];

5.2 大文件下载

@interface ViewController () <NSURLSessionDownloadDelegate>

//开始下载按钮
- (IBAction)downloadFile:(id)sender;

//暂停下载按钮
- (IBAction)stopDownload:(id)sender;

//显示当前下载进度
@property (weak, nonatomic) IBOutlet UILabel *progLabel;

@property (nonatomic, strong) NSString *cacheFile;

//只保存下载链接和上一次下载到的数据的位置
@property (nonatomic, strong) NSData *resumeData;

@property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];    
}

- (IBAction)downloadFile:(id)sender {


    NSURL *url = [NSURL URLWithString:@"http://localhost/uploads/Google.and.the.World.Brain.mkv"];

    [self downloadFileWithURL:url];
}

- (IBAction)stopDownload:(id)sender {

    __weak typeof(self) selfVc = self;
    [self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {

        selfVc.resumeData = resumeData;
        selfVc.downloadTask = nil;

    }];

}

//验证NSURLSession文件下载的操作在后台运行
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"哈哈");
}

//保存文件到沙盒,避免重复下载
- (void)setCacheFile:(NSString *)urlStr
{
    NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    //这里用MD5加密的方式对下载的文件进行命名
    urlStr = [urlStr myMD5];

    _cacheFile = [cacheDir stringByAppendingPathComponent:urlStr];
}

- (long long)cacheFileSize
{

    NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:self.cacheFile error:NULL];

    return [dict[NSFileSize] longLongValue];
}

- (void)downloadFileWithURL:(NSURL *)url
{

    self.cacheFile = [url absoluteString];

    //如果是相同的下载链接,MD5加密结果一样,因此可以根据当前文件名指向的文件的大小,判断文件是否重复下载
    if ([self cacheFileSize] != 0) {
        NSLog(@"文件已存在");
        return;
    }

    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];

    NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];

    //之前下载过就调用downloadTaskWithResumeData继续下载
    if (self.resumeData == nil) {
        self.downloadTask = [session downloadTaskWithURL:url];
    }else {
        self.downloadTask = [session downloadTaskWithResumeData:self.resumeData];
    }

    [self.downloadTask resume];
}

/**
 *  下载完毕后调用
 *
 *  @param location     文件临时地址
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    NSLog(@"finish downloading");

    NSFileManager *fm = [[NSFileManager alloc] init];

    [fm moveItemAtPath:location.path toPath:self.cacheFile error:nil];
}

/**
 *  每次写入沙盒完毕调用
 *
 *  @param bytesWritten              这次写入的大小
 *  @param totalBytesWritten         已经写入沙盒的大小
 *  @param totalBytesExpectedToWrite 文件总大小
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    //下载操作在后台运行,这里的代码在主线程运行
    NSLog(@"下载进度: %f", (double)totalBytesWritten);
    self.progLabel.text = [NSString stringWithFormat:@"下载进度: %.2f%%", 100 * (double)totalBytesWritten/totalBytesExpectedToWrite];
}

/**
 *  恢复下载时调用

 *  @param fileOffset         上次下载到的位置
 *  @param expectedTotalBytes 文件总大小
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
    NSLog(@"恢复下载: %lld %lld", fileOffset, expectedTotalBytes);
}

/**
 *  下载失败时会进入此方法,也可以用来恢复保存数据
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    self.resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
}

其中使用MD5加密的代码如下所示:

#import "NSString+Password.h"
#import <CommonCrypto/CommonDigest.h>

//随意写一串字符串
static NSString *token = @"fdhakfhieoafdklkhu3904njkgez.z‘vz;fz546489e35/.4,#498##84934/hifoefoa";

@implementation NSString (Password)

- (NSString *)myMD5
{
    NSString *str = [NSString stringWithFormat:@"%@%@", self, token];
    return [str MD5];
}

#pragma mark 使用MD5加密字符串
- (NSString *)MD5
{
    const char *cStr = [self UTF8String];
    unsigned char digest[CC_MD5_DIGEST_LENGTH];

    CC_MD5(cStr, strlen(cStr), digest);

    NSMutableString *result = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];

    for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
        [result appendFormat:@"%02x", digest[i]];
    }

    return result;
}

NSURLSession(Get & Post,JSON、XML数据解析,文件上传下载)

标签:

原文地址:http://blog.csdn.net/u011330638/article/details/51713354

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!