【总结回顾】iOS Apprentice Tutorial 2:Checklists(六)

这是***【总结回顾】iOS Apprentice Tutorial 2:Checklists ***系列的第六篇文章,前几篇文章请见(一) 、(二)、(三)、(四)、(五)。

本篇文章总结本书的第九、十章( Putting to-do items into the checklistsUsing NSUserDefaults to rememberstuff)中的重点内容,主要优化了存储方法,以及如何记录用户最后使用的界面的位置等等,从173页到204页。

数据结构从下图中看,可能会更容易理解:

【总结回顾】iOS Apprentice Tutorial 2:Checklists(六)_第1张图片

60. 存储方法优化

之前用户的每一次点击之后都要保存,这样写起来不仅繁琐,而且有时候会忘记某个点击效果,就没法保存了。

所以我们这次使用的方法是,在App被 terminate 之前保存数据,在 AppDelegation 文件里,有两个方法就能够涵盖所有需要保存的情况:

func applicationDidEnterBackground(application: UIApplication)

func applicationWillTerminate(application: UIApplication)

实际上代码如下

  ...  
  
  let dataModel = DataModel()

  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    
    let navigationController = window!.rootViewController as! UINavigationController
    let controller = navigationController.viewControllers[0] as! AllListsViewController
    controller.dataModel = dataModel
    
    return true
  }
  
  ...

  func applicationDidEnterBackground(application: UIApplication) {
    saveData()
  }

  func applicationWillTerminate(application: UIApplication) {
    saveData()
  }

  func saveData() {
    dataModel.saveChecklists()
  }


注意代码中使用的是let controller = navigationController.viewControllers[0] as! AllListsViewController,不是之前出现的topViewController,因为后者只是当前正在展示在界面上的 view controller。

而 DataModel 的代码为:

import Foundation

class DataModel {
  //声明变量并赋值
  var lists = [Checklist]()
    
    
  //初始化方法。(注意初始化方法里没有super.init(),因为DataModel没有父类superclass)
  init() {
    loadChecklists()
  }
  
  //找路径
  func documentsDirectory() -> String {
    let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
    return paths[0]
  }
  
  //新建文件
  func dataFilePath() -> String {
    return (documentsDirectory() as NSString).stringByAppendingPathComponent("Checklists.plist")
  }
  
  //存数据
  func saveChecklists() {
    let data = NSMutableData()
    let archiver = NSKeyedArchiver(forWritingWithMutableData: data)
    archiver.encodeObject(lists, forKey: "Checklists")
    archiver.finishEncoding()
    data.writeToFile(dataFilePath(), atomically: true)
  }
  
  //取数据
  func loadChecklists() {
    let path = dataFilePath()
    if NSFileManager.defaultManager().fileExistsAtPath(path) {
      if let data = NSData(contentsOfFile: path) {
        let unarchiver = NSKeyedUnarchiver(forReadingWithData: data)
        lists = unarchiver.decodeObjectForKey("Checklists") as! [Checklist]
        unarchiver.finishDecoding()
      }
    }
  }
}

Checklist 的代码如下:

import UIKit

class Checklist: NSObject, NSCoding {
  var name = ""
  var items = [ChecklistItem]()
  
  init(name: String) {
    self.name = name
    super.init()
  }

  required init?(coder aDecoder: NSCoder) {
    name = aDecoder.decodeObjectForKey("Name") as! String
    items = aDecoder.decodeObjectForKey("Items") as! [ChecklistItem]
    super.init()
  }
  
  func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(name, forKey: "Name")
    aCoder.encodeObject(items, forKey: "Items")
  }
}

ChecklistItem 的代码如下:

import Foundation

class ChecklistItem: NSObject, NSCoding {
  var text = ""
  var checked = false

  override init() {
    super.init()
  }

  required init?(coder aDecoder: NSCoder) {
    text = aDecoder.decodeObjectForKey("Text") as! String
    checked = aDecoder.decodeBoolForKey("Checked")
    super.init()
  }
  
  func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(text, forKey: "Text")
    aCoder.encodeBool(checked, forKey: "Checked")
  }
  
  func toggleChecked() {
    checked = !checked
  }
}

61. var 和 let 的区别

Swift 中,value types(值类型)reference types(引用类型)有显著的区别,在let使用方面也有一些不同。

对值类型而言,var后面是值,可以变。let后面也是值,不能变。

我们在 class 类定义的 objects,都是引用类型。对引用类型而言,引用类型的变量或者常量实际上没有包含一个真正的对象,只包含了这个对象的引用。如果用let声明引用类型常量,那么此常量不能再指向其他的对象了,但是此常量对象本身是可以变化的。

如果还没搞定什么时候用let什么时候用var,作者给出了一个小方法:全都用let,然后编译器告诉你某个应该用var声明,再去改成var。

如果是 optional,必须用 var
如果是 optional,必须用 var
如果是 optional,必须用 var

63. NSUserDefaults记录用户看到了哪里

需求如下:记录用户看到了哪个页面,下次打开App的时候,还在上次离开的地方。

我们使用 NSUserDefaults 来记录这个数据。NSUserDefaults 有些像是 词典Dictionary,每个键对应一个值。NSUserDefaults 只能记录小量的数据,大的数据是不能使用NSUserDefaults的。

为了实现需求,我们需要做三件事情:

(1)

