티스토리 뷰

스위프트

[Swift] - UserDefaults, Keychain, Core Data

강철곰탱이 2024. 5. 7. 04:15

 

iOS 면접 준비를 위한 4탄이다. Swift에서 데이터 관리를 위해 사용되는 UserDefaults, Keychain, Core Data에 대해 알아보자.

 

- 아래 링크 참고 

 

질문 리스트

1. UserDefaults의 사용 예시와 주의 사항을 설명해주세요.

2. Keychain은 어떤 데이터를 저장하는 데 적합한가요?
3. Core Data와 SQLite의 차이점은 무엇인가요?

 


UserDefaults

 

"앱 실행 시 지속적으로 key-value 쌍을 저장하는 사용자의 기본 데이터베이스에 대한 인터페이스이다."

 

  • Info.plist와 유사한 구조로 저장하며, plist에 데이터 저장
  • UserDefaults.plist는 앱 폴더 내의 Library 폴더에 저장
  • 사용자의 정보를 저장하는 싱글톤 인스턴스
  • 간단한 사용자 정보 저장 및 불러오기는 가능하지만, 내부적으로 plist 파일에 저장되므로 보안 취약
  • 앱을 꺼도 데이터가 유지되며, 앱을 삭제한 경우에만 해당 파일에 저장한 데이터가 제거된다.

 

 

어떤 데이터 저장?

 

  • 자동 로그인 여부
  • 아이디 저장
  • 알림 설정 여부
  • 사용자 정보
  • 팝업창 띄우기 여부 
  • ...등등

 

큰 데이터는 넣지 않는 것이 좋다.

=> 앱이 실행될 때 UserDefaults의 plist 파일이 메모리에 한 번에 load 되기 때문이다!

 

보안에 취약하기 때문에 민감한 데이터 api key, token, 비밀번호 등을 저장하지 않도록 한다.

 

✏️ UserDefaults의 주요 항목

 

//UserDefaults 싱글톤 인스턴스
class var standard: UserDefaults { get }

//데이터 불러오기
func object(forKey defaultName: String) -> Any?
func string(forKey defaultName: String) -> String?
func array(forKey defaultName: String) -> [Any]?

//데이터 저장하기
func set(_value: Any?, forKey defaultName: String)

//데이터 지우기
func removeObject(forKey defaultName: String)

 

UserDefaults의 standard는 UserDefaults 클래스의 싱글톤 인스턴스를 나타내기 때문에 데이터 저장/조회/삭제를 하기 위해서 'UserDefaults.standard'를 사용해야 한다.

 

🟡 데이터 저장

 

UserDefaults에 key-value 쌍으로 데이터를 저장할 수 있고 set 명령어를 사용해 key와 value를 입력해 준다. 

 

UserDefaults.standard.set({value}, forKey: {key})

 

 

🟡 데이터 불러오기

 

UserDefaults에 저장된 데이터를 불러올 수 있고 string 명령어를 사용하여 key에 해당하는 value를 가져온다.

 

UserDefaults.standard.string(forKey: {key})

 

아래와 같이 UserDefaults 사용하는 예시로 확인해 보자.

 

import SwiftUI

struct ContentView: View {
    // UserDefaults 키 정의
    let userDefaultsKey = "username"

    // 사용자 이름을 저장할 State 변수
    @State private var username: String = ""

    var body: some View {
        VStack {
            TextField("Enter your username", text: $username)

            Button("Save") {
                // UserDefaults에 사용자 이름 저장
                UserDefaults.standard.set(username, forKey: userDefaultsKey)
            }

            // 저장된 사용자 이름 표시
            Text("Saved Username: \(UserDefaults.standard.string(forKey: userDefaultsKey) ?? "")")
        }
        .padding()
    }
}

 

 

📖 저장/조회 로직 모듈화

 

UserDefaults를 편리하게 사용하기 위해 Model을 정의해 두고 편하게 저장과 조회를 할 수 있도록 하였다.

 

struct UserData: Codable {
    let id: Int
    let username: String
    let name: String
}

 

받아온 유저 데이터를 saveUserData 메서드로 UserDefaults에 저장한다.

 

func saveUserData(userData: UserData) {
    do {
        let userDataJSON = try JSONEncoder().encode(userData)

        UserDefaults.standard.set(userDataJSON, forKey: "userData")
        if let jsonString = String(data: userDataJSON, encoding: .utf8) {
            os_log("UserDefaults data: %@", log: .default, type: .debug, jsonString)
        }
    } catch {
        os_log("Error encoding UserData: %@", log: .default, type: .fault, error.localizedDescription)
    }
}

 

getUserData 메서드로 UserDefaults에 저장된 데이터를 조회한다.

 

