iOS 之数据持久化

引言

iOS 之网络请求 一文中已经了解到如何进行网络请求,当通过网络请求获取到数据后需要将其持久性地存储起来时,就需要了解在iOS 开发中如何进行持久性地存储数据

iOS 开发中对数据进行持久性地存储的方式主要有:本地存储数据库存储

本地存储

本地存储是指将数据存储在设备的本地文件系统上,使应用程序能够在没有互联网连接的情况下访问和操作数据。本地存储通常用于存储非敏感小规模的信息

UserDefaults

一种轻量级的本地存储方式,用于存储少量的键值对数据。通常用于存储应用的偏好设置和用户配置

UserDefaults是一个单例模式的类,通过.standard属性来访问应用程序中的唯一UserDefaults 实例。这使得在整个应用程序中方便地共享和访问数据

swift 复制代码
// 存储数据到UserDefaults
let datas = UserDefaults.standard
datas.set("JohnDoe", forKey: "Username")
datas.set(25, forKey: "Age")
datas.set(true, forKey: "IsLoggedIn")

// 读取数据从UserDefaults
let username = datas.string(forKey: "Username") 
let age = datas.integer(forKey: "Age")
let isLoggedIn = datas.bool(forKey: "IsLoggedIn")

当从UserDefaults中读取数据时,需要根据数据类型来正确读取数据。如果尝试使用不匹配的类型来读取数据,可能会导致运行时错误或返回默认值(如果设置了默认值)

经典单例模式结构

swift 复制代码
class MySingleton {
    static let shared = MySingleton()
    
    private init() {
        // 私有的初始化方法,确保不能在外部创建多个实例
    }
    
    // 这里可以添加其他属性和方法
}

单例模式的经典结构包括一个私有的构造方法和一个公共的静态属性(或方法),用于访问唯一实例

UserDefaults 单例模式

UserDefaults的实现方式与经典单例模式类的结构略有不同。UserDefaultsSwift 标准库提供的一个类,它是一个全局单例,其实现方式不同于传统的单例模式,而是通过Swift 标准库内部进行管理的,不需要手动实现单例模式。只需要使用UserDefaults.standard来访问全局的单例实例,而不需要自己创建单例。这是Swift标准库为了方便数据持久化存储而提供的一种机制,它隐藏了单例模式的复杂性

swift 复制代码
open class UserDefaults : NSObject {
    open class var standard: UserDefaults { get }
}

Plists

全称Property Lists,一种用于存储和序列化数据的XML二进制文件格式,通常以.plist文件扩展名保存

Plists文件通常是通过键-值(key-value)的形式来存储各种类型的数据。每个数据项都有一个唯一的键(key),用于标识该数据项,然后根据键来获取对应的值。这种结构适用于存储字典、数组、字符串、整数、浮点数等多种数据类型

Plists可用于存储结构化的数据,例如配置文件、应用设置、静态数据等

swift 复制代码
// 创建一个字典,包含要保存到 Plist 文件的数据
let dataToSave: [String: Any] = [
    "Username": "JohnDoe",
    "Age": 25,
    "IsLoggedIn": true
]

// 获取 Plist 文件的 URL,通常是在应用的沙盒目录中
if let plistURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("UserData.plist") {
    do {
        // 将数据写入 Plist 文件,使用 PropertyListSerialization
        let plistData = try PropertyListSerialization.data(fromPropertyList: dataToSave, format: .xml, options: 0)
        try plistData.write(to: plistURL)
        print("Data saved to \(plistURL)")
        
        // 从 Plist 文件读取数据 
        let plistDataRead = try Data(contentsOf: plistURL) 
        if let dictionary = try PropertyListSerialization.propertyList(from: plistDataRead, options: [], format: nil) as? [String: Any] { 
            if let username = dictionary["Username"] as? String {
                print("Username: \(username)") 
            } 
            if let age = dictionary["Age"] as? Int { 
                print("Age: \(age)") 
            } 
            if let isLoggedIn = dictionary["IsLoggedIn"] as? Bool { 
                print("IsLoggedIn: \(isLoggedIn)") 
            }
        }
    } catch {
        print("Error writing to Plist: \(error)")
    }
}

