iOS 的沙盒机制和数据持久化基本介绍

96
Laughingg
0.1 2016.06.08 15:53* 字数 3431

1、 沙盒概念基本介绍

iOS 应用程序只能在该 app 的文件系统中读取。这个默认的 app 文件系统就是我们说的沙盒。
所有的非代码文件都保存在这个地方,比如图片、声音、属性列表和文本文件等。
出于安全的考虑,iOS 系统的沙盒机制规定

  1. 每个 app 都在自己的沙盒里面
  2. 每个 app 只能访问自己 app 下的沙盒下的资源,不能跨越沙盒访问其他应用的沙盒
  3. app 需要向外请求数据或接收数据需要授权或认证
这张图可以用来理解沙盒机制

2. 沙盒目录

沙盒目录在模拟器下面的文件夹中。模拟器是在一个隐藏的资源库文件夹中。
2.1 显示隐藏文件 mac 配置

# 显示Mac隐藏文件的命令:
$ defaults write com.apple.finder AppleShowAllFiles -bool true

# 隐藏Mac隐藏文件的命令:
$ defaults write com.apple.finder AppleShowAllFiles -bool false

2.2 模拟器文件夹的查找
打开我们的 Finder

这里样可以找到 我们的 bundle 和 date 文件夹

主 bundle 目录


Snip20160608_55.png

沙盒目录


Snip20160608_56.png

由于对于某些文件夹做了了 md5 处理,要找到 app 对应的沙盒文件夹不是那么的容易。
关于沙盒路径我们一般有两种方法来获取

  1. 在项目中打印沙盒路径
// 沙盒路径
let homePath  = NSHomeDirectory()
print(homePath)
// 打印的路径
/Users/xxxx/Library/Developer/CoreSimulator/Devices/7113BE0C-E9CD-415E-99AA-DE4D7BC129A9/data/Containers/Data/Application/0CD943FA-B110-4EB5-BF18-A78A9BB52A31

打开 Finder ——> 菜单栏中的 前往 ——> 前往文件夹 ——> 输入路径回车就可以跳转到目标文件夹

  1. 使用工具软件

simPholders官网网址 土豪建议购买,软件的兼容性还是可以得到保障的。

Snip20160608_57.png

这个软件收费,我是很不理解的。
作为一个天朝人,你知道的。百度有各种破解的。

先要在模拟器上运行一下 app 后,在点击菜单栏中的下面标识的图标,点击的你对应的 app ,你就可以直接跳转到对应的沙盒中。


Snip20160608_58.png

2.3 沙盒目录的介绍
(iOS 8 开始 bundle和沙盒不在一个文件夹里面。在两个不同的地方)
沙盒文件夹结构:


20150423140748860.png

默认情况下,每个沙盒含有3个文件夹:Documents, Library 和 tmp。因为应用的沙盒机制,应用只能在几个目录下读写文件:

  • Documents: 苹果建议将程序中建立的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此目录。(通常用于保存比较重要的数据,例如游戏应用的存档数据
    注意点:将资源数据保存在这个文件夹中,app 上架时候,可能被拒。 解决方法:设置这个文件夹不会被 iTunes 备份。(百度源码))

  • Library:存储程序的默认设置或其它状态信息;
    Library/Caches:存放缓存文件,iTunes不会备份此目录,此目录下文件不会在应用退出删除。(用来保存运行时生成的需要持久化的数据,但是不重要不需要备份的数据(SDWebImage的缓存资源是保存到这个文件夹里面))
    Library/Preference:保存应用的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录(Preference:偏好设置文件夹。这个文件是由系统管理的。(我们基本不将自己的文件保存到这个文件夹里面)
    在使用这个文件夹的时候,系统提供了相应地方法。)

  • tmp:提供一个即时创建临时文件的地方,iTunes同步设备时不会备份该目录。((这个文件下面的数据不是安全的,可能会被系统清除 )
    主要用来保存当前需要用的数据,下次启动程序的时候比一定要的数据。)

iTunes在与iPhone同步时,备份所有的Documents和Library文件。
iPhone在重启时,会丢弃所有的tmp文件。
在开发中我们使用的最多的时Caches 和 temp 这个文件。Documents 主要是做游戏的时候才会用到(用作游戏的存档)。

其实上面都是一堆废话

Documents:                保存用户相关的数据(删除不会影响 app 使用)
Library:                  保存 app 相关(删除 app 就不能用)
tmp:                      保存一次使用的数据,可有可无的数据,主要是为了节省流量(删不删都可以用)

3、iOS 中数据持久化的常用方式

所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据。

  1. XML 属性列表(plist)归档
    plist文件只能归档(存储)字典和数组,字典和数组里面保存的数据必须是 Boolean,Data,Date,Number,String
    这几种系统自带的对象类型。
    不能存储自定义的对象

  2. Preference(偏好设置)
    Preference 是和 plist 文件类似,可以快速的进行一些键值对的存储。本质是底层封装了一个字典。
    不能存储自定义的对象

  3. NSKeyedArchiver归档(NSCoding) 和 NSKeyedUnarchiver 解档
    NSKeyedArchiver 可以对自定义的对象进行归档和解档。
    和xml(plist)偏好设置比较,更加的适合存储数据。

弊端:
但是NSKeyedArchiver 在进行多个对象归档的时候,显得过于繁琐。
NSKeyedArchiver 的文件的写入和读取是一次性的写入和读取
如果读取和写入的文件过于大,对于内存有限的移动设备,很容易导致app的奔溃。

  1. SQLite3
    SQLite3是一款开源的嵌入式关系型数据库,可移植性好、易使用、内存开销小,性能高,学习成本低。(SQLite3 已经运用到所有的移动端)
    SQLite3 的数据的写入是可以持续的分批次的,你想写多少写多少。读取数据也是可以持续的分批次的,你想读取多少就读取多少。

SQLite3是无类型的,意味着你可以保存任何类型的数据到任意表的任意字段中。
(虽然 SQLite3有常用的5种数据类型:text、integer、float、boolean、blob)对于可以保存二进制数据这一条。
我们就可以很好地利用。我们可以想使用服务器数据一样,直接存取二进制的数据和直接获取二进制的数据。

既然 SQLite3  是一个数据库,我们还以这个数据库对我们的数据进行管理。

有点忧伤的是 SQLite3 是纯 c 语言的。(我们要找第三方封装的oc框架使用,见得友好点)

  1. Core Data
    Core Data:苹果的亲儿子,是对 SQLite 做的面向对象的封装。

存在弊端:
既然是对 SQLite 做的面向对象的封装 ,在其中的 转换的时候是要消耗性能的。

Core Data:用在数据比较复杂的时候是比较好的,封装好一堆很好用的方法可以直接使用,减轻我们的开发难度。

手机端存储的数据越简单越好。这样性能的消耗就越小,对于移动端,处理简单的数据,建议使用 SQLite3 存储数据
plist归档,Preference (偏好设置) NSKeyedArchiver 归档(NSCoding)都是苹果自带的东西,其他的程序和软件是没有的。

  1. Realm
    Realm 是一个跨平台的移动数据库引擎,于 2014 年 7 月发布,准确来说,它是专门为移动应用所设计的数据持久化解决方案之一。
    Realm 并不是对 Core Data 的简单封装,相反地, Realm 并不是基于 Core Data ,也不是基于 SQLite 所构建的。它拥有自己的数据库存储引擎,可以高效且快速地完成数据库的构建操作。

4、沙盒的基本操作

  1. 沙盒文件夹目录的获取
    沙盒文件夹路径的获取方式有两种,一种是使用系统提供的函数,一种是使用文件夹路径的拼接的方式。
    方式一:
    路径拼接
// 沙盒根路径
 let homePath  = NSHomeDirectory()
 print(homePath)
        
 // 沙盒下文件夹路径的拼接
// NSString *documentsPath = [homePath stringByAppendingPathComponent:@"Documents"];  // 这个是 NSString 的方法, homePath 转为 NSString 后可以直接使用,
let documentsPath = homePath.stringByAppendingString("/Documents")
let libraryPath = homePath.stringByAppendingString("/Library")
let libraryCachePath = homePath.stringByAppendingString("/Library/Caches")
// Preferences 的plist 提供了系统类可以直接使用
let libraryPreferencesPath = homePath.stringByAppendingString("/Library/Preferences")
let tmpPath = homePath.stringByAppendingString("/tmp")
        
 print(documentsPath)
 print(libraryPath)
 print(libraryCachePath)
 print(libraryPreferencesPath)
 print(tmpPath)

方式二:
路径查找的函数
苹果为我们提供了一种查找文件夹的函数: NSSearchPathForDirectoriesInDomains函数 。