func getUserData() -> UserData? {
    if let userDataJSON = UserDefaults.standard.data(forKey: "userData") {
        do {
            let userData = try JSONDecoder().decode(UserData.self, from: userDataJSON)
            return userData
        } catch {
            os_log("Error decoding UserData: %@", log: .default, type: .fault, error.localizedDescription)
        }
    }
    return nil
}

 

아래와 같이 사용할 수 있다.

 if let userData = getUserData() {
    name = userData.name
    username = userData.username
}

Keychain

 

"Apple에서 제공하는 암호화된 데이터 저장공간"

 

  • 주로 보안에 중요한 정보를 저장하는 데 사용
  • 사용자가 직접 제거하지 않는 이상, 앱을 삭제해도 데이터는 남아있다.
  • 디바이스 Lock 하면 KeyChain도 잠기고, 디바이스를 UnLock 하면 Keychain도 풀린다.
    • KeyChain이 잠긴 상태: item값의 접근, 복호화 x
    • KeyChain이 풀린 상태: 해당 item을 생성하고 저장한 애플리케이션에만 접근 가능
  • 같은 개발자가 개발한 여러 앱에서 키체인 정보 공유 가능

https://developer.apple.com/documentation/security/keychain_services

 

어떤 데이터 저장?

 

  • 토큰
  • 비밀번호
  • 인증서
  • api key
  • ...등등

보안에 민감한 정보들을 저장한다.

 

Keychain item 

 

Keychain에 저장된 Data 단위이며, keychain은 하나 이상의 Keychain item을 가질 수 있다.

 

https://developer.apple.com/documentation/security/keychain_services/keychain_items

 

Keychain item class

 

Apple에서는 Keychain에 저장할 데이터의 종류에 따라서 Item Class를 구분했다.

Item Class에 따라서 설정할 수 있는 속성도 달라지는 것이다.

 

https://developer.apple.com/documentation/security/keychain_services/keychain_items/item_class_keys_and_values

 

Item Class는 총 5가지가 있다.

 

  • kSecClassGenericPassword : 일반 암호를 저장할 때 사용
  • kSecClassInternetPassword : 인터넷에서 불러온 암호를 저장할 때 사용
  • kSecClassCertificate : 인증서를 저장할 때 사용
  • kSecClassKey : 암호화 키 항목을 저장할 때 사용
  • kSecClassIdentity : ID 항목을 저장할 때 사용

 

필요에 따라 골라서 사용하면 된다.

 

 

KeychainHelper 구현

 

 

🟡 accessToken 저장

keychainQuery에 Keychain에 저장할 데이터와 관련된 정보를 포함하는 딕셔너리를 생성한다.

 

let keychainQuery: [CFString: Any] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrAccount: "accessToken",
    kSecValueData: accessToken.data(using: .utf8)!,
]

 

  • kSecClass:  저장할 항목의 클래스를 지정
  • kSecAttrAccount: 저장할 항목의 계정을 지정
  • kSecValueData: 저장할 데이터를 지정

 

kSecClassGenericPassword에 해당하는 속성들을 확인해보자.

 

https://developer.apple.com/documentation/security/ksecclassgenericpassword

 

속성들이 많은데 kSecAttrAccount 이걸 선택하여 구현했다.

 

let status = SecItemAdd(keychainQuery as CFDictionary, nil)

 

SecItemAdd 함수를 사용하여 Keychain에 데이터를 추가한다.

첫 번째 매개변수로 Keychain에 추가할 데이터를, 두 번째 매개변수로는 추가 작업의 결과를 전달받을 포인터를 받는다.

 

if status == errSecDuplicateItem {
    SecItemUpdate(keychainQuery as CFDictionary, [kSecValueData: accessToken.data(using: .utf8)!] as CFDictionary)
} else if status != noErr {
    print("Failed to save AccessToken to Keychain")
}

 

status를 확인하여 작업이 성공했는지, 중복된 아이템이 있었는지, 오류가 발생했는지 확인한다.

 

만약 중복된 아이템이 발견되었다면, SecItemUpdate 함수를 사용하여 이미 존재하는 데이터를 업데이트한다.

그렇지 않은 경우 오류가 발생했다는 메시지 출력!

 

 

 

🟡 accessToken 불러오기

 

query라는 변수에 Keychain에서 데이터를 검색하는 데 사용될 검색 쿼리를 포함하는 딕셔너리를 생성한다.

 

let query: [CFString: Any] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrAccount: "accessToken",
    kSecReturnData: kCFBooleanTrue!,
]

 

  • kSecClass:  저장할 항목의 클래스를 지정
  • kSecAttrAccount: 저장할 항목의 계정을 지정
  • kSecReturnData: 검색 결과로 데이터를 반환하도록 지정

 

var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)

 

SecItenCopyMatching 함수를 사용해 Keychain에서 데이터를 검색한다. 