拓展

Plist文件的数据类型:

  • 如果将文件扩展名设置为.plist,则数据将存储为XML 格式Plist文件
  • 如果将文件扩展名设置为.bplist,则数据将存储为二进制格式Plist文件

PropertyListSerialization.data(fromPropertyList: dataToSave, format: .xml, options: 0)处的format用于指定输出的格式,有.xml.binary两个值,这里的数据格式不会影响数据写入文件时的实际格式,写入文件的数据类型是与扩展名相关

Archiver

Archiver是将对象序列化为二进制数据,并将其保存在文件中的过程,以便后续可以还原成原始对象。在iOS 开发中,可以使用NSKeyedArchiverNSKeyedUnarchiver类来进行归档反归档操作

归档的步骤

  1. 创建要归档的对象:首先,你需要创建一个自定义的类,该类需要遵循NSCoding协议,这个协议要求实现encode(with:)init(coder:)方法,以便对象可以被归档反归档
  2. 归档对象:使用NSKeyedArchiver类的实例,可以将对象归档二进制数据并将其保存到文件中
  3. 反归档对象:使用NSKeyedUnarchiver类的实例,可以从文件中读取归档数据,并将其解析原始对象
1. 创建要归档的对象
swift 复制代码
class Person: NSObject, NSCoding {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    // 实现NSCoding协议的方法
    required init?(coder aDecoder: NSCoder) {
        self.name = aDecoder.decodeObject(forKey: "name") as! String
        self.age = aDecoder.decodeInteger(forKey: "age")
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: "name")
        aCoder.encode(age, forKey: "age")
    }
}
2. 归档
swift 复制代码
let person = Person(name: "John", age: 30)

let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let archiveURL = documentsDirectory.appendingPathComponent("person.archive")

do {
    let data = try NSKeyedArchiver.archivedData(withRootObject: person, requiringSecureCoding: false)
    try data.write(to: archiveURL)
    print("Person object archived successfully.")
} catch {
    print("Error archiving person object: \(error)")
}
3. 反归档
swift 复制代码
do {
    let data = try Data(contentsOf: archiveURL)
    if let loadedPerson = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Person {
        print("Loaded person: \(loadedPerson.name), Age: \(loadedPerson.age)")
    } else {
        print("Failed to unarchive person object.")
    }
} catch {
    print("Error loading person object: \(error)")
}

数据库存储

数据库存储是指将数据存储在结构化数据库中,以便有效地查询、检索和管理数据。这种方式适用于需要大规模数据存储复杂查询的情况,例如应用程序中的用户数据、购物车数据等

CoreData:苹果提供的一个高级持久性框架,提供对象关系映射(ORM)功能,适用于复杂的数据模型

SQLite:轻量级嵌入式关系型数据库,适用于复杂的数据结构和查询需求

第三方数据库:使用第三方数据库解决方案,如RealmFirebase Realtime DatabaseCouchbase Lite

持久化存储方案对比

UserDefaults 和 Plists 对比

相似之处

  1. 键-值存储: PlistUserDefaults都采用了键-值(key-value)的存储方式,其中每个数据项都有一个唯一的键用于标识
  2. 支持多种数据类型: 两者都支持存储多种数据类型,包括字符串、整数、浮点数、数组、字典等
  3. 持久性存储: PlistUserDefaults都支持将数据持久性地存储在设备上,以便在应用程序重新启动后保留数据

关键区别

  1. 存储位置: Plist通常是存储在应用的沙盒目录中的文件,可以轻松地创建、读取和编辑;UserDefaults则是一个键值对存储系统,使用更高级的 API进行访问
  2. 数据规模: Plist通常用于存储较大或结构化的数据;UserDefaults通常用于存储应用的用户设置和轻量级数据
  3. 访问方式: Plist需要手动读取和写入,通常需要使用PropertyListSerialization API进行操作;UserDefaults则提供了更高级别的 API,使数据的读取和写入更加方便

