티스토리 뷰
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을 생성하고 저장한 애플리케이션에만 접근 가능
- 같은 개발자가 개발한 여러 앱에서 키체인 정보 공유 가능
어떤 데이터 저장?
- 토큰
- 비밀번호
- 인증서
- api key
- ...등등
보안에 민감한 정보들을 저장한다.
Keychain item
Keychain에 저장된 Data 단위이며, keychain은 하나 이상의 Keychain item을 가질 수 있다.
Keychain item class
Apple에서는 Keychain에 저장할 데이터의 종류에 따라서 Item Class를 구분했다.
Item Class에 따라서 설정할 수 있는 속성도 달라지는 것이다.
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에 해당하는 속성들을 확인해보자.
속성들이 많은데 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 데이터베이스 모델을 사용하고 성능이 중요한 애플리케이션에 적합하다.
참고
'스위프트' 카테고리의 다른 글
[Swift] - TestFlight로 배포해보기 (0) | 2024.08.23 |
---|---|
[Swift] - iOS 프로젝트 배포 환경별 build 세팅 (2) | 2024.08.23 |
[Swift] - Multiple commands produce Error (0) | 2024.05.05 |
[Swift] - 클로저(Closure) (0) | 2024.05.01 |
[SwiftUI] - App, Scene, View, WindowGroup 이해 (0) | 2024.04.18 |