iOS14開發-數據存儲

Bundle

簡單理解就是資源文件包,會將許多圖片、xib、文本文件組織在一塊兒,打包成一個 Bundle 文件,這樣能夠在其餘項目中引用包內的資源。sql

// 獲取當前項目的Bundle
let bundle = Bundle.main

// 加載資源
let mp3 = Bundle.main.path(forResource: "xxx", ofType: "mp3")
複製代碼

沙盒

每個 App 只能在本身的建立的文件系統(存儲區域)中進行文件的操做,不能訪問其餘 App 的文件系統(存儲區域),該文件系統(存儲區域)被成爲沙盒。全部的非代碼文件都要保存在此,例如圖像,圖標,聲音,plist,文本文件等。數據庫

沙盒機制保證了 App 的安全性,由於只能訪問本身沙盒文件下的文件。swift

Home目錄

沙盒的主目錄,能夠經過它查看沙盒目錄的總體結構。跨域

// 獲取程序的Home目錄
let homeDirectory = NSHomeDirectory()
複製代碼

Documents目錄

保存應用程序運行時生成的持久化數據。可被iTunes備份,可備份到 iCloud。數組

// 方法1
let documentPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentPath = documentPaths[0]
 
// 方法2
let documentPath2 = NSHomeDirectory() + "/Documents"
複製代碼

上面的獲取方式最後獲得的是String,若是但願獲取的是URL,能夠經過下面的方式:緩存

let manager = FileManager.default
let urlForDocument = manager.urls(for: .documentDirectory, in:.userDomainMask)
let url: URL = urlForDocument[0]
複製代碼

NSSearchPathForDirectoriesInDomains

  • 訪問沙盒目錄經常使用的函數,它返回值爲一個數組,在 iOS 中因爲只有一個惟一路徑,因此直接取數組第一個元素便可。
func NSSearchPathForDirectoriesInDomains( _ directory: FileManager.SearchPathDirectory, _ domainMask: FileManager.SearchPathDomainMask, _ expandTilde: Bool) -> [String]
複製代碼
  • directory:指定搜索的目錄名稱。
  • domainMask:搜索主目錄的位置。userDomainMask 表示搜索的範圍限制於當前應用的沙盒目錄(參考定義註釋)。
  • expandTilde:是否獲取完整的路徑。
let documentPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, false)
let documentPath = documentPaths[0] // ~/Documents

let documentPaths2 = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentPath2 = documentPaths2[0] // /Users/yangfan/Library/Developer/XCPGDevices/982B6CBA-747B-4831-9D87-F82160197333/data/Containers/Data/Application/56C657D5-B36B-449D-AC6C-E2417EA65D00/Documents
複製代碼

Library目錄

存儲程序的默認設置和其餘信息,其下有兩個重要目錄:安全

  • Library/Preferences 目錄:包含應用程序的偏好設置文件。不該該直接建立偏好設置文件,而是應該使用UserDefaults類來取得和設置應用程序的偏好。
  • Library/Caches 目錄:主要存放緩存文件,此目錄下文件不會在應用退出時刪除。
// Library目錄-方法1
let libraryPaths = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
let libraryPath = libraryPaths[0]
 
// Library目錄-方法2
let libraryPath2 = NSHomeDirectory() + "/Library"

// Cache目錄-方法1
let cachePaths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
let cachePath = cachePaths[0]

// Cache目錄-方法2
let cachePath2 = NSHomeDirectory() + "/Library/Caches"
複製代碼
  • tmp目錄:存儲臨時文件,當在退出程序或設備重啓時,文件會被清除。
// 方法1
let tmpDir = NSTemporaryDirectory()
 
// 方法2
let tmpDir2 = NSHomeDirectory() + "/tmp"
複製代碼

注意

每次編譯代碼會生成新的沙盒路徑,因此模擬器運行同一個 App 時所獲得的沙盒路徑是不同的,但上架的 App 在真機上運行不存在這種狀況。markdown

plist讀寫

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // 獲取本地plist
        let path = Bundle.main.path(forResource: "cityData", ofType: "plist")
        if let path = path {
            let root = NSDictionary(contentsOfFile: path) // 藉助於NSDictionary
            // print(root!.allKeys)
            // print(root!.allKeys[31])
            // 獲取全部數據
            let cities = root![root!.allKeys[31]] as! NSArray // 藉助於NSArray
            // print(cities)
            // 沙盒路徑
            let documentDir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
            let filePath = documentDir! + "/localData.plist"
            // 寫入沙盒
            cities.write(toFile: filePath, atomically: true)
        }
    }
}
複製代碼

偏好設置

  • 通常用於保存如用戶名、密碼、版本等輕量級數據。
  • 經過UserDefaults來設置和讀取偏好設置。
  • 偏好設置以key-value的方式進行讀寫操做。
  • 默認狀況下數據自動以plist形式存儲在沙盒的Library/Preferences目錄。