/*!
 在某个域的文件夹里面搜索路径
 
 - parameter directory:   要查找的文件夹
 - parameter domainMask:  要搜索的文件域
 - parameter expandTilde: 路径是否展开
 
 - returns: 文件夹中查找到的路径数组
 */
public func NSSearchPathForDirectoriesInDomains(directory: NSSearchPathDirectory, _ domainMask: NSSearchPathDomainMask, _ expandTilde: Bool) -> [String]

文件夹枚举

public enum NSSearchPathDirectory : UInt {
    
    case ApplicationDirectory // supported applications (Applications)
    case DemoApplicationDirectory // unsupported applications, demonstration versions (Demos)
    case DeveloperApplicationDirectory // developer applications (Developer/Applications). DEPRECATED - there is no one single Developer directory.
    case AdminApplicationDirectory // system and network administration applications (Administration)
    case LibraryDirectory // various documentation, support, and configuration files, resources (Library)
    case DeveloperDirectory // developer resources (Developer) DEPRECATED - there is no one single Developer directory.
    case UserDirectory // user home directories (Users)
    case DocumentationDirectory // documentation (Documentation)
    case DocumentDirectory // documents (Documents)
    case CoreServiceDirectory // location of CoreServices directory (System/Library/CoreServices)
    @available(iOS 4.0, *)
    case AutosavedInformationDirectory // location of autosaved documents (Documents/Autosaved)
    case DesktopDirectory // location of user's desktop
    case CachesDirectory // location of discardable cache files (Library/Caches)
    case ApplicationSupportDirectory // location of application support files (plug-ins, etc) (Library/Application Support)
    @available(iOS 2.0, *)
    case DownloadsDirectory // location of the user's "Downloads" directory
    @available(iOS 4.0, *)
    case InputMethodsDirectory // input methods (Library/Input Methods)
    @available(iOS 4.0, *)
    case MoviesDirectory // location of user's Movies directory (~/Movies)
    @available(iOS 4.0, *)
    case MusicDirectory // location of user's Music directory (~/Music)
    @available(iOS 4.0, *)
    case PicturesDirectory // location of user's Pictures directory (~/Pictures)
    @available(iOS 4.0, *)
    case PrinterDescriptionDirectory // location of system's PPDs directory (Library/Printers/PPDs)
    @available(iOS 4.0, *)
    case SharedPublicDirectory // location of user's Public sharing directory (~/Public)
    @available(iOS 4.0, *)
    case PreferencePanesDirectory // location of the PreferencePanes directory for use with System Preferences (Library/PreferencePanes)
    
    @available(iOS 4.0, *)
    case ItemReplacementDirectory // For use with NSFileManager's URLForDirectory:inDomain:appropriateForURL:create:error:
    case AllApplicationsDirectory // all directories where applications can occur
    case AllLibrariesDirectory // all directories where resources can occur
}

域枚举

public struct NSSearchPathDomainMask : OptionSetType {
    public init(rawValue: UInt)
    
    public static var UserDomainMask: NSSearchPathDomainMask { get } // user's home directory --- place to install user's personal items (~)
    public static var LocalDomainMask: NSSearchPathDomainMask { get } // local to the current machine --- place to install items available to everyone on this machine (/Library)
    public static var NetworkDomainMask: NSSearchPathDomainMask { get } // publically available location in the local area network --- place to install items available on the network (/Network)
    public static var SystemDomainMask: NSSearchPathDomainMask { get } // provided by Apple, unmodifiable (/System)
    public static var AllDomainsMask: NSSearchPathDomainMask { get } // all domains: all of the above and future items
}

通过文件夹枚举和域枚举可以获取我们想要的路径,但是在 iOS 的 app 中能够使用的不多,大部分的都是用在 Mac 的应用。

在 iOS app 中使用路径搜索的只有 **Documents 和 Library/Caches **这两个。
偏好设置是有系统类直接管理,tmp 的路径有一个类可以直接使用

let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last
let libraryCachePath = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true).last
print(documentsPath)
print(libraryCachePath)

Library/Preference

Library/Preference:是由系统管理的,系统直接提供一个 NSUserDefaults类来管理。

tmp

let tmpPath = NSTemporaryDirectory()
print(tmpPath)
  1. 数据的读写
    2.1 属性列表
    2.1.1 属性列表读写的介绍
    属性列表是一种XML格式的文件,拓展名为plist(苹果特有的格式)


    字典类型数据的读写实例图(数组也是类似的)

