美文网首页iOS14开发
iOS14开发-数据存储

iOS14开发-数据存储

作者: YungFan | 来源:发表于2021-03-26 09:13 被阅读0次

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

    相关文章

      网友评论

        本文标题:iOS14开发-数据存储

        本文链接:https://www.haomeiwen.com/subject/znzdhltx.html