Overview of iOS Storage Options
Choosing the right data persistence method is crucial for iOS app performance and user experience. This guide covers all major storage options with real-world examples.
1. UserDefaults - Simple Key-Value Storage
// Basic UserDefaults usage
UserDefaults.standard.set("John Doe", forKey: "username")
UserDefaults.standard.set(true, forKey: "isFirstLaunch")
UserDefaults.standard.set(42, forKey: "userAge")
// Reading values
let username = UserDefaults.standard.string(forKey: "username")
let isFirstLaunch = UserDefaults.standard.bool(forKey: "isFirstLaunch")
let userAge = UserDefaults.standard.integer(forKey: "userAge")
// Custom objects with Codable
struct UserPreferences: Codable {
let theme: String
let notifications: Bool
let language: String
}
let preferences = UserPreferences(theme: "dark", notifications: true, language: "en")
if let encoded = try? JSONEncoder().encode(preferences) {
UserDefaults.standard.set(encoded, forKey: "userPreferences")
}
When to Use UserDefaults
- App settings and preferences
- Simple configuration data
- Small amounts of data (<1MB)
- Data that needs to persist between app launches
2. CoreData - Object-Relational Mapping
// CoreData setup
import CoreData
class CoreDataManager {
static let shared = CoreDataManager()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores { _, error in
if let error = error {
fatalError("CoreData error: \(error)")
}
}
return container
}()
var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
func saveContext() {
if context.hasChanges {
try? context.save()
}
}
}
When to Use CoreData
- Complex relational data
- Large datasets
- Need for queries and filtering
- Data synchronization requirements
3. Keychain - Secure Storage
// Keychain wrapper
class KeychainManager {
static let shared = KeychainManager()
func save(key: String, data: Data) -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data
]
SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil) == errSecSuccess
}
func load(key: String) -> Data? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: true
]
var item: CFTypeRef?
if SecItemCopyMatching(query as CFDictionary, &item) == errSecSuccess {
return item as? Data
}
return nil
}
}
When to Use Keychain
- Passwords and authentication tokens
- Sensitive user data
- Cryptographic keys
- Data that should survive app deletion
4. SwiftData - Modern Data Persistence
// SwiftData model
import SwiftData
@Model
class User {
var name: String
var email: String
var createdAt: Date
init(name: String, email: String) {
self.name = name
self.email = email
self.createdAt = Date()
}
}
// SwiftData usage
struct ContentView: View {
@Environment(\.modelContext) private var context
@Query private var users: [User]
var body: some View {
List(users) { user in
Text(user.name)
}
.onAppear {
addUser()
}
}
private func addUser() {
let newUser = User(name: "John", email: "john@example.com")
context.insert(newUser)
try? context.save()
}
}
When to Use SwiftData
- SwiftUI applications
- Modern iOS development (iOS 17+)
- Type-safe data modeling
- Replacing CoreData in new projects
Decision Matrix
Storage Method | Use Case | Data Size | Security |
---|---|---|---|
UserDefaults | Settings, preferences | Small (<1MB) | Low |
CoreData | Complex relational data | Large | Medium |
Keychain | Sensitive data | Small | High |
SwiftData | Modern SwiftUI apps | Medium-Large | Medium |