plist存储数据的注意点:
plist文件只能归档(存储)字典和数组,字典和数组里面保存的数据必须是 Boolean,Data,Date,Number,String 这几种系统自带的对象类型。
就可以使用writeToFile:atomically:方法直接将对象写到属性列表文件中。(我们自己创建的对象要通过归档和解档的方式来存储)

plist 存储的数据类型示例图

(plist文件存储 ,只要一个对象有writeToFile方法,就可以保存到Plist。)

2.1.2 数据写入
这里举例的是 NSArray , NSDictonary 也是一样的操作。

 let data = ["张三", "李四", "王五"] as NSArray
 let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last
  let filePath = documentsPath?.stringByAppendingString("/data.plist")
  /*
 atomically:  这个是否原则性存储。 
      true:  当写入到文件夹的数据不完整的时候不保存为文件。
      false:当写入到文件夹的数据不完整的时候,也将数据保存为文件。(可能产生垃圾数据)
  */
  data.writeToFile(filePath!, atomically: true)
Snip20160612_59.png

2.1.3 数据的读取

let cacheData = NSArray(contentsOfFile: filePath!)
print(cacheData)

 // 打印结果
 Optional(<__NSCFArray 0x7fbbf8d2dee0>(
张三,
李四,
王五
)
)

2.2 偏好设置
2.2.1 偏好设置读写的介绍
偏好设置,一般是用来保存 app 的配置信息, 比如保存用户名、是否保存密码、字体大小等设置。

系统会提供一个 NSUserDefaults 类对偏好设置进行管理,这个类是专门用来偏好设置的存储。

偏好设置存储是简单的做以一些键值对的设置就可以了(字典)
偏好设置底层就是包装了一个字典(使用plist进行存储)

使用偏好设置的好处:
1、偏好设置存储是不用关心存储的文件名()
2、快速的进行一些键值对的存储
偏好设置存储使用的注意点:
1. 在ios8之前,进行偏好设置的存储,我们需要做一个同步操作 (同步:将缓存数据保存到硬盘上)
2. 偏好设置是用来保存 app 的配置信息,一般不要在偏好设置中保存其他数据。

2.2.2 数据写入

// 获取全局单利
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setValue("张三", forKey: "name")
userDefaults.setValue("20", forKey: "age")
userDefaults.setValue("180", forKey: "height")
      
// 同步写入数据(避免数据写入失败)
userDefaults.synchronize()
Snip20160612_60.png

2.2.3 数据的读

let userDefaults = NSUserDefaults.standardUserDefaults()
let name = userDefaults.valueForKey("name")
let age = userDefaults.valueForKey("age")
let height = userDefaults.valueForKey("height")
print(name)
print(age)
print(height)

 // 打印结果
Optional(张三)
Optional(20)
Optional(180)

2.3 NSKeyedArchiver
2.3.1 NSKeyedArchiver 使用说明
不是所有的对象都可以直接用这种方法进行归档,只有遵守了NSCoding协议 实现了NSCoding协议的2个方法的对象才可以。

如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver 进行归档和恢复。

协议方法

public protocol NSCoding {
  // 每次归档对象时,都会调用这个方法。
  // 一般在这个方法里面指定如何归档对象中的每个实例变量,可以使用encodeObject:forKey:方法归档实例变量
  public func encodeWithCoder(aCoder: NSCoder)
 
 // 每次从文件中恢复(解码)对象时,都会调用这个方法。
 // 一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用decodeObject:forKey方法解码
  public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
}

2.3.2.1 NSArray 的数据归档

let data = ["张三", "李四", "王五"] as NSArray
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last
let filePath = documentsPath?.stringByAppendingString("/data.data")
data.writeToFile(filePath!, atomically: true)
        
 // 归档数据
 NSKeyedArchiver.archiveRootObject(data, toFile: filePath!)
Snip20160612_61.png

2.3.2.1 NSArray 的数据解档

// 解档数据
let cacheData = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath!) as! NSArray as Array  // 转换两下是为了打印好看
print(cacheData)

 // 打印结果
[张三, 李四, 王五]

** 2.3.3.1 自定义对象的归档解档** 重点
我们自定义的对象要存储,我们要使用归档。
我们要将我们保存的自定义对象的归档的文件进行解析就需要解档。
自定义对象默认是没有遵守 NSCoding 协议。

2.3.3.2 自定义对象归档

// 自定义对象
let p = Person()
p.name = "张三"
p.age = "20"
p.height = "180"    
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last

 // 文件的后缀可以随便写,一般写 data,写其他的后缀只是打不开而已
