CRUD 개념
-> 일반적인 개발론에서 CRUD 라는 용어는 자주 사용합니다. 뜻은 다음과 같습니다.
전화번호 앱에서 일어나는 CRUD 에 대해 생각해봅시다.
데이터 CRUD 는 네이티브 (앱) 내부에서도 일어날 수 있고, 서버에서도 일어날 수 있습니다.
CoreData
CoreData
는 앱에서 기기의 디스크
에 데이터를 읽고 쓸 수 있게 돕는 프레임워크.CoreData
와 UserDefaults
가 있다.Entity
만들기. Entity 는 저장될 데이터들의 집합.PhoneBook
이라는 Entity
를 만들어보겠습니다.Entity
이름은 PhoneBook
name
, phoneNumber
를 추가합니다.전화번호부
라는 데이터 집합 안에 이름
과 전화번호
라는 속성이 부여된 것.Codegen
개념Code Generator
의 줄임말. Entity 를 어떤 형식의 코드로 생성할 것인지 선택하는 속성.Manual/None
= Entity 의 서브 클래스를 자동으로 생성하지 않고 개발자가 클래스 작성.Class Definition
= Entity 의 서브 클래스를 자동으로 생성.Category/Extension
= Entity 클래스와 함께 extension 을 위한 파일까지 생성.Manual/None
으로 생성해보겠습니다.그럼 위와 같이 2개의 파일이 생성됨.
import Foundation
import CoreData
@objc(PhoneBook)
public class PhoneBook: NSManagedObject {
}
NSManagedObject는 Core Data 프레임워크에서 관리되는 객체를 나타내는 기본 클래스. 이 클래스는 Core Data 엔티티와의 상호작용을 관리하며, 속성 값의 저장 및 검색을 처리.
PhoneBook+CoreDataProperties.swift 간단 설명.
import Foundation
import CoreData
extension PhoneBook {
@nonobjc public class func fetchRequest() -> NSFetchRequest<PhoneBook> {
return NSFetchRequest<PhoneBook>(entityName: "PhoneBook")
}
@NSManaged public var name: String?
@NSManaged public var phoneNumber: String?
}
extension PhoneBook : Identifiable {
}
@nonobjc
= Objective-C 에서는 동작하지 않고 Swift 에서만 동작하는 메서드임을 명시.fetchRequest()
= PhoneBook 에 대한 여러가지 데이터 검색을 도움.@NSManaged
= CoreData 에 의해 관리되는 객체를 의미.Identifiable
= PhoneBook 타입이 고유하게 식별될 수 있음을 의미.NSPersistentContainer
는 CoreData에서 데이터를 저장하고 관리하는 데 필요한 핵심 객체. → 직역해보면 영구적인 저장 장소.NSPersistentContainer
를 생성해줘야하는데, 프로젝트 생성할 때 CoreData
를 사용한다고 체크 해줬으므로, AppDelegate.swift
에 기본적으로 NSPersistentContainer 를 세팅하는 코드가 존재.AppDelegate.swift
아래쪽에보면 saveContext()
라는 메서드도 자동으로 생성되어있는데, 직역하면 문맥을 저장한다는 뜻.saveContext()
를 호출해서 그 문맥을 저장해야 함.import UIKit
import CoreData
class ViewController: UIViewController {
var container: NSPersistentContainer!
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
self.container = appDelegate.persistentContainer
createData(name: "Adam", phoneNumber: "010-1111-2222")
readAllData()
}
// AdamCoreData 에 데이터 Create.
func createData(name: String, phoneNumber: String) {
guard let entity = NSEntityDescription.entity(forEntityName: "PhoneBook", in: self.container.viewContext) else { return }
let newPhoneBook = NSManagedObject(entity: entity, insertInto: self.container.viewContext)
newPhoneBook.setValue(name, forKey: "name")
newPhoneBook.setValue(phoneNumber, forKey: "phoneNumber")
do {
try self.container.viewContext.save()
print("문맥 저장 성공")
} catch {
print("문맥 저장 실패")
}
}
// AdamCoreData 에서 데이터 Read.
func readAllData() {
do {
let phoneBooks = try self.container.viewContext.fetch(PhoneBook.fetchRequest())
for phoneBook in phoneBooks as [NSManagedObject] {
if let name = phoneBook.value(forKey: "name") as? String,
let phoneNumber = phoneBook.value(forKey: "phoneNumber") as? String {
print("name: \(name), phoneNumber: \(phoneNumber)")
}
}
} catch {
print("데이터 읽기 실패")
}
}
}
→ 디스크에 저장했으므로, 데이터를 저장한 코드를 삭제한 뒤 앱을 다시켜도 데이터가 남아있음을 확인 가능.
💻 여기서 잠깐 리팩토링을 해봅시다.
코드를 좀 더 나은 방향으로 개선하는 것을 리팩토링이라고 합니다.
@objc(PhoneBook)
public class PhoneBook: NSManagedObject {
public static let className = "PhoneBook"
public enum Key {
static let name = "name"
static let phoneNumber = "phoneNumber"
}
}
→ static
프로퍼티는 그 타입에 대고 호출을 할 수 있는 프로퍼티입니다.
→ PhoneBook.className
→ PhoneBook
이라는 클래스 타입에 대고 점을찍고 호출.
“phoneNumber”
라고 타자를 치지 않기 때문에 실수할 일이 적어진다 → 휴먼 에러를 줄인다.import UIKit
import CoreData
class ViewController: UIViewController {
var container: NSPersistentContainer!
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
self.container = appDelegate.persistentContainer
createData(name: "Adam", phoneNumber: "010-1111-2222")
readAllData()
}
func createData(name: String, phoneNumber: String) {
guard let entity = NSEntityDescription.entity(forEntityName: PhoneBook.className, in: self.container.viewContext) else { return }
let newPhoneBook = NSManagedObject(entity: entity, insertInto: self.container.viewContext)
newPhoneBook.setValue(name, forKey: PhoneBook.Key.name)
newPhoneBook.setValue(phoneNumber, forKey: PhoneBook.Key.phoneNumber)
do {
try self.container.viewContext.save()
print("문맥 저장 성공")
} catch {
print("문맥 저장 실패")
}
}
func readAllData() {
do {
let phoneBooks = try self.container.viewContext.fetch(PhoneBook.fetchRequest())
for phoneBook in phoneBooks as [NSManagedObject] {
if let name = phoneBook.value(forKey: PhoneBook.Key.name) as? String,
let phoneNumber = phoneBook.value(forKey: PhoneBook.Key.phoneNumber) {
print("name: \(name), phoneNumber: \(phoneNumber)")
}
}
} catch {
print("데이터 읽기 실패")
}
}
}
func updateData(currentName: String, updateName: String) {
// 수정할 데이터를 찾기 위한 fetch request 생성
let fetchRequest = PhoneBook.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "name == %@", currentName) // 예시: 이름이 "Adam"인 데이터 수정
do {
// fetch request 실행
let result = try self.container.viewContext.fetch(fetchRequest)
// 결과 처리
for data in result as [NSManagedObject] {
// 데이터 수정
data.setValue(updateName, forKey: PhoneBook.Key.name) // 이름을 "Adam"에서 "Abel"로 수정
// 변경 사항 저장
try self.container.viewContext.save()
print("데이터 수정 완료")
}
} catch {
print("데이터 수정 실패")
}
}
func deleteData(name: String) {
// 삭제할 데이터를 찾기 위한 fetch request 생성
let fetchRequest = PhoneBook.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "name == %@", name)
do {
// fetch request 실행
let result = try self.container.viewContext.fetch(fetchRequest)
// 결과 처리
for data in result as [NSManagedObject] {
// 삭제
// CRUD 의 D.
self.container.viewContext.delete(data)
print("삭제된 데이터: \(data)")
}
// 변경 사항 저장
try self.container.viewContext.save()
print("데이터 삭제 완료")
} catch {
print("데이터 삭제 실패: \(error)")
}
}
UserDefaults
UserDefaults
또한 디스크
에 데이터를 저장할 수 있게 돕는 도구.key
와 value
를 이용해서 값을 저장.CoreData
가, 비교적 단순한 데이터를 담는 데에는 UserDefaults
가 적절.UserDefaults.standard.set()
메서드를 통해서 Create
, Update
UserDefaults.standard.string(forKey: "")
메서드를 통해서 Read
(각 타입에 맞는 메서드사용)Read
: UserDefaults.standard.bool(forKey: "")
Read
: UserDefaults.standard.integer(forKey: "")
UserDefaults.standard.removeObject(forKey: "")
메서드를 통해서 Delete
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Create
UserDefaults.standard.set("010-1111-2222", forKey: "phoneNumber")
// Read
let phoneNumber = UserDefaults.standard.string(forKey: "phoneNumber")
print("저장된 전화번호: \(phoneNumber)")
// Update
// 같은 키에다가 set 을 하면 됨.
UserDefaults.standard.set("010-6666-7777", forKey: "phoneNumber")
let newPhoneNumber = UserDefaults.standard.string(forKey: "phoneNumber")
print("바뀐 전화번호: \(newPhoneNumber)")
// Delete
UserDefaults.standard.removeObject(forKey: "phoneNumber")
print("전화번호가 남아있는가: \(UserDefaults.standard.string(forKey: "phoneNumber"))")
}
}
포스트잇 앱 만들기
UserDefaults
는 디스크에 데이터를 저장하기 때문에, 포스트잇에 적은 텍스트를 UserDefaults 에 저장 해둔다면, 앱을 종료해도 데이터가 소멸되지 않습니다.import UIKit
import SnapKit
class ViewController: UIViewController {
private let label: UILabel = {
let label = UILabel()
label.text = "포스트잇"
label.font = .boldSystemFont(ofSize: 30)
label.textColor = .black
return label
}()
private let textView: UITextView = {
let textView = UITextView()
textView.text = UserDefaults.standard.string(forKey: "memo")
textView.layer.cornerRadius = 10
textView.backgroundColor = UIColor(red: 75/255, green: 253/255, blue: 30/355, alpha: 1.0)
textView.textColor = .black
textView.font = .boldSystemFont(ofSize: 30)
return textView
}()
private lazy var button: UIButton = {
let button = UIButton()
button.setTitle("적용", for: .normal)
button.backgroundColor = .red
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 20)
button.layer.cornerRadius = 10
button.addTarget(self, action: #selector(buttonTapped), for: .touchDown)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
private func configureUI() {
[
label,
textView,
button
].forEach { view.addSubview($0) }
view.backgroundColor = .white
label.snp.makeConstraints {
$0.top.equalToSuperview().offset(100)
$0.centerX.equalToSuperview()
}
textView.snp.makeConstraints {
$0.top.equalTo(label.snp.bottom).offset(100)
$0.centerX.equalToSuperview()
$0.height.width.equalTo(200)
}
button.snp.makeConstraints {
$0.top.equalTo(textView.snp.bottom).offset(50)
$0.width.equalTo(60)
$0.height.equalTo(40)
$0.centerX.equalToSuperview()
}
}
@objc
private func buttonTapped() {
UserDefaults.standard.set(textView.text, forKey: "memo")
print("저장 완료")
}
}
동글바리님의 코어데이터 튜토리얼 보고 코데마(코어데리터 마스터) 댓어요