첫 번째 매개변수로 검색할 쿼리를, 두 번째 매개변수로 검색 결과를 전달받을 포인터를 받는다.

 

=> Keychain에서 데이터를 검색하고 그 결과를 status 변수에 저장한다.

 

if status == noErr, let data = item as? Data, let token = String(data: data, encoding: .utf8) {
    return token
} else {
    return nil
}

 

검색 작업이 성공적으로 완료되었는지 확인하고, 성공했다면 검색된 데이터를 UTF-8 형식의 문자열로 반환한다. 

 

- 최종 코드

class KeychainHelper {
    static func saveAccessToken(accessToken: String) {
        let keychainQuery: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: "accessToken",
            kSecValueData: accessToken.data(using: .utf8)!,
        ]
        
        let status = SecItemAdd(keychainQuery as CFDictionary, nil)
        if status == errSecDuplicateItem {
            SecItemUpdate(keychainQuery as CFDictionary, [kSecValueData: accessToken.data(using: .utf8)!] as CFDictionary)
        } else if status != noErr {
            print("Failed to save AccessToken to Keychain")
        }
    }
    
    static func loadAccessToken() -> String? {
        let query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: "accessToken",
            kSecReturnData: kCFBooleanTrue!,
        ]
        
        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        
        if status == noErr, let data = item as? Data, let token = String(data: data, encoding: .utf8) {
            return token
        } else {
            return nil
        }
    }
 }

 


Core Data

 

"앱에서 모델 계층 개체를 관리하는 데 사용하는 프레임워크"

 

UserDefaults와 비슷하지만 좀 더 복잡한 데이터를 저장하기 위해서는 Core Data가 적합하다고 한다.

앱에서 직접 DB를 직접 접근해서 쓰지 않아도 되고 해당 기기에 데이터를 저장하므로 오프라인에서도 동작 가능하다!

 

1️⃣ 기능 및 특징

 

 

1. 영속성

 

Core Data는 객체를 저장소에 매핑하는 세부 정보를 추상화하여 DB를 직접적으로 관리하지 않아도 데이터를 쉽게 저장할 수 있도록 한다.

 

2. Undo, Redo

 

Core Data의 undo 관리자는 변경사항을 추적하고 개별적으로나 그룹적으로 한번에 롤백할 수 있어 undo 및 redo를 쉽게 추가할 수 있도록한다.

 

3. Background Data Tasks 

 

JSON을 객체로 파싱하는 작업과 같이 UI를 블락할 수 있는 데이터 작업의 경우 백그라운데서 수행할 수 있다.

 

4. View Synchronization

 

Core Data는 뷰와 데이터 간의 싱크를 맞추는 걸 도와줄 수 있다. (테이블 뷰, 콜렉션 뷰에 데이터소스로 제공되는 방식으로)

 

5. Versioning and Migration(버전 관리 및 마이그레이션)

 

Core Data는 데이터 모델의 버전을 관리할 수 있게 하고 앱이 진화할 수록 사용자  데이터를 옮길 수 있는 메커니즘을 제공한다.

 

++ Core Data를 직접 구현해야 더 확실히 이해할 수 있을 것 같다...!

 

Core Data와 SQLite 차이

 

 

가장 큰 차이는 Core Data는 애플에서 제공하는 프레임워크, SQLite는 애플에서 제공하지 않는 외부 라이브러리로 데이터베이스이다.

 

 

Core Data

 

: 앱에서 모델 계층 개체를 관리하는 데 사용하는 프레임워크

주로 iOS 및 macOS 애플리케이션 데이터 관리에 사용

 

 

SQLite

 

: 서버가 아닌 응용 프로그램에 넣어 사용하는 경량의 관계형 데이터 베이스

모바일 및 데스크탑 애플리케이션부터 대규모 웹 애플리케이션까지 다양한 환경에서 사용된다.

 

직접 SQL 쿼리를 작성해야 하므로 약간의 학습이 필요할 수 있다.


 

 

=> SQLite는 경량 데이터 처리가 필요한 경우, Core Data는 복잡한 객체 그래프 관리가 필요한 경우에 사용한다.

 

Core Data가 SQLite보다 더 빠르게 기록을 가져올 수 있지만 더 많은 메모리와 저장공간을 사용한다.

 

  • 속도: Core Data > SQLite
  • 메모리 및 저장 공간 사용: Core Data > SQLite

 

Core Data는 객체 그래프와 객체 지향적인 데이터 모델링을 사용하는 애플리케이션에 적합하며, SQLite는 전통적인 SQL 데이터베이스 모델을 사용하고 성능이 중요한 애플리케이션에 적합하다.

 


참고

 

1. https://adora-y.tistory.com/entry/iOS-KeyChain%EC%9D%B4%EB%9E%80-Swift%EC%BD%94%EB%93%9C%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0

 

2. https://yeonduing.tistory.com/54

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함