本质上来说,不管是哪种缓存方式最终都会以文件的形式存储在磁盘上,只不过上层进行了某种“封装”或“抽象”,所以还是做了分类,目前iOS本地持久化缓存(Storage/Persistence)有以下几种形式:
UserDefaults
文件缓存
Keychain
Core Data
数据库(SQLite)
注意,这里讨论的只是持久化缓存,如果单纯地讨论缓存,严格地说,应该涉及类似NSCache、NSURLCache等等一些列的内存缓存也应该考虑在内。下面我们将从原理、示例代码、注意事项等方面来介绍这些缓存机制。
UserDefaults
UserDefaults是iOS中一种常见的轻量级缓存机制,它可以用来存储应用程序的配置信息、用户偏好设置、临时缓存数据等。
原理说明:
UserDefaults使用plist文件来进行存储,它将缓存的数据以key-value的形式保存在文件中。这个文件被存储在应用程序沙盒中的Library/Preferences目录下,并且是由系统自动管理的。
示例代码:
UserDefaults是单例对象,我们可以通过它的shared实例来访问它。下面是一个使用UserDefaults进行存储和读取的示例代码:
// 存储数据
UserDefaults.standard.set("hello", forKey: "greeting")
// 读取数据
let greeting = UserDefaults.standard.string(forKey: "greeting")
print(greeting)
注意事项:
UserDefaults只能存储基本数据类型和部分Foundation框架中的对象类型,如NSString、NSNumber、NSArray、NSDictionary等,不支持存储自定义对象类型。
由于UserDefaults的存储数据是通过plist文件进行保存的,所以对于需要频繁写入的数据,最好使用更高效的存储方式,比如Core Data、SQLite等。
在使用UserDefaults存储敏感数据时,需要进行加密处理,以确保数据安全性。
总结:
UserDefaults是iOS中常见的轻量级缓存机制,它使用plist文件来进行存储,支持存储基本数据类型和部分Foundation框架中的对象类型,使用方便简单,但需要注意存储数据类型的限制和数据安全性问题。
文件缓存
文件缓存机制也是一种常见的缓存方式。
原理说明:
它是一种将应用程序的数据永久地保存在本地文件系统中的方法,以便于下次启动应用程序时能够快速地读取数据。这种缓存机制的原理是将数据保存到应用程序的沙盒目录中,并在应用程序下一次启动时读取该数据。
示例代码:
使用文件持久化缓存机制需要使用iOS中的文件系统API,例如NSFileManager和NSFileHandle等类。这些类提供了对文件系统的基本操作,如创建、删除、移动和读写文件等。
以下是一个使用文件持久化缓存机制的示例代码:
// 获取应用程序的沙盒目录
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// 设置要保存的数据
let data = "Hello, world!".data(using: .utf8)
// 保存数据到文件中
let fileURL = documentsDirectory.appendingPathComponent("cache.txt")
do {
try data?.write(to: fileURL)
} catch {
print("Error writing to file: \(error)")
}
// 从文件中读取数据
do {
let cachedData = try Data(contentsOf: fileURL)
let cachedString = String(data: cachedData, encoding: .utf8)
print("Cached string: \(cachedString)")
} catch {
print("Error reading from file: \(error)")
}
使用文件持久化缓存机制需要注意以下几点:
应该尽量避免保存大量的数据到本地文件系统中,因为这会占用设备的存储空间。
对于敏感数据,应该使用加密算法来保护数据的安全。
应该定期清理缓存文件,以避免缓存文件过多导致设备存储空间不足。
应该注意处理文件读写时的错误,以避免程序崩溃或数据丢失的情况。
另外,文件缓存的还有一种特殊的但是也常用到的方式,就是KeyedArchiver。
KeyedArchiver
用于将对象序列化为二进制数据,并将其写入到文件中进行缓存。KeyedArchiver提供了一种便捷的方法,让开发者能够将自定义对象序列化为二进制数据,并将其保存到文件系统中,以便于以后从文件中读取数据。
以下是KeyedArchiver的使用方法:
实现NSCoding协议
首先,需要在自定义对象中实现NSCoding协议,以便KeyedArchiver能够将对象序列化为二进制数据。该协议包括encode和init(coder:)两个方法,分别用于将对象序列化为二进制数据和从二进制数据反序列化为对象。
class Person: NSObject, NSCoding {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name")
coder.encode(age, forKey: "age")
}
required init?(coder: NSCoder) {
name = coder.decodeObject(forKey: "name") as? String ?? ""
age = coder.decodeInteger(forKey: "age")
}
}
缓存对象
接下来,使用KeyedArchiver将自定义对象序列化为二进制数据,并将其写入到文件中进行缓存。
let person = Person(name: "Tom", age: 20)
// 获取文件路径
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let filePath = path + "/person.archive"
// 将对象序列化为二进制数据并写入文件
let data = NSKeyedArchiver.archivedData(withRootObject: person)
try? data.write(to: URL(fileURLWithPath: filePath))
从缓存中读取对象
// 从文件中读取二进制数据并反序列化为对象
let data = try? Data(contentsOf: URL(fileURLWithPath: filePath))
let cachedPerson = NSKeyedUnarchiver.unarchiveObject(with: data!) as? Person
print("Name: \(cachedPerson?.name ?? "")")
print("Age: \(cachedPerson?.age ?? 0)")
使用KeyedArchiver时需要注意以下几点:
要对自定义对象实现NSCoding协议中的encode和init(coder:)方法,以便KeyedArchiver能够将对象序列化为二进制数据。
KeyedArchiver会将对象序列化为二进制数据,因此需要注意内存消耗问题。
对于一些敏感数据,需要考虑数据的安全性。可以使用加密算法保护数据的安全。
应该注意处理文件读写时的错误,以避免程序崩溃或数据丢失的情况。
Keychain
Keychain是一种iOS的持久化缓存机制,也是iOS中安全框架的一部分,提供了用于以安全方式存储和检索数据的API。
原理说明:
Keychain中的数据存储在受保护的系统区域中,它是完全隔离的,不同的应用程序之间无法共享数据。每个应用程序都有自己的Keychain,它只能访问自己的数据,而无法访问其他应用程序的数据。这种隔离性保证了Keychain存储的安全性。
它可以保证在应用程序启动和设备重启之间安全地存储和检索敏感数据。它使用加密的方式存储数据,如密码、加密密钥和其他凭证,以便其他应用程序和系统无法访问这些数据。
对于每一个应用来说,KeyChain都有两个访问区,私有区和公共区。私有区是一个Sandbox,本程序存储的任何数据都对其他程序不可见,其他应用程序无法访问该区数据。使用Keychain API,可以通过键值对存储数据,并使用服务名称和账户名称对其进行标识,这样可以保证数据是与应用程序相关联的,并且只能由拥有相应服务名称和账户名称的应用程序访问。
如果要想将存储的内容放在公共区,实现多个应用程序间可以共同访问一些数据,则可以先声明公共区的名称,官方文档管这个名称叫“keychain access group”。
使用示例:
以下是Keychain的使用方法和示例代码(Swift):
导入Security框架
在使用Keychain之前,需要导入Security框架。
import Security
存储数据
使用SecItemAdd方法将数据存储到Keychain中。
let password = "123456"
let data = password.data(using: .utf8)!
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "MyPassword",
kSecValueData as String: data]
let status = SecItemAdd(query as CFDictionary, nil)
if status == errSecSuccess {
print("Password saved to Keychain.")
} else {
print("Failed to save password to Keychain.")
}
在上面的代码中,我们将字符串密码转换为二进制数据,并将其存储到Keychain中。kSecClass表示存储的数据类型,kSecAttrAccount是用来标识存储数据的名称,kSecValueData则是存储的二进制数据。
读取数据
使用SecItemCopyMatching方法从Keychain中读取数据。
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "MyPassword",
kSecReturnData as String: kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitOne]
var dataTypeRef: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == errSecSuccess {
if let retrievedData = dataTypeRef as? Data,
let password = String(data: retrievedData, encoding: .utf8) {
print("Retrieved password from Keychain: \(password)")
} else {
print("Failed to retrieve password from Keychain.")
}
} else {
print("Failed to retrieve password from Keychain.")
}
在上面的代码中,我们使用kSecReturnData来表示返回存储的二进制数据,使用kSecMatchLimit来表示返回数据的数量,这里我们只需要返回一个结果,所以指定为kSecMatchLimitOne。
使用Keychain时需要注意以下几点:
应该限制对Keychain的访问权限,并确保只有授权的用户能够访问Keychain中的数据。
Keychain中存储的数据是加密的,因此无法从外部访问它们。如果数据需要与其他应用程序共享,则可以通过group方式,让程序可以在App间共享,不过得要相同TeamID。
Keychain中数据并不存放在App的Sandbox中,即使删除了App,资料依然保存在keychain中。如果重新安装了app,还可以从keychain获取数据。
Keychain是一种安全的存储机制,但并不是完美的。例如,越狱的设备就可以访问Keychain存储的数据。因此,不应在Keychain中存储高度敏感的数据。因此仍然需要谨慎地使用和存储敏感信息。
在处理Keychain时,应该注意处理可能出现的错误,以避免程序崩溃或数据丢失的情况。
Apple针对keychain也提供了丰富的开发文档说明,包括有Keychain Services Programming Guide:文章中包含了使用mac和ios的keychain开发。
Core Data
Core Data是苹果提供的一种数据管理框架,它提供了一种方便的方式来管理和操作应用程序中的数据。其中最重要的特性就是持久化存储,也就是将数据存储在本地文件中,以便下一次应用程序启动时可以继续使用。
原理说明:
在Core Data中,持久化存储的实现是通过使用SQLite数据库来完成的。SQLite是一种轻量级的嵌入式数据库,可以轻松地嵌入到应用程序中,提供了一种高效、可靠的数据存储方式。
使用示例:
使用Core Data进行持久化存储需要进行以下步骤:
定义数据模型:在Xcode中创建一个数据模型文件,用于定义数据的实体、属性和关系等。
创建Core Data栈:使用NSPersistentContainer类创建Core Data栈,包括托管对象上下文、持久化存储协调器等组件。
存储和读取数据:使用NSManagedObjectContext类操作托管对象上下文,通过操作实体和属性等元素进行数据存储和读取。
下面是一个简单的Swift代码示例,演示如何使用Core Data进行数据存储:
// 创建Core Data栈
let container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores { _, error in
if let error = error {
print("Failed to load persistent stores: \(error)")
return
}
}
// 创建一个托管对象上下文
let context = container.viewContext
// 创建一个Person实体对象
let person = NSEntityDescription.insertNewObject(forEntityName: "Person", into: context) as! Person
person.name = "Tom"
person.age = 30
// 保存数据到持久化存储
do {
try context.save()
} catch {
print("Failed to save context: \(error)")
}
// 从持久化存储中读取数据
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
do {
let persons = try context.fetch(fetchRequest)
for person in persons {
print("Name: \(person.name!), Age: \(person.age)")
}
} catch {
print("Failed to fetch persons: \(error)")
}
在使用Core Data进行持久化存储时,需要注意以下几点:
定义数据模型时,应该尽可能地保持简单。复杂的数据模型会导致数据库操作变得缓慢,并且会增加代码的复杂性。
Core Data提供了多种持久化存储方式,如SQLite、二进制文件、XML文件等。开发者需要根据具体需求选择合适的存储方式。
Core Data是一种线程安全的框架,但是多线程编程仍然需要注意线程安全问题。建议在每个线程中创建独立的托管对象上下文。
总之,Core Data是iOS中一种强大的持久化存。
SQLite
iOS中,SQLite是一种轻量级的数据库引擎,被广泛用于持久化存储。SQLite使用文件作为数据存储介质,通过对数据进行编码和解码实现对数据的存储和读取。在iOS中,SQLite被用于开发本地应用,以存储大量结构化数据。
原理说明
SQLite是一种基于磁盘的关系型数据库,支持SQL语言操作。SQLite通过一个单一的文件存储所有的数据,这个文件可以被轻松地复制、备份和传输。SQLite的特点是占用内存极少、速度快,适合移动设备等资源受限的环境。
使用方法
使用SQLite持久化缓存需要进行以下步骤:
导入SQLite库:在项目中导入SQLite库,可以使用CocoaPods或手动导入。
创建数据库:通过SQLite库提供的API创建数据库文件。如果数据库文件已经存在,可以直接打开,否则可以使用SQL语句创建数据库文件。
创建表格:使用SQL语句创建表格,并为表格定义字段类型和约束。
执行SQL语句:通过SQLite提供的API执行SQL语句,对表格进行增删改查操作。
关闭数据库:在应用程序退出或不再使用数据库时,需要关闭数据库,释放资源。
示例代码
以下是一个使用SQLite进行数据存储的示例代码,其中使用了Swift的SQLite.swift库:
// 导入SQLite.swift库
import SQLite
// 打开数据库连接
let db = try! Connection("path/to/database.sqlite3")
// 定义数据表
let users = Table("users")
let id = Expression<Int64>("id")
let name = Expression<String>("name")
let email = Expression<String>("email")
// 创建表格
try! db.run(users.create { t in
t.column(id, primaryKey: true)
t.column(name)
t.column(email, unique: true)
})
// 插入数据
let insert = users.insert(name <- "Alice", email <- "alice@example.com")
try! db.run(insert)
// 查询数据
for user in try! db.prepare(users) {
print("id: \(user[id]), name: \(user[name]), email: \(user[email])")
}
// 更新数据
let alice = users.filter(name == "Alice")
try! db.run(alice.update(email <- "alice@example.org"))
// 删除数据
try! db.run(alice.delete())
// 关闭数据库连接
db.close()
注意事项:
在使用SQLite持久化缓存时,需要注意以下事项:
需要手动编写SQL语句进行数据操作,对于不熟悉SQL语言的开发者来说可能会有一定难度。
在进行数据操作时需要保证线程安全,可以使用SQLite提供的事务机制进行数据操作,避免数据损坏或丢失。
SQLite的数据库文件可以被轻松地复制、备份和传输,需要注意数据安全问题。
总结
经过以上介绍,我们统一总结下,如下:
UserDefaults
UserDefaults是轻量级的持久化存储方式,适用于存储一些简单的配置信息或用户偏好设置。它的优点在于简单易用,无需考虑数据模型和数据迁移等问题。但是它的缺点在于只能存储一些基本数据类型,对于复杂的数据结构无法支持。
最佳应用场景:适合存储少量的简单配置信息或用户偏好设置。
文件缓存
文件缓存可以将数据以文件形式存储在本地文件系统中,适用于存储较大的数据,如图片、视频等。它的优点在于可以灵活地控制缓存策略和缓存大小,可以有效地减轻服务器负载,提高用户体验。但是它的缺点在于需要手动管理缓存,包括缓存路径、缓存文件名、缓存过期时间等,一旦管理不当,可能会导致缓存文件过多,浪费存储空间。
最佳应用场景:适合存储大量的非敏感数据,如图片、音频、视频(SDWebImage)等。
Keychain
Keychain是一种安全的存储方式,可以将敏感信息(如用户密码、密钥等)加密后存储在系统中,保证数据的安全性。它的优点在于可以保护敏感信息不被恶意获取,同时也提供了方便的API来管理这些敏感信息。但是它的缺点在于只能存储较小的数据,不适合存储大量的数据。
最佳应用场景:适合存储敏感信息,如用户密码、密钥等。
Core Data
Core Data是一种ORM(对象关系映射)框架,可以将数据存储在SQLite数据库中。它的优点在于可以很方便地管理数据模型,同时提供了强大的查询、排序、过滤等功能,非常适合存储复杂的数据结构。但是它的缺点在于学习曲线较陡峭,需要理解Core Data的一些概念和API,同时也需要考虑数据模型的迁移等问题。
最佳应用场景:适合存储复杂的数据结构,如联系人、音乐播放列表、日历事件等。
数据库(SQLite)
数据库是一种通用的存储方式,可以存储任意类型的数据,同时也提供了丰富的查询、排序、过滤等功能。但是一般情况下,我们更倾向于在一些需要复杂存储与查询的场景中使用它。例如联系人、音乐播放列表等。
关注以下公众号,可与作者直接沟通,后续也会有更多文章更新: