NSURLSession 是 iOS 系统提供给我们的原生网络操作库,它提供了网络操作相关的一系列特性支持,比如缓存控制,Cookie管理,HTTP 认证处理等等,是一套整体的网络操作处理解决方案。
关于 NSURLSession 的基本特性,我们之前的一篇文章 NSURLSession
网络库 - 原生系统送给我们的礼物 有过详细的介绍,如果大家之前没有使用过这个库,可以先参考一下这篇内容。
这次我们不介绍任何 NSURLSession 的基础概念,我们将以一个实际的下载工具 APP 开发来实际的感受 NSURLSession 的大部分特性。那么闲言碎语不要讲,咱们就开始吧~
创建项目
首先,我们使用 Xcode 将我们的下载工具 APP 的项目建立好,点击 Xcode 菜单中 File -> New -> Project… 然后在弹出的窗口中选择项目的模板为 Tabbed Application:
接下来,输入 Product Name 为 downloader,然后选择项目语言为 Swift:
然后点击 Next 按钮,选择一个合适的位置存放你的项目文件。
项目创建完成后,运行 APP,就会看到为我们生成的默认界面了:
建立模型
开发一个下载管理工具,首先我们要建立下载任务的模型。
DownloadTask
我们建立一个 DownloadTask
结构用于表示下载任务:
struct DownloadTask { var url: NSURL var localURL:NSURL? var taskIdentifier: Int var finished:Bool = false init(url:NSURL, taskIdentifier: Int) { self.url = url self.taskIdentifier = taskIdentifier } } |
它包含4个属性,url
表示当前下载任务的
url 地址。localURL
表示下载成功后保存到本地文件的位置,taskIdentifier
作为这个下载任务的唯一标识。 finished
表示当前下载任务是否完成。
TaskManager
模型建立完成后,我们再创建一个 TaskManager
类,用于管理下载任务:
class TaskManager: NSObject, NSURLSessionDownloadDelegate { private var session:NSURLSession? var taskList:[DownloadTask] = [DownloadTask]() static var sharedInstance:TaskManager = TaskManager() override init() { super.init() let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("downloadSession") self.session = NSURLSession(configuration: config, delegate: self, delegateQueue: NSOperationQueue.mainQueue()) self.taskList = [DownloadTask]() self.loadTaskList() } //... } |
我们来看一下 TaskManager
的定义,它继承自 NSObject
,并且实现了 NSURLSessionDownloadDelegate
协议。这个协议用于检测下载进度,稍后我们会介绍到。
- 它还定义了一个属性
private var
作为我们所有下载任务所需的 NSURLSession 实例。
session:NSURLSession? var taskList:[DownloadTask]
这个属性用于保存和跟踪我们所有的下载任务记录。用我们开始定义的
= [DownloadTask]()DownloadTask
结构表示。static var sharedInstance:TaskManager
表示
= TaskManager()TaskManager
类的单例实例。
在 init() 方法中,我们使用 backgroundSessionConfigurationWithIdentifier 初始化了一个 background 类型的 NSURLSession 配置信息。这个配置可以让下载会话在应用切换到后台的情况下还能继续进行。
随后我们用这个配置信息对象初始化我们的 NSURLSession,指定它的代理类,以及代理类所在的线程队列。随后通过:
self.taskList = [DownloadTask]() self.loadTaskList() |
这两行代码初始化应用的下载任务列表,self.loadTaskList()
也是 TaskManager
类中的方法,我们稍后会详细介绍。
TaskManager 任务管理类建立好了,我们还需要它提供两个任务列表,一个是正在下载的任务,一个是已经下载完成的任务,这样我们的前端 UI 就可以将相应的任务显示给用户了:
func unFinishedTask() -> [DownloadTask] { return taskList.filter{ task in return task.finished == false } } func finishedTask() -> [DownloadTask] { return taskList.filter { task in return task.finished } } |
unFinishedTask()
和 finishedTask()
方法分别对象了正在下载的任务列表和已经下载完成的任务列表。并且他们的实现也非常简单,我们用到了
Swift 中 Array 提供的 filter 方法,来过滤列表中的数据。 filter 方法会遍历整个集合,并为每一个集合项提供一个闭包,我们只需要在闭包中返回一个 bool 值,来表示当前这个集合项是否符合条件。
两个列表方法提供好后,我们还需要另外一对工具方法。我们的下载任务是需要进行持久化保存的,这样我们每次重新打开 APP 就不会丢失以前建立的任务了,这也是一个下载工具必备的功能。我们这里将任务信息转换成 JSON 数据存储起来,以便下次打开 APP 的时候能够恢复任务数据:
func saveTaskList() { let jsonArray = NSMutableArray() for task in taskList { let jsonItem = NSMutableDictionary() jsonItem["url"] = task.url.absoluteString jsonItem["taskIdentifier"] = NSNumber(long: task.taskIdentifier) jsonItem["finished"] = NSNumber(bool: task.finished) jsonArray.addObject(jsonItem) } do { let jsonData = try NSJSONSerialization.dataWithJSONObject(jsonArray, options: NSJSONWritingOptions.PrettyPrinted) NSUserDefaults.standardUserDefaults().setObject(jsonData, forKey: "taskList") NSUserDefaults.standardUserDefaults().synchronize() }catch { } } func loadTaskList() { if let jsonData:NSData = NSUserDefaults.standardUserDefaults().objectForKey("taskList") as? NSData { do { guard let jsonArray:NSArray = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.AllowFragments) as? NSArray else { return } for jsonItem in jsonArray { if let item:NSDictionary = jsonItem as? NSDictionary { guard let urlstring = item["url"] as? String else { return } guard let taskIdentifier = item["taskIdentifier"]?.longValue else { return } guard let finished = item["finished"]?.boolValue else { return } let downloadTask = DownloadTask(url: NSURL(string: urlstring)!, taskIdentifier: taskIdentifier) self.taskList.append(downloadTask) } } } catch { } } } |
saveTaskList()
和 loadTaskList()
方法分别用于保存和加载任务数据,这两个方法的实现都是使用
JSON 数据和 NSUserDefaults 的相关操作,我们就不详细展开了。
我们接下来还需要一个方法来建立新的下载任务:
func newTask(url: String) { if let url = NSURL(string: url) { let downloadTask = self.session?.downloadTaskWithURL(url) downloadTask?.resume() let task = DownloadTask(url: url, taskIdentifier: downloadTask!.taskIdentifier) self.taskList.append(task) self.saveTaskList() } } |
newTask
方法接受一个
url 参数,代表我们这个新任务的下载地址,使用 self.session?.downloadTaskWithURL(url)
来建立一个新的
NSURLSession 的下载任务。接着,我们调用 downloadTask?.resume()
启动这个任务。
下载任务建立好了,我们还需要将它的信息保存起来,就需要实例化一个我们之前定义的 DownloadTask
类,然后将它加入到我们的存储列表 self.taskList
中。最后调用 self.saveTaskList()
将任务数据保存到持久化层中。
到现在,我们已经建立好了一套基础机制,可以新建并开启下载任务了。当然,我们还需要对下载任务进行一些处理,比如下载完成时候要将任务状态重新标记,以及实时显示任务的下载进度,这就需要用到我们最开始实现的 NSURLSessionDownloadDelegate 协议的方法,现在我们来完善它:
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) { var fileName = "" for var i = 0;i < self.taskList.count;i++ { if self.taskList[i].taskIdentifier == downloadTask.taskIdentifier { self.taskList[i].finished = true fileName = self.taskList[i].url.lastPathComponent! } } if let documentURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first { let destURL = documentURL.URLByAppendingPathComponent(fileName) do { try NSFileManager.defaultManager().moveItemAtURL(location, toURL: destURL) } catch { } } self.saveTaskList() NSNotificationCenter.defaultCenter().postNotificationName(DownloadTaskNotification.Finish.rawValue, object: downloadTask.taskIdentifier) } func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) { } func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { let progressInfo = ["taskIdentifier": downloadTask.taskIdentifier, "totalBytesWritten": NSNumber(longLong: totalBytesWritten), "totalBytesExpectedToWrite": NSNumber(longLong: totalBytesExpectedToWrite)] NSNotificationCenter.defaultCenter().postNotificationName(DownloadTaskNotification.Progress.rawValue, object: progressInfo) } |
NSURLSessionDownloadDelegate 协议中定义了三个方法:
URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) |
didFinishDownloadingToURL
在下载任务完成的时候被调用, didResumeAtOffset
在下载任务恢复的时候被调用, didWriteData
在下载任务进度发生变化的时候被调用。
我们先来看一下 didFinishDownloadingToURL
的实现,首先我们遍历整个
self.taskList 列表,通过 taskIdentifier 找到我们完成的这个下载任务对应的 DownloadTask 对象,然后将它的 finished 属性设置为 true,随后通过 fileName 将这个下载任务的文件名暂存下来。
这个协议方法中的 location 参数表示下载成功后,文件的暂存位置。使用 NSURLSession 下载的文件,都会先存放到一个临时目录中,我们需要将这个文件从临时目录中移动到我们 APP 的持久目录中,也就是我们接下来做的事情:
if let documentURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first { let destURL = documentURL.URLByAppendingPathComponent(fileName) do { try NSFileManager.defaultManager().moveItemAtURL(location, toURL: destURL) } catch { } } |
移动完成后,我们调用 self.saveTaskList()
方法保存当前的任务信息。最后我们会发送一个通知消息,让前端
UI 相应的进行刷新操作:
NSNotificationCenter.defaultCenter().postNotificationName(DownloadTaskNotification.Finish.rawValue, object: downloadTask.taskIdentifier) |
这样我们的 didFinishDownloadingToURL 方法就实现完成了。注意通知的名称 DownloadTaskNotification.Finish.rawValue
是一个枚举值,使用枚举来定义通知的名称的好处是复用起来比较方便,我们来看看这个枚举的定义:
enum DownloadTaskNotification: String { case Progress = "downloadNotificationProgress" case Finish = "downloadNotificationFinish" } |
定义了两个枚举项,Finish 和 Progress,分别用于表示下载完成和下载进度变化。
我们继续分析 didWriteData 方法的定义,这个方法做的事情就是将当前的任务进度相关信息分装成一个 Dictionary, 然后通过通知的方式发送给前端进行处理:
let progressInfo = ["taskIdentifier": downloadTask.taskIdentifier, "totalBytesWritten": NSNumber(longLong: totalBytesWritten), "totalBytesExpectedToWrite": NSNumber(longLong: totalBytesExpectedToWrite)] NSNotificationCenter.defaultCenter().postNotificationName(DownloadTaskNotification.Progress.rawValue, object: progressInfo) |
到此我们这个下载工具基础模型就搭建好了,接下来我们就可以开始进行前端 UI 的创建了,即将看到效果咯。
交互与 UI
我们的这个下载 APP 是 Tab 结构的,这在最开始建立项目的时候我们已经看到,我们可以打开项目的 storyboard 文件,看到当前的 UI 结构:
两个 Tab,每一个 Tab 直接关联到一个 UIViewController。我们需要对现有结构进行一些修改,我们在 UIViewController 和 Tab 之间再加入一层 UINavigationController。
拖放两个 UINavigationController 到 storyboard 中,切断 First View 和 Second View 两个控制器与 Tab 控制器的关联。然后将这两个控制器与两个 UINavigationController 的 rootViewController 关联起来,在将 UINavigationController 与 Tab 控制器关联起来,修改后的结构如下:
我们进入到 FirstViewController 代码中(也就是第一个 Tab 对应的控制器),为它添加两个属性:
class FirstViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { private var mainTableView: UITableView? private var taskList: [DownloadTask]? //... } |
FirstViewController 实现了 UITableViewDelegate
和 UITableViewDataSource
协议。 mainTableView
用于显示我们的任务列表,taskList
表示我们当前显示的任务列表。
接着,我们实现 ViewDidLoad 方法:
override func viewDidLoad() { super.viewDidLoad() self.title = "正在下载" self.navigationController?.navigationBar.translucent = false self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: Selector("addTask")) self.mainTableView = UITableView(frame: CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)) self.mainTableView?.delegate = self self.mainTableView?.dataSource = self self.view.addSubview(self.mainTableView!) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("reloadData"), name: DownloadTaskNotification.Finish.rawValue, object: nil) } |
这里设置的这个控制器的 title 属性,以及 navigationBar 的透明度。然后创建了一个导航条按钮,用来添加任务。接着创建了 UITableView 对象,用来显示任务列表。最后,将我们的这个类的 reloadData 方法注册给 DownloadTaskNotification.Finish 通知。每当有任务下载完成的时候,我们都会重新加载列表内容。
接下来,我们创建重新加载数据的方法:
override func viewDidAppear(animated: Bool) { self.reloadData() } func reloadData() { taskList = TaskManager.sharedInstance.unFinishedTask() self.mainTableView?.reloadData() } |
每次在 viewDidAppear 中,我们会调用 self.reloadData() 方法重新加载数据,reloadData() 方法中使用我们之前定义的 TaskManager.sharedInstance.unFinishedTask() 方法获取正在下载的任务列表。最后调用 self.mainTableView?.reloadData() 方法刷新 UITableView 的显示。
我们接下来在实现 UITableView 的一系列代理方法:
func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return 70.0 } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.taskList == nil ? 0 : self.taskList!.count } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as? DownloadTaskCell if cell == nil { cell = DownloadTaskCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell") } cell?.updateData((self.taskList?[indexPath.row])!) return cell! } |
都是比较常用的逻辑,不需要过多展开了。唯一需要注意的一点是,这里用到了一个我们自定义的类 DownloadTaskCell
,我们接下来就创建这个类:
class DownloadTaskCell: UITableViewCell { var labelName: UILabel = UILabel() var labelSize: UILabel = UILabel() var labelProgress: UILabel = UILabel() var downloadTask: DownloadTask? //... } |
这个类继承自 UITableViewCell,它定义了 3 个 UILabel,分别用于显示下载任务的名称,已完成情况和下载进度。 还定义了一个 downloadTask 属性,表示当前单元格所表示的是哪一个下载任务。
再来看看它的初始化方法:
override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.addSubview(labelName) self.addSubview(labelSize) self.addSubview(labelProgress) self.labelName.font = UIFont.systemFontOfSize(14) self.labelSize.font = UIFont.systemFontOfSize(14) self.labelProgress.font = UIFont.systemFontOfSize(14) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("updateProgress:"), name: DownloadTaskNotification.Progress.rawValue, object: nil) } |
将 UILabel 添加到视图中并设置了它们的字体,最后将 updateProgress
方法注册为
DownloadTaskNotification.Progress 通知,这样我们在下载任务进度变化的时候,就可以及时更新 UI 显示了。
接下来定义 updateProgress
方法:
func updateProgress(notification: NSNotification) { guard let info = notification.object as? NSDictionary else { return } if let taskIdentifier = info["taskIdentifier"] as? NSNumber { if taskIdentifier.integerValue == self.downloadTask?.taskIdentifier { guard let written = info["totalBytesWritten"] as? NSNumber else { return } guard let total = info["totalBytesExpectedToWrite"] as? NSNumber else { return } let formattedWrittenSize = NSByteCountFormatter.stringFromByteCount(written.longLongValue, countStyle: NSByteCountFormatterCountStyle.File) let formattedTotalSize = NSByteCountFormatter.stringFromByteCount(total.longLongValue, countStyle: NSByteCountFormatterCountStyle.File) self.labelSize.text = "\(formattedWrittenSize) / \(formattedTotalSize)" let percentage = Int((written.doubleValue / total.doubleValue) * 100.0) self.labelProgress.text = "\(percentage)%" } } } |
首先我们将之前封装传入进来的任务进度信息解析出来,还记不记得我们在 TaskManager 中定义的这个结构:
let progressInfo = ["taskIdentifier": downloadTask.taskIdentifier, "totalBytesWritten": NSNumber(longLong: totalBytesWritten), "totalBytesExpectedToWrite": NSNumber(longLong: totalBytesExpectedToWrite)] |
这里解析的正式这个数据。首先我们取出 info[“taskIdentifier”], 然后将它与我们当前单元格对应的 DownloadTask 的 taskIdentifier 进行对比:
if taskIdentifier.integerValue == self.downloadTask?.taskIdentifier |
如果对比成功,是当前的任务进度发生变化,那么就需要对 UI 进行更新,接下来,我们就取出其余的任务进度信息,然后将他们设置到 labelSize
和 labelProgress
的文本中:
guard let written = info["totalBytesWritten"] as? NSNumber else { return } guard let total = info["totalBytesExpectedToWrite"] as? NSNumber else { return } let formattedWrittenSize = NSByteCountFormatter.stringFromByteCount(written.longLongValue, countStyle: NSByteCountFormatterCountStyle.File) let formattedTotalSize = NSByteCountFormatter.stringFromByteCount(total.longLongValue, countStyle: NSByteCountFormatterCountStyle.File) self.labelSize.text = "\(formattedWrittenSize) / \(formattedTotalSize)" let percentage = Int((written.doubleValue / total.doubleValue) * 100.0) self.labelProgress.text = "\(percentage)%" |
我们继续定义一个 updateData 方法,用它来绑定 DownloadTask 信息:
func updateData(task : DownloadTask) { self.downloadTask = task labelName.text = self.downloadTask?.url.lastPathComponent } |
然后覆盖 layoutSubviews 方法,用来布局 UI 界面:
override func layoutSubviews() { super.layoutSubviews() self.labelName.frame = CGRectMake(20, 10, self.contentView.frame.size.width - 50, 20) self.labelSize.frame = CGRectMake(20, 40, self.contentView.frame.size.width - 50, 20) self.labelProgress.frame = CGRectMake(self.contentView.frame.size.width - 45, 20, 40, 30) } |
这样, DownloadTaskCell 类的实现就完成了。
添加任务
对任务的显示,更新这些操作都处理完了,我们还需要一个 UI 来添加新的下载任务,创建一个新的类 NewTaskViewController
:
class NewTaskViewController: UIViewController { var textView:UITextView? override func viewDidLoad() { super.viewDidLoad() self.title = "添加任务" self.view.backgroundColor = UIColor.whiteColor() self.navigationController?.navigationBar.translucent = false self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "下载", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("add")) self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "取消", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("close")) self.textView = UITextView(frame: CGRectMake(10, 10, self.view.frame.size.width - 20, 250)) self.textView?.layer.borderWidth = 1 self.textView?.layer.borderColor = UIColor.grayColor().CGColor self.textView?.layer.cornerRadius = 5 self.textView?.font = UIFont.systemFontOfSize(14) self.view.addSubview(self.textView!) } override func viewDidAppear(animated: Bool) { self.textView?.becomeFirstResponder() } func close() { self.dismissViewControllerAnimated(true, completion: nil) } func add() { TaskManager.sharedInstance.newTask(self.textView!.text) self.dismissViewControllerAnimated(true, completion: nil) } } |
这个类有一个 textView 控件,用于输入下载链接,导航条的两个按钮分别对应 close() 和 add() 方法。 close 方法直接关闭这个界面。 add 方法会调用 TaskManager.sharedInstance.newTask(self.textView!.text)
方法开启并添加下载任务,然后关闭添加界面。
我们还需要给这个添加界面建立一个入口,回到 FirstViewController 类中,我们添加一个方法:
func addTask() { let viewController = NewTaskViewController() let navController = UINavigationController(rootViewController: viewController) self.presentViewController(navController, animated: true, completion: nil) } |
好了,下载工具的基础结构都搭建好了,现在你可以试着运行一下程序了。
点击添加任务按钮后,我们可以输入一个下载地址,比如 https://nodejs.org/dist/v4.2.3/node-v4.2.3.pkg:
点击好确定后,下载任务即可开启:
这时候,你可以试着将 APP 切换到后台,过几分钟后再重新进入 APP, 你会发现下载任务的进度增长了一大块,或是下载任务已经完成了。这时因为在你切换到后台这段时间,NSURLSession 还在后台继续进行下载任务。
APP 运行一会儿之后,下载任务完成了,这时你会看到 “正在下载” 任务列表中的这个任务 “消失” 了,是因为我们注册了通知,当有下载任务完成的时候,我们就会刷新列表显示,刷新后只会把还没有下载完成的任务显示出来。所以我们刚刚那个完成的任务就看不到了。
显示已下载任务
所以我们还需要一个地方来显示我们已经下载完成的任务,我们打开 SecondViewController, 同样,给它添加一个 mainTableView 和 taskList 属性:
class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { private var mainTableView:UITableView? private var taskList: [DownloadTask]? //... } |
实现它的 viewDidLoad 方法:
override func viewDidLoad() { super.viewDidLoad() self.title = "已完成" self.navigationController?.navigationBar.translucent = false self.mainTableView = UITableView(frame: CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)) self.mainTableView?.delegate = self self.mainTableView?.dataSource = self self.view.addSubview(self.mainTableView!) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("reloadData"), name: DownloadTaskNotification.Finish.rawValue, object: nil) } |
这里也是进行了控件初始化和注册通知,我们再来创建处理通知的 reloadData 方法:
override func viewDidAppear(animated: Bool) { self.reloadData() } func reloadData() { self.taskList = TaskManager.sharedInstance.finishedTask() self.mainTableView?.reloadData() } |
和 “正在下载” 列表不同的是,这次我们使用 TaskManager.sharedInstance.finishedTask()
方法来获取任务列表,我们只获取那些已经完成的下载任务,随后重新加载
UITableView 数据。
最后,我们再实现 UITableView 的代理方法:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return 70.0 } func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.taskList == nil ? 0 : self.taskList!.count } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCellWithIdentifier("FinishedCell") as? DownloadTaskCell if cell == nil { cell = DownloadTaskCell(style: UITableViewCellStyle.Default, reuseIdentifier: "FinishedCell") } cell?.updateData(self.taskList![indexPath.row]) return cell! } |
这里面的实现方法没有太多变化。这样我们再重新运行应用,点击第二个 “已完成” Tab,就可以看到我们刚才下载完成的任务了。
结语
回顾一下。这次通过一个完整的 APP 开发向大家展示了 NSURLSession 在实际开发中的应用。更主要在下载操作方面的应用。我们首先创建了 background 类型的 NSURLSession,这种类型的 Session 建立的下载任务可以在应用切换到后台后继续运行,并且我们通过代理和通知机制,非常轻便的就实现了 UI 界面的及时更新。
当然,NSURLSession 的特性还有很多,比如我们这里还没有实现断点续传,以及如果在后台下载完成后的相应处理。大家也可以参考一下相关文档发挥一下自己的思考,同样,我稍后还会为大家继续完善这个例子,以及关于刚才说的断点续传这些功能的文章。
这里很多地方用到了 guard 关键字,这个是 Swift 2.0 版本新提供的特性,如果需要对这个进行了解,可以参考这里:guard
关键字