案例

  • 記住密碼
class ViewController: UIViewController {
    @IBOutlet weak var username: UITextField!
    @IBOutlet weak var password: UITextField!
    @IBOutlet weak var swit: UISwitch!
    // UserDefaults
    let userDefaults = UserDefaults.standard
      
    override func viewDidLoad() {
        super.viewDidLoad()
       
        // 取出存儲的數據
        let name = userDefaults.string(forKey: "name")
        let pwd = userDefaults.string(forKey: "pwd")
        let isOn = userDefaults.standard.bool(forKey: "isOn")
        // 填充輸入框
        username.text = name
        password.text = pwd
        // 設置開關狀態
        swit.isOn = isOn
    }

    @IBAction func login(_ sender: Any) {        
        print("密碼已經記住")
    }
    
    @IBAction func remember(_ sender: Any) {        
        let swit = sender as! UISwitch
        // 若是記住密碼開關打開
        if swit.isOn {          
            let name = username.text
            let pwd = password.text 
            // 存儲用戶名和密碼
            userDefaults.set(name, forKey: "name")
            userDefaults.set(pwd, forKey: "pwd")
            // 同時存儲開關的狀態
            userDefaults.set(swit.isOn, forKey: "isOn")
            // 最後進行同步
            userDefaults.synchronize()      
        }
    }
}
複製代碼
  • 新特性界面
class SceneDelegate: UIResponder, UIWindowSceneDelegate { 
    var window: UIWindow?
    // 當前版本號
    var currentVersion: Double!
    // UserDefaults
    let userDefaults = UserDefaults.standard

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        
        guard let windowScene = (scene as? UIWindowScene) else { return }
        window = UIWindow(windowScene: windowScene)
        if isNewVersion {
            // 新特性界面
            let newVC = UIViewController()
            newVC.view.backgroundColor = .green
            window?.rootViewController = newVC
            // 存儲當前版本號
            userDefaults.set(currentVersion, forKey: "localVersion")
            userDefaults.synchronize()
        } else {
            // 主界面
            let mainVC = UIViewController()       
            mainVC.view.backgroundColor = .red
            window?.rootViewController = mainVC
        }
        
        window?.makeKeyAndVisible()
    }
}

extension SceneDelegate {
    // 是否新版本
    private var isNewVersion: Bool {
        // 獲取當前版本號
        currentVersion = Double(Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String)!
        // 本地版本號
        let localVersion = userDefaults.double(forKey: "localVersion")
        // 比較大小
        return currentVersion > localVersion
    }
}
複製代碼

默認值

若是須要在使用時設置 UserDefaults 的默認值,可使用register方法。session

enum Keys: String {
    case name // 名字
    case isRem // 記住密碼
}

// 設置默認值
UserDefaults.standard.register(defaults: [
    Keys.name.rawValue: "UserA",
    Keys.isRem.rawValue: false
])
複製代碼

注意:在設置默認值後若是修改了其中的屬性值,即便再次執行register方法也不會重置。app

跨域

通常狀況下使用UserDefaults.standard沒有太大問題,但當 App 足夠複雜時就會產生幾個問題:

  • 須要保證設置數據 key 具備惟一性,防止產生衝突。
  • 同一個 plist 文件愈來愈大形成的讀寫效率下降。
  • 沒法便捷的清除特定的偏好設置數據。

所以還有另一種獲取 UserDefaults 對象的方法:UserDefaults(suiteName: String?),能夠根據傳入的 suiteName 參數進行處理:

  • 傳入 nil:等同於UserDefaults.standard
  • 傳入 App Groups 的 ID:操做共享目錄中的 plist 文件,以便在跨 App 或宿主 App 與擴展應用之間(如 App 與 Widget)共享數據。
  • 傳入其餘值:操做Documents/Library/Preferences目錄下以suiteName命名的 plist 文件。

能夠經過以下的方式刪除指定suiteName的 plist 文件裏的所有數據。

let userDefaults = UserDefaults(suiteName: "abc")
userDefaults?.removePersistentDomain(forName: "abc")
複製代碼

歸檔與反歸檔

  • 歸檔(序列化)是把對象轉爲Data,反歸檔(反序列化)是從Data還原出對象。
  • 能夠存儲自定義數據。
  • 存儲的數據須要繼承自NSObject並遵循NSSecureCoding協議。

案例

  • 自定義對象
class Person: NSObject, NSSecureCoding {   
    var name:String?
    var age:Int?
    
    override init() {   
    }
    
    static var supportsSecureCoding: Bool = true
    
    // 編碼- 歸檔調用
    func encode(with aCoder: NSCoder) {        
        aCoder.encode(age, forKey: "age")
        aCoder.encode(name, forKey: "name")
    }
    
