2023년 1월 1일
08:00 AM
Buffering ...

최근 글 👑

[Swift-Study] iOS 앱 개발 숙련 1주차 - 내부 저장소

2024. 4. 12. 21:41ㆍIOS/Swift-Study
SMALL

내부 저장소란?

앱 종료와 함께 사라지지 않는, 앱이 기억하고 있어야 할 비휘발성 데이터를 저장하기 위해 사용되는 개념

디바이스에 저장되기에 많은 데이터를 저장하기에는 부적합


1. Device에 데이터를 저장하는 여러가지 방법들

1-1. UserDefaults

UserDefaults는 간단한 Key-Value 데이터를 저장하는데 사용

 

주로 설정 값이나 사용자 기본 설정과 같은 작은 데이터를 저장할 때 사용

(여기서 key값은 반드시 'String' 타입)

 

// 데이터 추가 및 업데이트
UserDefaults.standard.set(value, forKey: key)

// 데이터 조회 | Any? 타입이여서 as를 사용한 타입 변환 후 사용
UserDefaults.standard.value(forKey: key)

// 데이터 조회 | 특정 타입으로 반환할 경우 (String)
UserDefaults.standard.string(forKey: key)

// 데이터 삭제
UserDefaults.standard.removeObject(forKey: key)

1-2. KeyChain

iOS 앱에서 보안 정보를 안전하게 저장하고 관리하기 위한 메커니즘

키체인은 사용자의 비밀번호, 토큰, 인증서, 민감한 데이터와 같은 보안 관련 정보를 저장하는데 사용

키체인은 암호화 → 앱 외부에서 쉽게 접근불가

사용자 데이터 안전성 보장

// ADD
// Keychain에 저장할 데이터를 추가하는 쿼리 생성
let addQuery: [String: Any] = [
  kSecClass as String: kSecClassGenericPassword, // Keychain 내에서 다루고자 하는 데이터의 유형을 지정
  kSecAttrService as String: "service", // 데이터를 저장하는 서비스나 앱을 식별하는데 사용
  kSecAttrAccount as String: "key", // Keychain에서 데이터를 찾거나 식별하는데에 사용
  kSecValueData as String: "data" // 실제로 Keychain에 저장될 데이터를 의미
]

// 데이터를 Keychain에 추가
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
// GET
// Keychain에서 데이터를 조회 위한 쿼리 생성
let getQuery: [String: Any] = [
  kSecClass as String: kSecClassGenericPassword, // Keychain 내에서 다루고자 하는 데이터의 유형을 지정
  kSecAttrService as String: "service", // 데이터를 저장하는 서비스나 앱을 식별하는데 사용
  kSecAttrAccount as String: "key", // Keychain에서 데이터를 찾거나 식별하는데에 사용
  kSecReturnData as String: kCFBooleanTrue, // Keychain에서 데이터를 가져올 때 반환할 데이터의 형태를 지정
  kSecMatchLimit as String: kSecMatchLimitOne // 일치하는 항목의 수를 제한하는데 사용
]

// 데이터를 Keychain에서 조회
var item: CFTypeRef?
let getStatus = SecItemCopyMatching(getQuery as CFDictionary, &item)
// UPDATE
// Keychain에서 데이터를 업데이트하기 위한 쿼리 생성
let updateQuery: [String: Any] = [
  kSecClass as String: kSecClassGenericPassword, // Keychain 내에서 다루고자 하는 데이터의 유형을 지정
  kSecAttrService as String: "service", // 데이터를 저장하는 서비스나 앱을 식별하는데 사용
  kSecAttrAccount as String: "key", // Keychain에서 데이터를 찾거나 식별하는데에 사용
]

// 업데이트할 데이터로 속성 설정
let attributes: [String: Any] = [
  kSecValueData as String: "newData"
]

// 데이터를 Keychain에서 업데이트
let updateStatus = SecItemUpdate(updateQuery as CFDictionary, attributes as CFDictionary)
// DELETE
// Keychain에서 데이터를 삭제하기 위한 쿼리 생성
let deleteQuery: [String: Any] = [
  kSecClass as String: kSecClassGenericPassword, // Keychain 내에서 다루고자 하는 데이터의 유형을 지정
  kSecAttrService as String: "service", // 데이터를 저장하는 서비스나 앱을 식별하는데 사용
  kSecAttrAccount as String: "key", // Keychain에서 데이터를 찾거나 식별하는데에 사용
]
// 데이터를 Keychain에서 삭제
let deleteStatus = SecItemDelete(deleteQuery as CFDictionary)

1-3. 파일 시스템

앱 내부 또는 외부 파일 시스템에 데이터 저장 가능

(주로 텍스트, 이미지, 동영상 등의 파일 데이터를 저장할 때 사용)