Plists 和 Archiver 对比

  • Archiver:适用于保存自定义对象复杂数据模型应用状态信息等需要保留对象类型信息的场景。归档可以将整个对象图保存到文件中,以便恢复对象的状态
  • Plists:适用于保存简单的数据结构,如键-值对、配置信息、用户偏好设置等。Plists不适用于保存自定义对象,因为它不会保留对象的类信息

总之,PlistArchiver都可以保存二进制数据,但是Plist不具备自动解析并还原自定义对象的功能,而归档(Archiver)会保留对象的类信息,使恢复更加容易

拓展

open 和 public 关键字

openpublic 是Swift中的访问控制关键字,它们用于控制类、结构体、函数、属性等成员的可见性和访问权限。它们之间的主要区别在于访问级别和继承性:

  1. open 使用open关键字修饰的成员具有最高级别的访问权限,对于其他模块(框架或库)来说是可见的,并且可以被继承。这意味着其他模块可以扩展和继承open成员。通常,open用于创建可扩展的公共接口,以便其他开发者可以继承和扩展
  2. public 使用public关键字修饰的成员也对其他模块可见,但不具有继承性。这意味着其他模块可以访问public成员,但不能继承或重写

沙盒目录

沙盒目录(Sandbox Directory)iOS 应用程序私有文件系统空间,每个iOS 应用都有自己的独立沙盒目录,用于存储应用程序的数据和文件。沙盒目录的概念是为了确保应用程序之间的隔离性安全性,防止一个应用程序访问其他应用程序的数据或文件

iOS 的文件系统会根据应用程序包标识符为每个应用程序创建一个独立的沙盒目录。这意味着不同应用程序的沙盒目录是相互隔离的,一个应用程序无法直接访问另一个应用程序的沙盒目录内的文件

沙盒目录包括的主要文件夹

  1. Documents 目录: 用于存储用户生成的文件,例如文档、照片、视频等。这些文件会在应用程序备份时被包括在备份中,并可以通过iCloud共享
  2. Library 目录: 该目录包含两个子目录
    • Caches 目录: 用于存储可以重新生成的临时数据,例如缓存文件。这些文件不会在应用程序备份时被包括在备份中,因为它们可以随时重新生成
    • Preferences 目录: 用于存储应用程序的偏好设置,通常以plist 文件的形式存在
  3. tmp 目录: 用于存储临时文件,这些文件不会被应用程序备份,且在应用程序退出后可能会被删除
  4. 应用程序包目录: 包含了应用程序的可执行文件资源文件,通常不允许在运行时修改这个目录中的文件
swift 复制代码
// 获取路径
// for: .documentDirectory, .libraryDirectory, .cachesDirectory, .preferencePanesDirectory
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first{}

小结

关于iOS 开发中关于数据持久化部分,主要是以本地存储数据为主,对比说明了UserDefaultsPlistsArchiver这三种本地存储的方式;对于数据库存储仅是做了简要概述,感兴趣可以深入了解,后面如果有用到也会更新相关文章的~~

相关推荐
2401_865854882 小时前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
Bonne journée8 小时前
SQLite数据库是什么?DB Browser for SQLite是什么?
数据库·sqlite
HackerTom13 小时前
iOS用rime且导入自制输入方案
ios·iphone·rime
良技漫谈13 小时前
Rust移动开发:Rust在iOS端集成使用介绍
后端·程序人生·ios·rust·objective-c·swift
2401_8524035514 小时前
高效管理iPhone存储:苹果手机怎么删除相似照片
ios·智能手机·iphone
q567315231 天前
使用 Python 编辑 XML 文件中的文本字段
xml·java·数据库·python·sqlite
星际码仔1 天前
【动画图解】是怎样的方法,能被称作是 Flutter Widget 系统的核心?
android·flutter·ios
emperinter1 天前
WordCloudStudio:AI生成模版为您的文字云创意赋能 !
图像处理·人工智能·macos·ios·信息可视化·iphone
关键帧Keyframe1 天前
音视频面试题集锦第 8 期
ios·音视频开发·客户端
pb81 天前
引入最新fluwx2.5.4的时候报错
flutter·ios