[iOS] 데이터를 저장하는 방법들 / 간단 예제
2024. 12. 31. 07:42ㆍ🍏/Swift
간단한 예제들과 함께 알아보는 iOS에서 데이터를 저장하는 방법
1. UserDefaults
NSUserDefaults에 저장되어 키-값 쌍으로 앱이 삭제되기 전까지 영구적으로 저장됩니다.
간단하고 사용이 쉽습니다만 대규모 데이터나 민감한 데이터에 적합하지 않습니다.
import Foundation class UserDefaultsHelper { static let shared = UserDefaultsHelper() private let launchCountKey = "launchCount" private init() {} // 실행 횟수 읽기 func getLaunchCount() -> Int { return UserDefaults.standard.integer(forKey: launchCountKey) } // 실행 횟수 업데이트 func updateLaunchCount() { let currentCount = getLaunchCount() UserDefaults.standard.set(currentCount + 1, forKey: launchCountKey) print("App launched \(currentCount + 1) times") } } // 사용 예제 let userDefaultsHelper = UserDefaultsHelper.shared userDefaultsHelper.updateLaunchCount()
2. Keychain
iOS보안 프레임워크 기반으로 높은 보안으로 저장됩니다. 데이터는 앱 삭제 후에도 유지가 가능합니다.
민감한 데이터 저장에 최적되어있지만 사용이 복잡하며, 키-값 쌍 저장밖에 하지 못합니다.
import Security class KeychainHelper { static let shared = KeychainHelper() private init() {} // 데이터 저장 func savePassword(_ password: String, for account: String) { let passwordData = password.data(using: .utf8)! let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecValueData as String: passwordData ] SecItemDelete(query as CFDictionary) // 기존 데이터 삭제 let status = SecItemAdd(query as CFDictionary, nil) if status == errSecSuccess { print("Password saved successfully") } else { print("Failed to save password") } } // 데이터 읽기 func getPassword(for account: String) -> String? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne ] var item: CFTypeRef? let status = SecItemCopyMatching(query as CFDictionary, &item) if status == errSecSuccess, let data = item as? Data { return String(data: data, encoding: .utf8) } print("Failed to retrieve password") return nil } } // 사용 예제 let keychainHelper = KeychainHelper.shared keychainHelper.savePassword("securePassword123", for: "userAccount") if let password = keychainHelper.getPassword(for: "userAccount") { print("Retrieved password: \(password)") }
3. Core Data
메모리와 디스크를 효율적으로 사용하며, 데이터 캐싱이 가능합니다.
데이터 모델링 및 관계형 데이터 관리에 적합하지만, 초기 설정 및 학습이 다소 복잡합니다.
import Foundation import CoreData class CoreDataHelper { static let shared = CoreDataHelper() private let persistentContainer: NSPersistentContainer private init() { persistentContainer = NSPersistentContainer(name: "AppModel") persistentContainer.loadPersistentStores { _, error in if let error = error { fatalError("Failed to load Core Data stack: \(error)") } } } var context: NSManagedObjectContext { return persistentContainer.viewContext } // 데이터 저장 func saveTask(name: String) { let task = Task(context: context) task.name = name task.date = Date() saveContext() } // 데이터 읽기 func fetchTasks() -> [Task] { let fetchRequest: NSFetchRequest<Task> = Task.fetchRequest() do { return try context.fetch(fetchRequest) } catch { print("Failed to fetch tasks: \(error)") return [] } } private func saveContext() { if context.hasChanges { do { try context.save() print("Data saved successfully") } catch { print("Failed to save data: \(error)") } } } } // 사용 예제 let coreDataHelper = CoreDataHelper.shared coreDataHelper.saveTask(name: "Buy groceries") let tasks = coreDataHelper.fetchTasks() tasks.forEach { print("Task: \($0.name ?? "Unnamed")") }
4. SQLite
경량 SQL DB입니다. 구조화된 데이터를 효율적으로 저장하고 조회할 수 있습니다.
플랫폼 독립적이여서 꺼내서 다른곳에서 읽을 수도 있습니다. SQL쿼리를 알아야 사용할 수 있다는 러닝커브가 존재합니다.
import Foundation import SQLite3 class SQLiteHelper { static let shared = SQLiteHelper() private var db: OpaquePointer? private let dbName = "AppData.sqlite" private let writeQueue = DispatchQueue(label: "com.usicroom.sqlite.write", attributes: .concurrent) private init() { openDatabase() createTable() } private func openDatabase() { let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) .first! .appendingPathComponent(dbName) if sqlite3_open(fileURL.path, &db) != SQLITE_OK { print("Error opening database") } else { print("Database opened successfully") } } private func createTable() { let createTableQuery = """ CREATE TABLE IF NOT EXISTS AppUsage ( id INTEGER PRIMARY KEY, launchCount INTEGER ); """ var statement: OpaquePointer? if sqlite3_prepare_v2(db, createTableQuery, -1, &statement, nil) == SQLITE_OK { if sqlite3_step(statement) == SQLITE_DONE { print("Table created or already exists") } else { print("Failed to create table") } } else { print("Error preparing create table statement") } sqlite3_finalize(statement) } // 실행 횟수 읽기 (동기) func getLaunchCount() -> Int { let query = "SELECT launchCount FROM AppUsage WHERE id = 1 LIMIT 1;" var statement: OpaquePointer? var count = 0 if sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK { if sqlite3_step(statement) == SQLITE_ROW { count = Int(sqlite3_column_int(statement, 0)) } } else { print("Error preparing select statement") } sqlite3_finalize(statement) return count } // 실행 횟수 읽기 (비동기) func getLaunchCountAsync(completion: @escaping (Int) -> Void) { DispatchQueue.global().async { [weak self] in guard let self = self else { return } let count = self.getLaunchCount() DispatchQueue.main.async { completion(count) } } } // 실행 횟수 업데이트 (비동기, 멀티스레드 지원) func updateLaunchCount(_ newCount: Int, completion: (() -> Void)? = nil) { writeQueue.async(flags: .barrier) { [weak self] in guard let self = self else { return } let updateQuery = """ INSERT INTO AppUsage (id, launchCount) VALUES (1, ?1) ON CONFLICT(id) DO UPDATE SET launchCount = ?1; """ var statement: OpaquePointer? if sqlite3_prepare_v2(self.db, updateQuery, -1, &statement, nil) == SQLITE_OK { sqlite3_bind_int(statement, 1, Int32(newCount)) if sqlite3_step(statement) == SQLITE_DONE { print("Launch count updated to \(newCount)") } else { print("Failed to update launch count") } } else { print("Error preparing update statement") } sqlite3_finalize(statement) DispatchQueue.main.async { completion?() } } } deinit { sqlite3_close(db) } }
5. File System
파일 형태로 데이터를 직접 저장하는 방식입니다. 원하는 형식의 데이터를 자유롭게 저장 가능하나 데이터 검색 및 관리가 복잡해 질 수 있습니다.
import Foundation class FileSystemHelper { static let shared = FileSystemHelper() private let fileName = "userSettings.json" private init() {} private var fileURL: URL { let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! return documentDirectory.appendingPathComponent(fileName) } // 데이터 저장 func saveSettings(_ settings: [String: Any]) { do { let data = try JSONSerialization.data(withJSONObject: settings, options: .prettyPrinted) try data.write(to: fileURL) print("Settings saved to \(fileURL)") } catch { print("Failed to save settings: \(error)") } } // 데이터 읽기 func loadSettings() -> [String: Any]? { do { let data = try Data(contentsOf: fileURL) return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] } catch { print("Failed to load settings: \(error)") return nil } } } // 사용 예제 let fileSystemHelper = FileSystemHelper.shared fileSystemHelper.saveSettings(["theme": "dark", "fontSize": 14]) if let settings = fileSystemHelper.loadSettings() { print("Loaded settings: \(settings)") }
6. Cloud Kit
iCloud와 연동하여 데이터를 클라우드에 저장합니다. 애플 생태계에 최적화된 클라우드 솔루션이며 동기화 및 공유 데이터에 적합합니다.
import CloudKit class CloudKitHelper { static let shared = CloudKitHelper() private let container = CKContainer.default() private let publicDB: CKDatabase private init() { publicDB = container.publicCloudDatabase } // 데이터 저장 func saveRecord(title: String, content: String) { let record = CKRecord(recordType: "Memo") record["title"] = title record["content"] = content publicDB.save(record) { savedRecord, error in if let error = error { print("Failed to save record: \(error)") } else { print("Record saved: \(savedRecord!)") } } } // 데이터 읽기 func fetchRecords(completion: @escaping ([CKRecord]) -> Void) { let query = CKQuery(recordType: "Memo", predicate: NSPredicate(value: true)) publicDB.perform(query, inZoneWith: nil) { records, error in if let error = error { print("Failed to fetch records: \(error)") completion([]) } else { completion(records ?? []) } } } } // 사용 예제 let cloudKitHelper = CloudKitHelper.shared cloudKitHelper.saveRecord(title: "First Note", content: "This is a test note.") cloudKitHelper.fetchRecords { records in records.forEach { print("Title: \($0["title"] ?? ""), Content: \($0["content"] ?? "")") } }
7. Realm
경량 객체 기반 DB입니다. Core Data보다 간단하고 직관적입니다. 관계형 데이터 관리와 쿼리 성능이 우수합니다.
외부라이브러리를 관리해야하는 번거로움이 있습니다.
import RealmSwift class Task: Object { @objc dynamic var id = UUID().uuidString @objc dynamic var name = "" @objc dynamic var date = Date() override static func primaryKey() -> String? { return "id" } } class RealmHelper { static let shared = RealmHelper() private let realm = try! Realm() private init() {} // 데이터 저장 func saveTask(name: String) { let task = Task() task.name = name try! realm.write { realm.add(task) print("Task saved: \(name)") } } // 데이터 읽기 func fetchTasks() -> Results<Task> { return realm.objects(Task.self) } } // 사용 예제 let realmHelper = RealmHelper.shared realmHelper.saveTask(name: "Read a book") let tasks = realmHelper.fetchTasks() tasks.forEach { print("Task: \($0.name), Date: \($0.date)") }

'🍏 > Swift' 카테고리의 다른 글
[iOS / macOS] 내 맥북에서 서버를 만들고 내 아이폰과 소켓 통신을 해보자 (0) | 2024.12.31 |
---|---|
[iOS] 에러 처리 / Error Handling (0) | 2024.12.30 |
[Swift] 접근 제어자(Access Control Levels) (0) | 2024.12.29 |
[iOS] Protocol / 프로토콜 / 개념 의미 요구사항 확장 POP (0) | 2024.12.29 |
[Swift] Tail Call Optimization / TCO / 꼬리재귀 (0) | 2024.03.16 |