💾 iOS Data Persistence
Core Data, UserDefaults, and other data persistence options in iOS
7 min read
January 8, 2024
Data Persistence Options
iOS provides several options for persisting data, each with its own use cases and trade-offs.
1. UserDefaults
Simple key-value storage for user preferences and app settings.
// Storing data
UserDefaults.standard.set("John Doe", forKey: "username")
UserDefaults.standard.set(25, forKey: "age")
UserDefaults.standard.set(true, forKey: "isFirstLaunch")
// Retrieving data
let username = UserDefaults.standard.string(forKey: "username")
let age = UserDefaults.standard.integer(forKey: "age")
let isFirstLaunch = UserDefaults.standard.bool(forKey: "isFirstLaunch")
// Custom UserDefaults wrapper
extension UserDefaults {
enum Keys {
static let username = "username"
static let age = "age"
static let isFirstLaunch = "isFirstLaunch"
}
var username: String? {
get { string(forKey: Keys.username) }
set { set(newValue, forKey: Keys.username) }
}
}
2. Core Data
Apple's object graph and persistence framework for complex data models.
// Core Data Stack
class CoreDataStack {
static let shared = CoreDataStack()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores { _, error in
if let error = error {
fatalError("Core Data error: \(error)")
}
}
return container
}()
var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
func saveContext() {
if context.hasChanges {
do {
try context.save()
} catch {
print("Save error: \(error)")
}
}
}
}
// Using Core Data
class UserManager {
private let context = CoreDataStack.shared.context
func createUser(name: String, email: String) {
let user = User(context: context)
user.name = name
user.email = email
user.id = UUID()
CoreDataStack.shared.saveContext()
}
func fetchUsers() -> [User] {
let request: NSFetchRequest = User.fetchRequest()
do {
return try context.fetch(request)
} catch {
print("Fetch error: \(error)")
return []
}
}
}
3. File System
Direct file system access for custom data formats and large files.
class FileManager {
static let shared = FileManager()
private let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
func saveData(_ data: Data, to fileName: String) {
let fileURL = documentsDirectory.appendingPathComponent(fileName)
do {
try data.write(to: fileURL)
} catch {
print("Save error: \(error)")
}
}
func loadData(from fileName: String) -> Data? {
let fileURL = documentsDirectory.appendingPathComponent(fileName)
return try? Data(contentsOf: fileURL)
}
func saveJSON(_ object: T, to fileName: String) {
do {
let data = try JSONEncoder().encode(object)
saveData(data, to: fileName)
} catch {
print("JSON encoding error: \(error)")
}
}
func loadJSON(_ type: T.Type, from fileName: String) -> T? {
guard let data = loadData(from: fileName) else { return nil }
do {
return try JSONDecoder().decode(type, from: data)
} catch {
print("JSON decoding error: \(error)")
return nil
}
}
}
4. Keychain
Secure storage for sensitive data like passwords and tokens.
import Security
class KeychainManager {
static let shared = KeychainManager()
private let service = "com.yourapp.keychain"
func save(_ data: Data, for key: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key,
kSecValueData as String: data
]
SecItemDelete(query as CFDictionary)
SecItemAdd(query as CFDictionary, nil)
}
func load(for key: String) -> Data? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key,
kSecReturnData as String: true
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
return status == errSecSuccess ? result as? Data : nil
}
func delete(for key: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key
]
SecItemDelete(query as CFDictionary)
}
}
5. Best Practices
✅ Do's
- Use UserDefaults for simple preferences
- Use Core Data for complex relational data
- Use Keychain for sensitive data
- Handle errors gracefully
- Consider data migration strategies
❌ Don'ts
- Don't store sensitive data in UserDefaults
- Don't perform heavy operations on the main thread
- Don't ignore Core Data migration
- Don't store large files in Core Data