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

【总结回顾】iOS Apprentice Tutorial 2:

作者: sing_crystal | 来源:发表于2016-04-15 16:40 被阅读147次

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

    本篇文章总结本书的第七、八章( Saving and loading the checklist itemsMultiple checklists)中的重点内容,从126页到172页。第七章以数据持久化的内容为主,第八章主要是增加了一个嵌套清单,之前已经学过如何创建 table view controller,作者尽可能地用另外一种方法实现同样的效果。

    50. 数据持久化三件事

    1) 找到可以存放文件的路径,创建文件。

    找到路径首页要了解一下iOS的沙盒机制,每个App都有自己的文件目录,不能进入其他App的文件目录里。沙盒机制能够保护手机不受手机病毒的干扰。

    所以,App可以存储数据的文件目录名字为“Document”,Document里的内容会和iTunes或iCloud同步。当发布新的版本后,Document里的内容仍然在。App目录里除了Document之外还有其他的文件夹?有,Library和tmp两个文件夹。Library里都是是cache文件,和偏好设置文件。Library是由系统控制管理的。tmp文件夹里都是临时文件,tmp里的文件都会时不时地被系统清理删除。

    所以,我们就把数据存储到Document里。

    那么,接下来需要的做2件事情:找路径、创建存储文件

        //找路径
        func documentsDirectory() -> String {
            let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
            return paths[0]
        }
    
        //在找到的路径里创建文件
        func dataFilePath() -> String {
            return(documentsDirectory() as NSString).stringByAppendingPathComponent("某某.plist")
        }
    
    

    注意:.DocumentDirectoryDocumentationDirectory的区别。

    我们创建的文件的扩展名为.plist,plist表示Property List ,是XML文件格式,能够存储结构化数据。

    2)把 数据 存放到文件中,每当用户改变了数据时,改变后的数据也能同步存放到数据中。

    我们保存数据需要用到 NSCoder,可以将数据储到结构化格式文件里。将对象转换成文件,再将文件转换回来的过程,就是 Serialization(序列化)。

      func saveChecklists() {
        let data = NSMutableData()
        let archiver = NSKeyedArchiver(forWritingWithMutableData: data)
        archiver.encodeObject(lists, forKey: "Checklists")
        archiver.finishEncoding()
        data.writeToFile(dataFilePath(), atomically: true)
      }
    
    

    方法saveChecklists()用了两步将 items 数组转换成了二进制数据:
    a. NSKeyedArchiver能将数组和 ChecklistItem 转换成二进制文件然后写入对应的文件里。

    b. data放置在NSMutableData对象里,然后将自己写入文件所在的路径中

    最后一点,NSKeyedArchiver知道如何encode一个数组对象,但是并不了解 ChecklistItem,所以,需要让 ChecklistItem 遵守 NSCoding 协议才可以。也就是说,凡是NSKeyedArchiver要encode的对象,都要遵守 NSCoding 协议。或者说,你想让某个对象使用 NSCoder 系统,就要让这个对象遵守 NSCoding 协议。有关 NSCoding 的知识点请见 #53。

    3)应用启动时能够加载数据(取数据)。

      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()
          }
        }
      }
    
    

    51. NSCoding 协议

    协议里有两个方法是必须要实现的:

    • func encodeWithCoder(aCoder: NSCoder) 用来saving 或者 encoding 对象。(存)
    • init?(coder aDecoder: NSCoder)初始化方法,用于创建新的对象,通过从 plist 文件里 loading 或者 decoding 对象来创建对象。(取)
      func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(name, forKey: "Name")
        aCoder.encodeObject(items, forKey: "Items")
      }
    
    

    NSKeyedArchiver尝试encodeChecklistItem对象时,NSKeyedArchiver会给ChecklistItem发送encodeWithCoder(coder)消息。

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

    52. 调试bug小技巧

    有时候出现bug时,Xcode会转换到 debugger 情景下,显示哪一行代码导致了程序崩溃。不过有时候会显示是 AppDelegate 的问题,如下图:

    这对改bug来说可没有什么帮助。那怎么办呢?见下图:

    Breakpoint navigator -> 点击 +

    然后再次 Run,Xcode就会显示真正导致crash的代码行了。

    53.题外话

    • 作者推荐了一个Mac软件:TextWrangler。
    • 在Xcode里如果遇到看不懂的方法,按住Alt/Option键,点击这个代码即可出现帮助信息。
    • 帮你区分两个文件中代码异同的小工具:Xcode -> Open Developer Tool -> FileMerge

    54. Initializers 构造器

    在创建新的对象时,才需要 init 方法。比如:当用户点击+时,用init()来创建 ChecklistItem,用init?(coder) 将 ChecklistItems存储到硬盘上。

    init() 的标准步骤:

    init() {
        //给常量或变量实例赋值
        super.init()
        //其他初始化代码,比如调用方法,写在这里就好。必须在super.init()之后,不然报错
    }
    

    init 方法不用 func 关键词开头。

    override initrequired init?, 一个对象A是对象B的子类,如果要在对象A里添加init方法,前面需要有 override或者required,比如:

      init(name: String) {
        self.name = name
        super.init()
      }
    
    
      override init() {
        super.init()
      }
    
    

    当init有问号时,表示当 init 失败时,会返回nil。如果plist文件里没有足够的信息,decoding一个对象就会失败。

    当你声明一个变量或者常量时,需要给常量或变量一个初始值。

    如果声明了变量,却没有给出初始值,只给出了类型,比如:

    var checked: Bool
    

    这样的话,必须要在init方法里给变量赋值。不然,Swift会报错(Optional 类型的变量除外)。

    在给所有的变量常量实例都赋值后,就可以调用 super.init() 方法来初始化这个对象的superclass(父类)。之后,就可以写其他初始化代码,比如调用某些方法,必须在super.init()之后,不然报错。

    虽然 Swift 的初始化规则看起来比较复杂,还好,要你你忘了提供init方法,编译器会提示你的。

    最后以 table view controller 举例, table view controller 和很多其他的对象一样,会有多个 init 方法:

    • init?(coder):view controller 自动从 storyboard 中载入
    • init(nibName, bundle):你想手动从一个nib文件中载入 view controller
    • init(style):你想不使用 storyboard 或 nib 来创建 table view controller。
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    

    注意到 init?(coder)的参数有些奇怪了吗,外部标签和内部标签和其他的方法不太一样。coder标签是方法名字的一部分,方法参数是aDecoder
    当年调用super.init方法,用coder标签表示super的初始化方法的参数,从aDecoder来的对象作为参数的值。这句话可能不太好理解,可能是翻译错了,附上原文:

    When you call super.init, you use the label coder to refer to the parameter of super's init method, and the object from aDecoder as that parameter's value.

    总结一下 init 方法三步骤:
    1)确保实例变量有值
    2)调用 superclass 的 init(),
    3)调用其他的方法

    55. 创建 table view cell 的四种方法

    方法一:使用 prototype cells

    在storyboard中找到cell,输入identifier:ChecklistItem,然后写代码:

    let cell = tableView.dequeueReusableCellWithIdentifier("ChecklistItem", forIndexPath: indexPath)
    

    注意dequeueReusableCellWithIdentifier方法里有参数forIndexPath,只能用在 prototype cells 中。

    方法二:使用静态cell(static cells)

    已经确定有哪些cell,而且内容不会变动。

    方法三:使用nib文件

    nib,也就是XIB,有点像是迷你型的storyboard,里面包含定制的 UITableViewCell 对象。

    方法四:手动创建

    
        let cellIdentifier = "Cell"
        if let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) {
          return cell
        } else {
          return UITableViewCell(style: .Default, reuseIdentifier: cellIdentifier)
        }
    
    

    注意dequeueReusableCellWithIdentifier方法里没有参数。

    这样可能不太深刻,实际使用的时候是什么样子呢?如下:

    func cellForTableView(tableView: UITableView) -> UITableViewCell {
        let cellIdentifier = "Cell"
    
        if let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) {
            return cell
        } else {
            return UITableViewCell(style: .Default, reuseIdentifier: cellIdentifier)
        }
    }
     
      override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = cellForTableView(tableView)
    
        let checklist = lists[indexPath.row]
        cell.textLabel!.text = checklist.name
        cell.accessoryType = .DetailDisclosureButton
        
        return cell
      }
    
    

    总之,对于 UITabieViewCell,我有一个忠告:
    尽可能的复用cell(reuse cells)
    尽可能的复用cell(reuse cells)
    尽可能的复用cell(reuse cells)
    重要的事情说三遍。

    56. 新方法之点击跳转界面的同时传值(一)

    首先,storyboard中,黄点拖动(见下图),输入Identifier:ShowChecklist。

    然后写代码:

      override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    
        performSegueWithIdentifier("ShowChecklist", sender: nil)
      }
    
    

    上面代码中有 sender,借用sender可以传值(这个功能是重点,省时省力好帮手),如下:

      override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let checklist = lists[indexPath.row]
        performSegueWithIdentifier("ShowChecklist", sender: checklist)
      }
    
    

    理解这两行代码非常关键。

    当然,还不能忘了 prepare�ForSegue(sender) 方法。

    override func prepareForSegue(sender: UIStoryboardSegue, sender: AnyObject?) {
       if segue.identifier == "ShowChecklist" {
           let controller = segue.destinationViewController as! ChecklistViewController {
               controller.checklist = sender as! Checklist //看,用上 sender 了!
           }
       }
    }
    

    当然了,ChecklistViewController里一定要声明(声明里为什么要有叹号,在后面会提及):

    var checklist: Checklist!
    

    好了,上面就是所有的步骤了。

    接下来说一下上述步骤中涉及的一些知识点,先看图,看看实际上 perform 一个 sugue 涉及多少步骤,然后讲解知识点:


    • 调用顺序。viewDidLoad()prepareForSegue()之后调用,也就是说,先调用prepareForSegue(),然后再调用viewDidLoad()
    • checklist为什么要有叹号。加一个叹号可以允许 checklist 暂时为 nil 直到 viewDidLoad() 被调用。

    在#59里,会介绍另外一种也就是第三种跳转页面并且传值的方法。

    57. 创建自己的构造器(init 方法)

    var list = Checklist()
    list.name = "Name of the checklist"
    

    想把上面的两行变成下面这一行,该怎么做呢?

    list = Checklist(name: "Name of the checklist")
    

    需要写一个自己的 init 方法,让 name 作为一个参数:

    init(name: String) {
        self.name = name
        super.init()
    }
    

    这个构造器的作用就是把参数 name 赋值给实例变量(self.name)。
    self.name 指的是当前 Checklist 对象的变量 name。

    创建这个构造器的好久就是,可以保证每次我创建新的 Checklist 对象时,都一定会有 name 属性。

    58. Type Cast(类型检查)

    类型检查的目的是让 Swift 把某个值拥有不同的数据类型。

    59. 新方法之点击跳转界面的同时传值(二)

    点击 cell 里的 Accessory,除了用 storyboard 之外,还可以用:

      override func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) {
    
      }
    
    

    点击 cell 的 Accessory 跳转界面并且传值的方法如下:
    先到 storyboard 中找到你要跳转的目的地界面,然后如下图;


    在 Storyboard ID 中输入对应的 Identity,然后写代码:

      override func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) {
        let navigationController = storyboard!.instantiateViewControllerWithIdentifier("ListDetailNavigationController") as! UINavigationController
        
        let controller = navigationController.topViewController as! ListDetailViewController
        controller.delegate = self
        
        let checklist = dataModel.lists[indexPath.row]
        controller.checklistToEdit = checklist
        
        presentViewController(navigationController, animated: true, completion: nil)
      }
    
    

    其中关键代码两行:

    let navigationController = storyboard!.instantiateViewControllerWithIdentifier("ListDetailNavigationController") as! UINavigationController
    
     presentViewController(navigationController, animated: true, completion: nil)
    

    这个方法非常好用~

    相关文章

      网友评论

      • 死神一护:这个教程我已经写了三遍了,好经典
        sing_crystal:@死神一护 目前在北京
        死神一护:@sing_change ray的这个教程,我之前完完整整写过三遍代码,收获良多。楼主你在哪座城市工作
        sing_crystal:@死神一护 你指的是写了三遍代码还是文章?

      本文标题:【总结回顾】iOS Apprentice Tutorial 2:

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