华过的痕迹

NSURLSession使用实战教程

  我的前面两篇文章介绍了NSURLSession套件的使用和NSURLSession套件的主要类。今天我们使用NSURLSession来完成一个小的应用程序。在实战之前,我先补充一点,为什么苹果会主推NSURLSession技术,而放弃大家都熟悉的NSURLConnection技术,苹果这么做肯定是有原因的,下面列举了NSURLSession的优点:

1.后台上传和下载。当你的程序退出了也能进行网络操作,这对用户和APP来说都是个好消息,不用运行APP就可以下载和上传,这样更节约手机电量。

2.能够暂停和恢复网络操作。不需要使用NSOperation就可以实现暂停、继续、重启等操作。

3.可配置的容器。

4.可以子类化并且可以设置私有存储方式。可以修改数据的存储方式和存储位置。

5.改进了授权处理机制。

6.代理更强大。

7.通过文件系统上传和下载。

 

  好了,进入整体开始我们的实战,开发一个小的APP叫《ByteClub》。这篇文章的实战我是参考国外的网站做的,原文《NSURLSession Tutorial》,地址:http://www.raywenderlich.com/51127/nsurlsession-tutorial。觉得它有点啰嗦,英文好的也可以看原文。

我没有去原文翻译它,参考它做完例子之后,我按照自己的思路写的本教程。

 

准备工作

1.如果你打算跟我一起动手做的话,您需要一个FQ工具,因为我需要使用dropbox(类似百度云盘)做http网络服务器,它在国内被墙掉了😢,我使用的是lantern.下载地址:http://pan.baidu.com/s/1hqhQqHI。下载完记得一定要安装和运行起来。

 

2.在dropbox网站注册成为开发者,然后创建一个APP。Dropbox开发者应用注册地址:https://www.dropbox.com/developers/apps

3.然后下载dropbox for mac,我自己准备好了安装文件DropboxInstaller.dmg,下载地址:http://pan.baidu.com/s/1sjDvZNB。然后安装,安装好后在跟目录下创建byteclub的目录。如图:

 

 

4.请在byteclub目录中,随便创建或者复制进来几个文件,然后会同步到服务器上,然后我们的第一步开发工作,就是从服务器读取到这些文件的文件名。

5.然后再下载起始项目,代码把UI界面以及dropbox的http接口做好了封装,以便我们专注于NSURLSession部分的实战和学习。起始项目代码下载地址:http://pan.baidu.com/s/1mg8M2Vm

6.如果您想查看最终效果,可以下载我实战完成后的代码。下载地址:http://pan.baidu.com/s/1sj48BAP

 

项目完成后的效果图:

 

 

您可以先稍微熟悉下初始项目。

 

 

开工干活

 

第一阶段:读取Dropbox的跟目录文件名,并显示。

1.打开Dropbox.m将您的apiKey和appSecret,appFolder设置进去,前两者认证需呀,后者是我们在准备阶段在Dropbox创建的目录,比如我的设置为:

static NSString *apiKey = @"rctz909lpd47vyq";
static NSString *appSecret = @“odz1qfezg4ij3pz";
NSString * const appFolder = @“byteclub";

然后你可以运行看看,按照引导信息,输入您在dropbox的账号信息,然后应该就可以认证通过了。

 

 

2.在NotesViewController.m文件中添加一个会话属性,用于保存我们的会话。

/**
 *   会话
 */
@property (nonatomic, strong) NSURLSession *session;

3.在initWithStyle:前面添加另外一个初始化方法:

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    //下面内容为创建会话
    if (self) {
        
        //会话配置,这里配置为短暂配置,还有默认配置和后台配置
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        //配置请求头
        [config setHTTPAdditionalHeaders:@{@"Authorization":[Dropbox apiAuthorizationHeader]}];
        
        //初始化会话
        _session = [NSURLSession sessionWithConfiguration:config];
    }
    return self;
}