// WRITE
let fileName = "example.txt"
let content = "이것은 파일에 쓰여진 내용"
  
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
  let fileURL = dir.appendingPathComponent(fileName)
        
  do {
    try content.write(to: fileURL, atomically: false, encoding: .utf8)
    print("파일이 성공적으로 생성됨.")
  } catch {
    print("파일 생성 중 에러가 발생: \(error.localizedDescription)")
  }
}
// READ
let fileName = "example.txt"
    
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
  let fileURL = dir.appendingPathComponent(fileName)
        
  do {
    let content = try String(contentsOf: fileURL, encoding: .utf8)
    print("파일 내용: \(content)")
  } catch {
    print("파일 읽기 중 에러가 발생: \(error.localizedDescription)")
  }
}

1-4. Core Data

iOS 앱에서 데이터를 관리 → 영구적으로 저장에 사용되는 프레임워크

데이터 모델을 정의 → 데이터를 읽고 사용

더보기

* Core Data 사용 설정하기 *

Core Data를 사용을 위해 기본적인 프로젝트 설정방법

( 프로젝트 생성 시, "Core Data"를 선택하였다면 해당 단계 건너뛰기 가능)

* 데이터 모델 만들기 *

command + N 또는 File - New - File... → Core Data 섹션의 Data Model을 생성

 


1-5. Core Data Stack 설정

"AppDelegate.swift" 파일 → "Core Data 스택(Stack)"을 설정가능

"NSPersistentContainer" 를 생성 시 "name" → 앞서 생성한 "Data Model"의 파일명과 동일하게 입력

import CoreData

// Core Data 스택
lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: /* 생성한 Data Model 명 */)
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
}()

// MARK: - Core Data Saving support

