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->前往 -> 前往文件夹中,查看文档。


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文件,打开后会发现是我们刚刚存入的水果名称。


plist1.png
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可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。
缺点: 归档的形式来保存数据,只能一次性归档保存以及一次性解压。所以只能针对小量数据,而且对数据操作比较笨拙,即如果想改动数据的某一小部分,还是需要解压整个数据或者归档整个数据。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容