iOS 的沙盒機制和數據持久化基本介紹

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數據持久化存儲&文件系統編程

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容