let filePath = documentsPath?.stringByAppendingString("/p.data")
// 自定义数据归档
 NSKeyedArchiver.archiveRootObject(p, toFile: filePath!)
Snip20160612_63.png

2.3.3.3 自定义对象的对象解档

let cacheP = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath!) as! Person
print(cacheP.name)
print(cacheP.age)
print(cacheP.height)

 // 打印结果
张三
20
180

2.3.3.4 自定义对象的处理

注意:要对一个对象进行归档和解档,
1、这个对象必须遵守 < NSCoding>协议。
2、必须实现 encodeWithCoder: 这个归档的方法 和 initWithCoder 这个解档方法。

import UIKit
/*
一个对象归档必须先遵守: NSCoding
*/
class Person: NSObject, NSCoding {
  var name: String = ""
  var age: String = ""
  var height: String = ""

  /*
   默认构造方法,当实现了其他的构造方法的时候,默认构造方法不会自动生成
   */
  override init() {
      
  }
  
  // MARK: -  NSCoding 协议必须实现的方法
  // 自定义对象必须实现这个方法,(那些属性需要归档,归档时怎么存储)
  func encodeWithCoder(aCoder: NSCoder) {

    /*
      什么时候需要调用 super :
            父类遵守<NSCoding> 协议的时候是需要调用 super。 需要对父类的属性进行归档。要不然父类的属性值就不能进行存储
      */
      aCoder .encodeObject(name, forKey: "name")
      aCoder .encodeObject(age, forKey: "age")
      aCoder .encodeObject(height, forKey: "height")
  }
  
// 自定义对象解档必须实现这个方法,(那些属性需要解档,解档后怎么赋值)
  required init?(coder aDecoder: NSCoder) {
      
      /*
      什么时候需要调用 super :
            父类遵守<NSCoding> 协议的时候是需要调用 super。 需要对父类的属性进行解档,否则父类的属性值会为 空,造成程序崩溃。
      */
      name = aDecoder.decodeObjectForKey("name") as! String
      age = aDecoder.decodeObjectForKey("age") as! String
      height = aDecoder.decodeObjectForKey("height") as! String
  }
}

2.3.4 多对象的归档和解档( NSData )
2.3.4.1 多对象的归档和解档的原因
使用archiveRootObject:toFile:方法可以将一个对象直接写入到一个文件中,但有时候可能想将多个对象写入到同一个文件中,那么就要使用NSData来进行归档对象。NSData可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。可以使用[NSMutableData data]创建可变数据空间。

20150423213634951.png

2.3.4.2 data 多对象的归档
```swift
let p1 = Person()
p1.name = "张三"
p1.age = "20"
p1.height = "180"
let p2 = Person()
p2.name = "李四"
p2.age = "20"
p2.height = "180"

let data = NSMutableData()

// 将 data 数据区连接到一个NSKeyedArchiver对象
let archiver = NSKeyedArchiver(forWritingWithMutableData: data)
archiver.encodeObject(p1, forKey: "p1")
archiver.encodeObject(p2, forKey: "p2")

// 数据存档结束
archiver.finishEncoding()

// 写入文件
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last
let filePath = documentsPath?.stringByAppendingString("/ps.data")
data.writeToFile(filePath!, atomically: true)

![Snip20160612_64.png](http://upload-images.jianshu.io/upload_images/446092-e97be246c033d90d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
2.3.4.3 data 的解档
```swift
let cacheData = NSMutableData(contentsOfFile: filePath!)
let cacheUnarchiver = NSKeyedUnarchiver(forReadingWithData: cacheData!)
let cacheP1 = cacheUnarchiver.decodeObjectForKey("p1") as! Person
let cacheP2 = cacheUnarchiver.decodeObjectForKey("p2") as! Person
        
 print(cacheP1.name)
 print(cacheP2.name)
        
 // 结束解档
 cacheUnarchiver.finishDecoding()

注意:
上面介绍的数据持久化的方式是文件操作的方式,数据的读取时一次性的读取全部数据,数据写入时覆盖性的。当文件中保存的数据过多的时候,读取的时间过长,内存暴增,程序崩溃。写入时间过长,容易造成数据写入失败。不适合进行大量数据的存储。

还有一个致命的缺点:
不能对保存的数据进行索引

关于 SQLite ,CoreData ,reaml 的使用,请查看后续的章节。

iOS 文件系统学习:
总结:ios数据持久化存储&文件系统编程