4.找到notesOnDropbox:方法,然方法内输入如下代码:

  //显示加载提示
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    //获取你的Dropbox的根目录
    NSURL *url = [Dropbox appRootURL];
    //创建数据任务,这个方法主要用来请求HTTP的GET方法,并返回NSData对象,我们需要将数据再解析成我们需要的数据
    NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (!error) {
            // 响应状态代码为200,代表请求数据成功,判断成功后我们再进行数据解析
            NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
            if (httpResp.statusCode == 200) {
                
                NSError *jsonError;
                
                             //解析NSData数据
                NSDictionary *notesJSON =
                [NSJSONSerialization JSONObjectWithData:data
                                                options:NSJSONReadingAllowFragments
                                                  error:&jsonError];
                
                NSMutableArray *notesFound = [[NSMutableArray alloc] init];
                
                if (!jsonError) {                    
                    // 获取contents键值,文件路径保存在这里
                    NSArray *contentsOfRootDirectory = notesJSON[@"contents"];
                    
                    for (NSDictionary *data in contentsOfRootDirectory) {
                        if (![data[@"is_dir"] boolValue]) {
                            DBFile *note = [[DBFile alloc] initWithJSONData:data];
                            [notesFound addObject:note];
                        }
                    }
                     //排序
                    [notesFound sortUsingComparator:
                     ^NSComparisonResult(id obj1, id obj2) {
                         return [obj1 compare:obj2];
                     }];
                    
                    self.notes = notesFound;
                    
                    // NSURLSession的方法是在异步执行的,所以更新UI回到主线程
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                        [self.tableView reloadData];
                    });
                }
            }
            
        }
    }];
    
    //启动任务
    [dataTask resume];

  注释已经比较清晰,如果我们查看Dropbox的文档就会发现,私用这个URL返回的数据就是json数据,数据格式如下:

 

{

    "hash": "6a29b68d106bda4473ffdaf2e94c4b61",

    "revision": 73052,

    "rev": "11d5c00e1cf6c",

    "thumb_exists": false,

    "bytes": 0,

    "modified": "Sat, 10 Aug 2013 21:56:50 +0000",

    "path": "/byteclub",

    "is_dir": true,

    "icon": "folder",

    "root": "dropbox",

    "contents": [{

        "revision": 73054,

        "rev": "11d5e00e1cf6c",

        "thumb_exists": false,

        "bytes": 16,

        "modified": "Sat, 10 Aug 2013 23:21:03 +0000",

        "client_mtime": "Sat, 10 Aug 2013 23:21:02 +0000",

        "path": "/byteclub/test.txt",

        "is_dir": false,

        "icon": "page_white_text",

        "root": "dropbox",

        "mime_type": "text/plain",

        "size": "16 bytes"

    }],

    "size": "0 bytes"

}

 

 

  dropbox服务器的返回状态码有如下一些:

 

400 –代表参数有误.

401 – token错误或过期.

403 – 错误 OAuth 请求

404 – 请求的文件和目录不存在

405 – 请求方法错误,一般我们只使用get 和post

429 –程序请求次数过多

503 –请再次尝试

507 –使用Dropbox空间超过配额限制

5xx – 服务器错误

 

5.运行一下看看效果,我的效果是这样的:

 

第一阶段的实战完成,你可以先复习一下内容,然后再进行第二阶段的实战。

 

第二阶段的实战

  通过APP输入内容,并通过文件的形式保存到Dropbox服务器并然后显示。

 

 

1.点击右上角的“+”按钮,跳转到添加Notes界面,你可以输入内容,但目前没什么作用,我们需要实现这些功能。打开NotesViewController.m文件,找到prepareForSegue:sender:跳转方法,在代码showNote.delegate = self后添加下面一行代码,将会话session传递到新的页面,这样我们就可以在新的页面使用同一个会话了:

   //传递会话
    showNote.session = _session;

2.找到新页面所属类的实现文件NoteDetailsViewController.m文件,找到 (IBAction)done:(id)sender方法,这是我们点击done按钮需要执行的方法,替换为如下代码:

- (IBAction)done:(id)sender
{
    // must contain text in textview
    if (![_textView.text isEqualToString:@""]) {
        
        // check to see if we are adding a new note
        if (!self.note) {
            DBFile *newNote = [[DBFile alloc] init];
            newNote.root = @"dropbox";
            self.note = newNote;
        }
        
        _note.contents = _textView.text;
        _note.path = _filename.text;
        
        // - 上传文件到 DROPBOX - //
        // 获取需要上传文件的路径
        NSURL *url = [Dropbox uploadURLForPath:_note.path];
        
        // 创建请求,这里使用了put方法
        NSMutableURLRequest *request =
        [[NSMutableURLRequest alloc] initWithURL:url];
        [request setHTTPMethod:@"PUT"];
        
        //数据
        NSData *noteContents = [_note.contents dataUsingEncoding:NSUTF8StringEncoding];
        
        // 上传任务,NSURLSessionUploadTask支持文件,NSData,数据流stream的类型数据上传
        NSURLSessionUploadTask *uploadTask = [_session
                                              uploadTaskWithRequest:request
                                              fromData:noteContents
                                              completionHandler:^(NSData *data,
                                                                  NSURLResponse *response,
                                                                  NSError *error)
        {
            //根据HTTP返回的代号确定是否成功,200代表成功,成功后我调用代理方法
            NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
            
            if (!error && httpResp.statusCode == 200) {
                
                [self.delegate noteDetailsViewControllerDoneWithDetails:self];
            } else {
                // alert for error saving / updating note
            }
        }];
        
        // 必须要的动作,启动任务
        [uploadTask resume];
        
    } else {
        UIAlertView *noTextAlert = [[UIAlertView alloc] initWithTitle:@"输入为空"
                                                              message:@"总得输入点啥吧,亲"
                                                             delegate:nil
                                                    cancelButtonTitle:@""
                                                    otherButtonTitles:nil];
        [noTextAlert show];
    }
}

3.打开NoteDetailsViewController.m方法,找到方法retreiveNoteText:,替换为如下内容:

-(void)retreiveNoteText
{
    // 根据Dropbox API设置要查看的note的请求路径
    NSString *fileApi =
    @"https://api-content.dropbox.com/1/files/dropbox";
    NSString *escapedPath = [_note.path
                             stringByAddingPercentEscapesUsingEncoding:
                             NSUTF8StringEncoding];
    
    NSString *urlStr = [NSString stringWithFormat: @"%@/%@",
                        fileApi,escapedPath];
    
    NSURL *url = [NSURL URLWithString: urlStr];
    
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    
    // 执行下载数据任务
    [[_session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        
        if (!error) {
            NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
            if (httpResp.statusCode == 200) {
                // 数据转字符串
                NSString *text =
                [[NSString alloc]initWithData:data
                                     encoding:NSUTF8StringEncoding];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                    self.textView.text = text;
                });
                
            } else {
                // 处理错误的响应 //
            }
        } else {
            // 处理意外错误 //
        }
        // 一定要创建任务后启动哦
    }] resume];

}

4.运行看看,比如输入内容,点击done按钮,过一会儿你查看下你的byteclub目录,应该会创建一个新的文件。我的运行效果是这样的:

 

 

  好了,我们的第二阶段任务就完成,还是建议您复习一下刚才的内容,然后我们开始第三阶段的实战。

 

第三阶段

使用NSURLSessionTask的代理方法发送图片到dropbox

 

 

1.请在byteclub目录下新建一个photos目录,然后拖一些你的图片到里面来,等待一会儿,图片应该就会上传完。

 

2.打开PhotosViewController.m,找到tableView:cellForRowAtIndexPath:方法,替换为下面内容:

tableView:cellForRowAtIndexPath:

3.相同文件,找到refreshPhotos方法,替换为下面内容:

//获取图片
- (void)refreshPhotos
{
    //网络家提示开启
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
    //dropbox的API,这个接口用于搜索图片
    NSString *photoDir = [NSString stringWithFormat:@"https://api.dropbox.com/1/search/dropbox/%@/photos?query=.jpg",appFolder];
    NSURL *url = [NSURL URLWithString:photoDir];
    //启动一个数据下载任务
    [[_session dataTaskWithURL:url completionHandler:^(NSData
                                                       *data, NSURLResponse *response, NSError *error) {
        if (!error) {
            NSHTTPURLResponse *httpResp =
            (NSHTTPURLResponse*) response;
            //状态码为200请求成功
            if (httpResp.statusCode == 200) {
                //返回的数据类型为json数组,解析
                NSError *jsonError;
                NSArray *filesJSON = [NSJSONSerialization
                                      JSONObjectWithData:data
                                      options:NSJSONReadingAllowFragments
                                      error:&jsonError];
                NSMutableArray *dbFiles =
                [[NSMutableArray alloc] init];
                
                if (!jsonError) {
                    for (NSDictionary *fileMetadata in
                         filesJSON) {
                        DBFile *file = [[DBFile alloc]
                                        initWithJSONData:fileMetadata];
                        [dbFiles addObject:file];
                    }
                    
                    [dbFiles sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
                        return [obj1 compare:obj2];
                    }];
                    //添加到数组中保存
                    _photoThumbnails = dbFiles;
                    //更新主界面
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
                        [self.tableView reloadData];
                    });
                }
            } else {
                // 处理相应失败//
            }
        } else {
            // 这里处理失败 //
        }
    }] resume];

}
refreshPhotos:

4.休息一下,运行看看,应该可以看到图片加载了,我的效果如图:

 

 

  好了,现在我做上传图片的操作,并通过NSURLSessionDelegate和NSURLSessionTaskDelegate来帮助我们了解上传的状态和进度。

 

5.修改PhotosViewController.m使其遵守NSURLSessionTaskDelegate代理协议,代码如图:

@interface PhotosViewController ()<UITableViewDelegate, UITableViewDataSource, UIImagePickerControllerDelegate, UINavigationControllerDelegate, NSURLSessionTaskDelegate>

6.添加如下属性,用于保存上传任务:

@property (nonatomic, strong) NSURLSessionUploadTask *uploadTask;

7.修改uploadImage:方法,替代为如下内容:

- (void)uploadImage:(UIImage*)image
{
    NSData *imageData = UIImageJPEGRepresentation(image, 0.6);
    
    // 配置一次只能对服务器一个连接
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    config.HTTPMaximumConnectionsPerHost = 1;
    [config setHTTPAdditionalHeaders:@{@"Authorization": [Dropbox apiAuthorizationHeader]}];
    
    // 初始化上传会话
    NSURLSession *upLoadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
    
    // 上传任务的URL地址
    NSURL *url = [Dropbox createPhotoUploadURL];
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setHTTPMethod:@"PUT"];
    
    // 设置上传的图片数据
    self.uploadTask = [upLoadSession uploadTaskWithRequest:request fromData:imageData];
    
    // 上传进度
    self.uploadView.hidden = NO;
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
    
    // 启动任务
    [_uploadTask resume];
  
}
uploadImage:

8.最类最后面添加NSURLSessionTaskDelegate的两个方法实现:

#pragma mark - NSURLSessionTaskDelegate 方法

//
/**
 *这个代理方法可以跟踪进度
 *
 *  @param session                  会话
 *  @param task                     任务
 *  @param bytesSent                正在接受到的数据大小
 *  @param totalBytesSent           总的接受的数据带大小
 *  @param totalBytesExpectedToSend 估算总共需要接受的数据大小
 */
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
    //根据接收到数据大小和总的数据大小计算出进度条显示的进度值
    dispatch_async(dispatch_get_main_queue(), ^{
        [_progress setProgress:
         (double)totalBytesSent /
         (double)totalBytesExpectedToSend animated:YES];
    });
}

/**
 *当上传或下载数据成功时执行
 *
 *  @param session 会话
 *  @param task    任务
 *  @param error   错误
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    // 主线程更新进度,隐藏加载提示,隐藏上传进度
    dispatch_async(dispatch_get_main_queue(), ^{
        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
        _uploadView.hidden = YES;
        [_progress setProgress:0.5];
    });
    
    if (!error) {
        // 2
        dispatch_async(dispatch_get_main_queue(), ^{
            [self refreshPhotos];
        });
    } else {
        //处理错误
    }
}
NSURLSessionTaskDelegate 方法

9.找到cancelUpload:方法,当我们点击cancel取消时会调用此方法,这里我们需要取消上传任务:

// 停止上传
- (IBAction)cancelUpload:(id)sender
{
    //取消上传任务
    if (_uploadTask.state == NSURLSessionTaskStateRunning) {
        [_uploadTask cancel];
    }

}

10.哈哈,大功告成,运行一下看看效果吧

 

 

 

  至此我通过三篇文章介绍了NSULRSession套件的原理,和它的常用类,然后通过一个实战例子告诉大家NSURLSession如何使用。谢谢大家!

posted on 2015-11-27 19:49  华过的痕迹  阅读(3081)  评论(1编辑  收藏  举报

导航