iOS14开发-数据存储

Bundle

简单理解就是资源文件包,会将许多图片、xib、文本文件组织在一起,打包成一个 Bundle 文件,这样可以在其他项目中引用包内的资源。

// 获取当前项目的Bundle
let bundle = Bundle.main

// 加载资源
let mp3 = Bundle.main.path(forResource: "xxx", ofType: "mp3")

沙盒

每一个 App 只能在自己的创建的文件系统(存储区域)中进行文件的操作,不能访问其他 App 的文件系统(存储区域),该文件系统(存储区域)被成为沙盒。所有的非代码文件都要保存在此,例如图像,图标,声音,plist,文本文件等。

沙盒机制保证了 App 的安全性,因为只能访问自己沙盒文件下的文件。

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 在真机上运行不存在这种情况。

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
    }
}

归档与反归档

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

推荐阅读更多精彩内容