func saveContext() {
    let context = persistentContainer.viewContext
    if context.hasChanges {
        do {
            try context.save()
        } catch {
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    }
}
더보기

* Core Data Stack의 개념 *

데이터를 관리 및 저장을 위한 "Core Data" 프레임워크의 핵심 구성 요소들의 집합

 

* Managed Object Model (NSManagedObejctModel) *

애플리케이션의 데이터 모델을 정의하는 객체

엔티티(Entity)와 그들의 속성들을 정의 이러한 모델은 데이터베이스 스키마에 해당

 

* Managed Object Context (NSManagedObjectContext) *

애플리케이션의 데이터를 가져오거나 수정하는데 사용되는 객체

(주로 메모리에서 데이터를 관리하고 데이터베이스와 상호 작용)

 

* Persistent Store Coordinator (NSPersistentStoreCoordinator) *

영구 저장소를 관리, 데이터베이스와의 통신을 담당하는 객체

SQLite, XML, Binary 등 다양한 데이터 저장소 유형과의 연결을 관리

 

* Persistent Store (NSPersistentStore) *

실제로 데이터를 영구적으로 저장하는데 사용되는 데이터베이스 파일 or 다른 형태의 저장소


2. Data Model 파일 구성 (데이터 모델링)

'Data Model' 파일의 구성요소를 이해저장할 데이터 구조를 모델링


2-1 Entity (엔티티)

데이터베이스의 테이블과 비슷한 개념인 'Entity(엔티티)'는

데이터 모델 내에서 객체의 유형을 정의

해당 객체들이 갖는 속성들을 나타냄


2-2. Entity(엔티티) 추가

편집기 영역 하단에서 'Add Entity' 를 클릭 시 'Entity'가 생성 → 'Entities List'에 'Entity' 표시

'Entities List(엔티티 리스트)' 에서 새로 추가된 개체를 두 번 클릭 

이름변경

(이 단계에서 데이터 모델 검사기에 표시되는 'Entity(엔티티)'이름과 클래스 이름을 모두 업데이트)

'View' - 'Inspectors' - 'Show Data Model Inspector'를 사용

'Data Model Inspector'를 열어, 'Entity'를 구성가능


2-3. Entity 주요 속성들

Entity Name Abstract Entity Parent Entity Class Name Module
'Entity' 이름  해당 'Entity'를
추상(Abstract) 'Entity'로
만들 경우


해당 옵션 선택
유사 "Entity"가
여러 개 있는 경우

'Parent Entity'를 정의 

공통 속성 정의
이 'Entity'의
인스턴스 생성 시

사용할 'Class Name'
'Entity class'가 존재 하는 모듈
'Entities List'

표시된 이름과
동일
기본적으로 이 옵션은 선택 X

데이터베이스에 실제로 저장되는
'Entity'의 형태

'concrete Entity'가 생성
'Child Entity'

해당 속성 상속가능
(기본적으로 이 필드는 비어있음)
기본적으로
'Class Name'은
'Entity'의 이름

미러링
기본적으로
'Core Data'

전역 네임 스페이스 클래스 파일을 찾음
  'Abstract Entity'는 상속 통해 확장

공통적 특성을 가진 여러 'Entity'

효율적 관리가능
  반대
'Class Name'
변경한 경우

'Entity' 이름변경 X
 

2-4. Attribute

데이터베이스 'column(컬럼)' 혹은 'attribute(엣트리부트)'와 비슷한 개념인

'Attribute'는, 'Entity(엔티티)'가 가지는 속성들을 나타냄


2-5. Attribute 추가

'Entity' 선택한 상태 → 편집기 영역 아래쪽 'Add Attribute' 클릭 시 → 'Attributes List'에 해당에 'Attribute'가 표시

'Attributes List' → 새로 추가된 'Attribute' 두 번 클릭 → 이름바꿈

아래 두번째 이미지처럼, 해당 데이터의 'Type'을 선택 가능

 

'View' - 'Inspectors' - 'Show Data Model Inspector' 를 사용

'Data Model Inspector' → Attribute를 구성 가능

'Transformable(트랜스포머블)' 타입 선택 → 오른쪽 'Inspector(인스펙터)'영역 → 'Custom Class'를 지정

리스트에 노출되는 기본적인 타입 외 커스텀한 타입을 저장 가능


2-6. Attribute 주요 속성들

Attribute Type Optional Validation Default Value
'Attribute'의 데이터 형식 해당 'Attribute'의
필수 속성 선택 가능
해당 'Attribute' 의
유효성 검사 옵션 지정 가능
데이터가(데이터베이스에서의 행) 생성되는 시점

해당 'Attribute'의
기본값 지정 가능
'Attribute List'에서

선택한 타입과 동일
     

2-7. Relationship

두 개 이상 'Entity'를 정의 → 'Entity' 간의 관계 추가 가능


2-8. Relationship(릴레이션쉽) 추가

'Entity' 선택 상태 → 'Relationship' 섹션의 좌측 하단 + 버튼 누를 시

'Relationships List'에 해당에 'Relationship'이 표시

'Relationships List'에서 새로 추가된 'Relationship'을 두 번 클릭 → 이름 변경

'View' - 'Inspectors' - 'Show Data Model Inspector' 를 사용

'Data Model Inspector'를 열어, Relationship을 구성가능


2-9. Relationship 주요 속성

Optional Destination Delete Rule Type
해당 'Relationship'
필수 속성선택 가능
관계 설정 대상이 되는
'Entity'를 선택
'Core Data'가 원본 인스턴스를 삭제할 때 변경 내용이
관계 'Relationship'간에
전파되는 방식을 지정
'Relationship'을 1:1(To One) 또는
1:n(To Many)로 지정 할지 선택
    'No Action'

원본 객체 인스턴스를 삭제하되
수동으로 업데이트하는 모든 대상, 객체 인스턴스에 해당 객체에 대한 참조를 남겨 둠
 
    'Nullify'

'Destination'의
참조를 'nil'로 설정
 
    'Cascade(캐스케이드)'

'Destination(데이스터네이션)' 객체를 연쇄적으로 삭제
 
    'Deny'

아무 'Destination'을
가리키지 않는 경우에만
원본 개체를 삭제
 

2-10. 코드에서 Data 사용

위에서 정의한 'Data Model' 기반,

코드에서 데이터의 CRUD(크러드)하는 방식

아래 예제코드 'Entity'의 스펙

2-11. 'persistentContainer'에 접근

'appDelegate'에 정의한 'persistentContainer'에 다음과 같이 접근 가능

import CoreData

var persistentContainer: NSPersistentContainer? {
    (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer
}

2-12. 데이터 쓰기 (Create) - 데이터 작성 방법

// name이 “benz”인 Data를 생성하는 코드
guard let context = self.persistentContainer?.viewContext else { return }

let newCar = Car(context: context)

newCar.id = UUID()
newCar.name = "benz"

try? context.save()

2-13. 데이터 읽기 (Read) - 데이터 조회 방법

// Car Entity타입의 모든 Data를 조회하는 코드
guard let context = self.persistentContainer?.viewContext else { return }

let request = Car.fetchRequest()        
let cars = try? context.fetch(request)

print(cars)

2-14. 데이터 수정 (Update) - 데이터 수정 방법

// name이 “benz”인 모든 Car의 name을 “tesla”로 수정하는 코드
guard let context = self.persistentContainer?.viewContext else { return }

let request = Car.fetchRequest()

guard let cars = try? context.fetch(request) else { return }

let filteredCars = cars.filter({ $0.name == "benz" })

for car in filteredCars {
  car.name = "tesla"
}

try? context.save()

2-15. 데이터 삭제 (Delete) - 데이터 삭제 방법

// name이 “tesla”인 모든 Car를 삭제하는 코드
guard let context = self.persistentContainer?.viewContext else { return }

let request = Car.fetchRequest()

guard let cars = try? context.fetch(request) else { return }

let filteredCars = cars.filter({ $0.name == "tesla" })

for car in filteredCars {
    context.delete(car)
}

try? context.save()
728x90