    // 解碼-反歸檔調用
    required init?(coder aDecoder: NSCoder) {        
        super.init()        
        age = aDecoder.decodeObject(forKey: "age") as? Int
        name = aDecoder.decodeObject(forKey: "name") as? String
    }
}
複製代碼
  • 歸檔與反歸檔
class ViewController: UIViewController {
    var data: Data!
    var origin: Person!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // 歸檔
    @IBAction func archiver(_ sender: Any) {
        let p = Person()
        p.age = 20
        p.name = "zhangsan"

        do {
            try data = NSKeyedArchiver.archivedData(withRootObject: p, requiringSecureCoding: true)
        } catch {
            print(error)
        }
    }

    // 反歸檔
    @IBAction func unarchiver(_ sender: Any) {
        do {
            try origin = NSKeyedUnarchiver.unarchivedObject(ofClass: Person.self, from: data)
            print(origin!.age!)
            print(origin!.name!)
        } catch {
            print(error)
        }
    }
}
複製代碼

數據庫—sqlite3

因爲 Swift 直接操做 sqlite3 很是不方便,因此藉助於SQLite.swift的框架。

  • Model
struct Person {    
    var name : String = ""
    var phone : String = ""
    var address : String = ""
}
複製代碼
  • DBTools
import SQLite

struct DBTools {
    // 數據庫路徑
    let dbPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! + "/person.db"
    // 數據庫鏈接 
    var db: Connection!
    // 表名與字段
    let personTable = Table("t_person") // 表名
    let personID = Expression<Int>("id") // id
    let personName = Expression<String>("name") // name
    let personPhone = Expression<String>("phone") // phone
    let personAddress = Expression<String>("address") // address

    // MARK: - 構造函數,數據庫有則鏈接 沒有就建立後鏈接
    init() {
        do {
            db = try Connection(dbPath)
            print("數據庫建立/鏈接成功")
        } catch {
            print("數據庫建立/鏈接失敗")
        }
    }

    // MARK: - 建立表格,表若存在不會再次建立,直接進入catch
    func createTable() {
        // 創表
        do {
            try db.run(personTable.create(block: { t in
                t.column(personID, primaryKey: .autoincrement)
                t.column(personName)
                t.column(personPhone)
                t.column(personAddress)
            }))
            print("數據表建立成功")
        } catch {
            print("數據表建立失敗")
        }
    }

    // MARK: - 插入數據
    func insertPerson(person: Person) {
        let insert = personTable.insert(personName <- person.name, personPhone <- person.phone, personAddress <- person.address)
        // 插入
        do {
            try db.run(insert)
            print("插入數據成功")
        } catch {
            print("插入數據失敗")
        }
    }

    // MARK: - 刪除數據
    func deletePerson(name: String) {
        // 篩選數據
        let p = personTable.filter(personName == name)
        // 刪除
        do {
            let row = try db.run(p.delete())          
            if row == 0 {
                print("暫無數據刪除")
            } else {
                print("數據刪除成功")
            }
        } catch {
            print("刪除數據失敗")
        }
    }

    // MARK: - 更新數據
    func updatePerson(person: Person) {
        // 篩選數據
        let p = personTable.filter(personName == person.name)
        // 更新
        do {
            let row = try db.run(p.update(personPhone <- person.phone, personAddress <- person.address))    
            if row == 0 {
                print("暫無數據更新")
            } else {
                print("數據更新成功")
            }
        } catch {
            print("數據更新失敗")
        }
    }

    // MARK: - 查詢數據
    func selectPerson() -> [Person]? {
        // 保存查詢結果
        var response: [Person] = []
        // 查詢
        do {
            let select = try db.prepare(personTable)       
            for person in select {
                let p = Person(name: person[personName], phone: person[personPhone], address: person[personAddress])
                response.append(p)
            }
            
            if !response.isEmpty {
                print("數據查詢成功")
            } else {
                print("對不起,暫無數據")
            }
            return response
        } catch {
            print("數據查詢失敗")
            return nil
        }
    }
}
複製代碼
  • ViewController
class ViewController: UIViewController {    
    var dbTools: DBTools?

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func createDB(_ sender: Any) {
        dbTools = DBTools()
    }

    @IBAction func createTab(_ sender: Any) {
        dbTools?.createTable()
    }

    @IBAction func insertData(_ sender: Any) {
        let p = Person(name: "zhangsan", phone: "18888888888", address: "AnHuiWuhu")
        dbTools?.insertPerson(person: p)
    }

    @IBAction func deleteData(_ sender: Any) {
        dbTools?.deletePerson(name: "zhangsan")
    }

    @IBAction func updateData(_ sender: Any) {
        let p = Person(name: "zhangsan", phone: "17777777777", address: "JiangSuNanJing")
        dbTools?.updatePerson(person: p)
    }

    @IBAction func selectData(_ sender: Any) {
        let person = dbTools?.selectPerson()
        if let person = person {
            for p in person {
                print(p)
            }
        }
    }
}
複製代碼
相關文章
相關標籤/搜索