当segue从主界面到清单详细内容界面时,记录下所选清单的index,存到NSUserDefaults里。

写入值的方法为:

NSUserDefaults.standardUserDefaults().setInter(indexPath.row, forKeys: "ChecklistIndex")

(2)

当用户点击返回按钮返回到主界面时,需要去掉存到NSUserDefaults里的index值。可以赋值-1,表示在主界面。(注意:NSUserDefaults里不能使用optional)

首先要知道用户点击了返回按钮,然后才能赋值。如何才能知道用户点击了返回按钮呢?方法如下:

让主界面view controller 成为 navigation controller 的delegate,这样主界面就能够监听用户点击返回按钮事件了。类的开头写上delegate的名字UINavigationControllerDelegate,在viewDidAppear()写上navigationController?.delegate = self

然后可以实现delegate中的方法了:

  func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
    if viewController === self {
      NSUserDefaults.standardUserDefaults().setInter(indexPath.row, forKeys: "ChecklistIndex")
    }
  }

任何时候只要navigation controller要滑到新的页面时,这个方法都会被调用。
调用条件很重要,附上原文:

This method is called whenever the navigation controller will slide to a new screen.

(3)

如果App在启动后NSUserDefaults的值不是-1,需要自动跳转到NSUserDefaults里记录的页面。

首先要从NSUserDefaults取出值,才能知道跳转到哪个界面,读取值的方法为:

let index = NSUserDefaults.standardUserDefaults().integerForKey("ChecklistIndex")

跳转界面的方法为:

  override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    
    navigationController?.delegate = self
    
    let index = dataModel.indexOfSelectedChecklist
    if index >= 0 && index < dataModel.lists.count {
      let checklist = dataModel.lists[index]
      performSegueWithIdentifier("ShowChecklist", sender: checklist)
    }
  }

每次在view controller可见后,UIKit都会自动调用 viewDidAppear 方法。

为什么使用 viewDidAppear 方法而不是 viewDidLoad ?这里非常

64. Equal 或者 identical 的异同

===三个等号:检查三个等号两边的对象是否为同一个对象
==两个等号:在检查两个等号两边的变量是否有相同的值

65. Defensive programming 防御性/防错性程序设计

可是在用户第一次运行App的时候,NSUserDefaults里还没有值,这时候运行App,就会报错。因为NSUserDefaults的取值方法integerForKey()如果没有找到key对应的值,就会返回数字零,可是在这里,0是index的值,index 0 那行用户还没有创建呢。自然就崩溃了。

所以,我们要给NSUserDefaults设置一个初始值,在DataModel里写就好:

  func registerDefaults() {
    let dictionary = [ "ChecklistIndex": -1]

    NSUserDefaults.standardUserDefaults().registerDefaults(dictionary)
  }

然后在DataModel初始化方法里调用上面这方法就可以了。其实NSUserDefaults的读取也可以放到DataModel里,毕竟是MVC里的M嘛。

读取NSUserDefaults可以用Swift最新的一个语法:

  var indexOfSelectedChecklist: Int {
    get {
      return NSUserDefaults.standardUserDefaults().integerForKey("ChecklistIndex")
    }
    set {
      NSUserDefaults.standardUserDefaults().setInteger(newValue, forKey: "ChecklistIndex")
      NSUserDefaults.standardUserDefaults().synchronize()
    }
  }

NSUserDefaults.standardUserDefaults().synchronize()的作用是:每当indexOfSelectedChecklist的值发生改变的时候,强制NSUserDefaults保存数值,保证NSUserDefaults和.plist文件处于同步状态。

当计算机尝试读取indexOfSelectedChecklist的值的时候,get闭包里的代码会被执行,当尝试给indexOfSelectedChecklist赋予新值的时候,set闭包里的代码就会执行。这样#63中的方法可以更新如下:

  let index = dataModel.indexOfSelectedChecklist
  dataModel.indexOfSelectedChecklist = indexPath.row
  dataModel.indexOfSelectedChecklist = -1

第二个Defensive programming例子,看if后面跟的条件:

  override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    
    navigationController?.delegate = self
    
    let index = dataModel.indexOfSelectedChecklist
  
    if index >= 0 && index < dataModel.lists.count {
      let checklist = dataModel.lists[index]
      performSegueWithIdentifier("ShowChecklist", sender: checklist)
    }
  }
  

66. 创建默认的清单数据

如果App下载后第一次打开就有默认数据,有一个名为list的清单,同时App还会自动跳转到这个名为list的清单里面,可以直接添加内容(items)。如何实现需求呢?

  func registerDefaults() {
    let dictionary = [ "ChecklistIndex": -1,"FirstTime": true ]

    NSUserDefaults.standardUserDefaults().registerDefaults(dictionary)
  }
  

  func handleFirstTime() {
    let userDefaults = NSUserDefaults.standardUserDefaults()
    let firstTime = userDefaults.boolForKey("FirstTime")
    if firstTime {
      let checklist = Checklist(name: "List")
      lists.append(checklist)
      indexOfSelectedChecklist = 0
      userDefaults.setBool(false, forKey: "FirstTime")
      userDefaults.synchronize()
    }
  }

  init() {
    loadChecklists()
    registerDefaults()
    handleFirstTime()
  }

你可能感兴趣的:(【总结回顾】iOS Apprentice Tutorial 2:Checklists(六))