iOS ~ Swift之数据存储(.plist,NSUserDefaults,NSKeyedArchiver)

最近公司项目中使用了本地存储,所以趁着周六日来整理一下在Swift中的几种数据存储方式。

一、plist文件存储

存储目录:Documents

每次在Xcode中新建一个iOS项目后,都会自己产生一个.plist文件,里面记录项目的一些配置信息。我们也可以自己创建.plist文件来进行数据的存储和读取。
.plist文件其实就是一个XML格式的文件,支持的数据类型有NSDictionary、NSArray、Boolean、NSData、NSDate、NSNumber、NSString这些类型。

在这里简单说一下关于应用程序各个文档所在的目录:
  • Home目录 ./

获取方式:
let homePath = NSHomeDirectory()
打印出来以后,可以复制到Finder->前往 -> 前往文件夹中,查看文档。


iOS ~ Swift之数据存储(.plist,NSUserDefaults,NSKeyedArchiver)_第1张图片
home.png

正如我们看到的,在home下有三个文件夹,下面我们分别来看一下这三个目录。

  • 1.Documents目录 ./Documents

用户文档目录,苹果建议将程序中建立的或者在程序中浏览到的文件数据保存到该目录下,iTunes备份和恢复的时候会包括此目录
获取方式:
//方法1
//swift 2
let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0].stringByAppendingString("user")
//swift 3
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0].appending("user")
//方法2
let ducumentPath2 = NSHomeDirectory() + "/Documents"

  • 2.Library目录 ./Library

这个目录下有两个子目录:Caches 和 Preferences
Library/Preferences目录,包含应用程序的偏好设置文件。iOS的Settings(设置)应用会在该目录中查找应用的设置信息。不应该直接创建偏好设置文件,而是应该使用NSUserDefaults类来取得和设置应用程序的偏好。
Library/Caches目录,主要存放缓存文件,iTunes不会备份此目录,此目录下文件不会再应用退出时删除。一般存储体积大、不需要备份的非重要数据。
//Library目录-方法1
let libraryPaths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.LibraryDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let libraryPath = libraryPaths[0] as! String
//Library目录-方法2
let libraryPath2 = NSHomeDirectory() + "/Library"

  //Cache目录-方法1
  let cachePaths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomainMask.UserDomainMask, true)
  let cachePath = cachePaths[0] as! String
  //Cache目录-方法2
  let cachePath2 = NSHomeDirectory() + "/Library/Caches"
  • 3.tmp目录 ./tmp

保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。重启后清空。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录。
//方法1
let tmpDir = NSTemporaryDirectory()
//方法2
let tmpDir2 = NSHomeDirectory() + "/tmp"

说了半天还没有给大家介绍怎么把数据写入.plist文件中,我们下面以数组为例,来看一下:
    let array = NSArray(objects: "apple","orange","pear","banana","watermelon")
    let filePath:String = NSHomeDirectory() + "/Documents/fruit.plist"
    array.writeToFile(filePath, atomically: true)

这个时候我们会看到在Documents文件夹下面产生了一个.plist文件,打开后会发现是我们刚刚存入的水果名称。


iOS ~ Swift之数据存储(.plist,NSUserDefaults,NSKeyedArchiver)_第2张图片
plist1.png
iOS ~ Swift之数据存储(.plist,NSUserDefaults,NSKeyedArchiver)_第3张图片
plist2.png
存入plist后我们来读取一下fruit.plist中的数据:
let fruitArr = NSArray(contentsOfFile: NSHomeDirectory() + "/Documents/fruit.plist")

同理,我们还可以存入.plist文件支持的其他类型的数据。

缺点:用.plist文件只能存储含有writeToFile:方法的对象,局限性。

二、NSUserDefaults

存储目录:Library/Preference

用来保存应用程序设置和属性、用户保存的数据,用户再次打开程序或开机后这些数据仍然存在。NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、 NSDictionary。如果要存储其他类型,则需要转换为前面的类型,才能用NSUserDefaults存储。很多iOS应用都支持偏好设置,比如保存用户名、密码、字体大小等设置,iOS提供了一套标准的解决方案来为应用加入偏好设置功能,每个应用都有个NSUserDefaults实例,通过它来存取偏好设置,比如,保存用户名、字体大小、是否自动登录等。

