[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 |