美文网首页iOS 学习地图程序员
【总结回顾】iOS Apprentice Tutorial 2:

【总结回顾】iOS Apprentice Tutorial 2:

作者: sing_crystal | 来源:发表于2016-04-20 17:04 被阅读93次

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

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

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

    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:

      本文链接:https://www.haomeiwen.com/subject/thynlttx.html