示例:
  • 1.对原生数据类型的存储和读取
    var userDefault = NSUserDefaults.standardUserDefaults()

    //AnyObject
    userDefault.setObject("apple", forKey: "Object")
    var objectValue:AnyObject? = userDefault.objectForKey("Object")
    
    //Int类型
    userDefault.setInteger(12345, forKey: "Int")
    var intValue = userDefault.integerForKey("Int")
    
    //Float类型
    userDefault.setFloat(3.2, forKey: "Float")
    var floatValue = userDefault.floatForKey("Float")
    
    //Double类型
    userDefault.setDouble(5.2240, forKey: "Double")
    var doubleValue = userDefault.doubleForKey("Double")
    
    //Bool类型
    userDefault.setBool(true, forKey: "Bool")
    var boolValue = userDefault.boolForKey("Bool")
    
    //NSURL类型
    userDefault.setURL(NSURL(string:"http://baidu.com")!, forKey: "NSURL")
    var urlValue = userDefault.URLForKey("NSURL")
    
    //NSString类型
    userDefault.setObject("apple", forKey: "NSString")
    var nsStringValue = userDefault.objectForKey("NSString") as! NSString
    
    //NSNumber类型
    var number:NSNumber = NSNumber(int:22)
    userDefault.setObject(number, forKey: "NSNumber")
    number = userDefault.objectForKey("NSNumber") as! NSNumber
    
    //NSArray类型
    var array:NSArray = NSArray(array: ["123","456"])
    userDefault.setObject(array, forKey: "NSArray")
    
    //NSDictionaryy类型
    var dictionary:NSDictionary = NSDictionary(dictionary: ["1":"apple"])
    userDefault.setObject(dictionary, forKey: "NSDictionary")
    dictionary = userDefault.objectForKey("NSDictionary") as! NSDictionary
    
  • 2.系统对象的存储与读取
    系统对象实现存储,需要通过 archivedDataWithRootObject 方法转换成 NSData 为载体,才可以存储。下面以 UIImage 对象为例:
    let userDefault = NSUserDefaults.standardUserDefaults()
    //UIImage对象存储
    //将对象转换成NSData流
    let image = UIImage(named: "apple.png")
    let imageData:NSData = NSKeyedArchiver.archivedDataWithRootObject(image!)
    //存储NSData对象
    userDefault.setObject(imageData, forKey: "imageData")
    //设置同步
    userDefault.synchronize()

     //UIImage对象读取
     //获取NSData
     let objData:NSData = userDefault.objectForKey("imageData") as! NSData
     //还原对象
     let myImage = NSKeyedUnarchiver.unarchiveObjectWithData(objData) as! UIImage
     print(myImage)
    
  • 3.自定义对象的存储和读取
    如果想要存储自己定义的类,首先需要对该类实现 NSCoding 协议来进行归档和反归档(序列化和反序列化)。即该类内添加 func encodeWithCoder(_encoder:NSCoder) 方法和 init(coder decoder:NSCoder) 方法,将属性进行转换。
    首先,我们定义一个model来存储用户的信息:
    //----- 自定义对象类 -----
    class UserInfo: NSObject {
    var name:String
    var phone:String

       //构造方法
        init(name:String="",phone:String=""){
           self.name = name
           self.phone = phone
           super.init()
        }
    
       //从nsobject解析回来
        init(coder aDecoder:NSCoder!){
           self.name=aDecoder.decodeObjectForKey("Name") as! String
           self.phone=aDecoder.decodeObjectForKey("Phone") as! String
        }
    
        //编码成object
       func encodeWithCoder(aCoder:NSCoder!){
          aCoder.encodeObject(name,forKey:"Name")
          aCoder.encodeObject(phone,forKey:"Phone")
       }
    }
    

下面我们来存储自定义对象
let userDefault = NSUserDefaults.standardUserDefaults()

    //自定义对象存储
    let model = UserInfo(name: "猫咪", phone: "18800000000")
    //实例对象转换成NSData
    let modelData:NSData = NSKeyedArchiver.archivedDataWithRootObject(model)
    //存储NSData对象
    userDefault.setObject(modelData, forKey: "myModel")
    //设置同步
    userDefault.synchronize()
    
    //自定义对象读取
    let myModelData = userDefault.objectForKey("myModel") as! NSData
    _ = NSKeyedUnarchiver.unarchiveObjectWithData(myModelData) as! UserInfo

当我们退出登录的时候,我们可能会想清除本地的数据,我们可以通过removeObjectForKey() 方法可以删除已保存的数据。当然如果这个存储对象不存在也不会报错。
NSUserDefaults.standardUserDefaults().removeObjectForKey("myModel")

注意:UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法强制写入userDefault.synchronize()
缺点: 本质还是plist文件存储,相对于plist文件存储来讲存储数据更快捷.

三、NSKeyedArchiver(NSCoding)

如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复,不是所有的对象都可以直接用这种方法进行归档,只有遵守了NSCoding协议的对象才可以。
NSCoding协议有2个方法:

encodeWithCoder:每次归档对象时,都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量,可以使用encodeObject:forKey:方法归档实例变量;
initWithCoder:每次从文件中恢复(解码)对象时,都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用decodeObject:forKey方法解码实例变量。
示例:
// 解档 归档
required init?(coder aDecoder: NSCoder) {
    access_token = aDecoder.decodeObjectForKey("access_token") as! String
    expires_in = aDecoder.decodeDoubleForKey("expires_in")
    expiresDate = aDecoder.decodeObjectForKey("expiresDate") as! NSDate
    uid = aDecoder.decodeObjectForKey("uid") as! String
    name = aDecoder.decodeObjectForKey("name") as? String
    avatar_large = aDecoder.decodeObjectForKey("avatar_large") as? String
}
func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(access_token, forKey: "access_token")
    aCoder.encodeDouble(expires_in, forKey: "expires_in")
    aCoder.encodeObject(expiresDate, forKey: "expires_in")
    aCoder.encodeObject(uid, forKey: "uid")
    aCoder.encodeObject(name, forKey: "name")
    aCoder.encodeObject(avatar_large, forKey: "avatar_large")
}
归档路径,swift中的类常量都是使用 static 来定义的
 static let accountPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last!.stringByAppendingPathComponent("account.plist")
在OC中,要注意:
如果父类也遵守了NSCoding协议,请注意:
应该在encodeWithCoder:方法中加上一句[super encodeWithCode:encode];确保继承的实例变量也能被编码,也能被归档;
应该在initWithCoder:方法中加上一句self = [super initWithCoder:decoder];确保继承的实例变量也能被解码,即也能被恢复; 
但有时候可能想将多个对象写入到同一个文件中,那么就要使用NSData来进行归档对象,NSData可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。
缺点: 归档的形式来保存数据,只能一次性归档保存以及一次性解压。所以只能针对小量数据,而且对数据操作比较笨拙,即如果想改动数据的某一小部分,还是需要解压整个数据或者归档整个数据。

这一部分先整理到这里,欢迎大家来指出不足~~~~~( _ )/~~拜拜

你可能感兴趣的:(iOS ~ Swift之数据存储(.plist,NSUserDefaults,NSKeyedArchiver))