apple/Docs, iOS, Swift
[iOS] CoreData 정리 2 실습 (2/2)
lgvv
2023. 12. 10. 22:19
[iOS] CoreData 정리 2 (실습) (2/2)
1편은 이론, 2편에서는 코드를 작성하면서 알아봅시다.
1편: https://rldd.tistory.com/586
CoreDataSampleCode
Core Data를 사용하기 위한 사전준비
- 프로젝트 생성시
이렇게 생성하면 AppDelegate와 SceneDelegate에 기본적으로 Core Data를 위한 코드가 추가됩니다.
먼저 SceneDelegate에서는 백그라운드로 넘어갔을때 현재 컨텍스트를 저장하고 있습니다.
해당 컨텍스트의 hasChanges 즉 변화가 있다면 save를 하고 있습니다.
먼저 persistentContainer 변수를 살펴봅시다.
NSPersistentContainer(name: ... )에 name안에는 내가 생성한 모델의 이름을 넣어줍니다.
해당 모델은 아래 사진의 파일 이름과 일치해야합니다.
Model파일에서 Memo 엔티티 중 title Attibute를 확인해봅시다.
우측 인스펙터를 확인해보면 Type 등을 설정할 수 있습니다.
실습 UI
좌측 첫번째 사진에서는 coreData를 가장 기본적으로 사용하는 방법에 대해서 알아봅니다.
우측 두번째 사진에서는 coreData 구조를 개선하고, SwiftUI에서도 사용할 수 있는 형태에 대해서 알아봅니다.
해당 형태로 CRUD 과정을 수행할 수 있습니다.
FirstViewController
import UIKit
import SwiftUI
import SnapKit
import RxSwift
import RxCocoa
import CoreData
final class FirstViewController: UIViewController {
var persistentContainer: NSPersistentContainer? {
(UIApplication.shared.delegate as? AppDelegate)?.persistentContainer
}
private var diposeBag = DisposeBag()
private func bind() {
createButton.rx.tap
.debug("createButton Tapped")
.bind(with: self) { this, _ in
this.create()
}.disposed(by: diposeBag)
readButton.rx.tap
.debug("readButton Tapped")
.bind(with: self) { this, _ in
this.read()
}.disposed(by: diposeBag)
updateButton.rx.tap
.debug("updateButton Tapped")
.bind(with: self) { this, _ in
this.update()
}.disposed(by: diposeBag)
deleteButton.rx.tap
.debug("deleteButton Tapped")
.bind(with: self) { this, _ in
this.delete()
}.disposed(by: diposeBag)
routeButton.rx.tap
.bind(with: self) { this, _ in
let vc = SecondViewController()
vc.memoDiskCache = MemoDiskCache.defaultValue
this.navigationController?.pushViewController(vc, animated: true)
}.disposed(by: diposeBag)
}
override func viewDidLoad() {
super.viewDidLoad()
configureLayout()
bind()
}
private func create() {
guard let context = persistentContainer?.viewContext else { return }
let memo = Memo(context: context)
memo.title = "제목"
memo.contents = "임시내용"
let date = Date()
memo.createAt = date
memo.updateAt = date
try? context.save()
}
private func read() {
guard let context = self.persistentContainer?.viewContext else { return }
let request = Memo.fetchRequest()
guard let memos = try? context.fetch(request) else { return }
print(memos)
}
private func update() {
guard let context = self.persistentContainer?.viewContext else { return }
let request = Memo.fetchRequest()
guard let memos = try? context.fetch(request) else { return }
let filteredMemos = memos.filter { $0.title == "제목" }
filteredMemos.forEach {
$0.title = "CARROT!"
$0.updateAt = Date()
}
try? context.save()
}
private func delete() {
guard let context = self.persistentContainer?.viewContext else { return }
let request = Memo.fetchRequest()
guard let memos = try? context.fetch(request) else { return }
memos.forEach { context.delete($0) }
let filteredMemos = memos
.filter { $0.title == "제목" }
filteredMemos.forEach { context.delete($0) }
try? context.save()
}
// MARK: - UIComponents
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 20, weight: .bold)
label.text = String(describing: Self.self)
return label
}()
private lazy var container: UIStackView = {
let stackView = UIStackView()
stackView.distribution = .fillProportionally
stackView.spacing = 3
return stackView
}()
private lazy var createButton: UIButton = {
let button = makeButton("create")
return button
}()
private lazy var readButton: UIButton = {
let button = makeButton("read")
return button
}()
private lazy var updateButton: UIButton = {
let button = makeButton("update")
return button
}()
private lazy var deleteButton: UIButton = {
let button = makeButton("delete")
return button
}()
private lazy var routeButton: UIButton = {
let button = makeButton("go to second viewController")
return button
}()
private func makeButton(_ title: String) -> UIButton {
let button = UIButton()
button.setTitle(title, for: .normal)
button.backgroundColor = .blue
button.layer.cornerRadius = 6
return button
}
// MARK: - Layout
private func configureLayout() {
view.addSubview(titleLabel)
titleLabel.snp.makeConstraints {
$0.bottom.equalToSuperview().dividedBy(4)
$0.centerX.equalToSuperview()
}
view.addSubview(container)
container.snp.makeConstraints {
$0.leading.trailing.equalToSuperview()
$0.center.equalToSuperview()
}
container.addArrangedSubview(createButton)
container.addArrangedSubview(readButton)
container.addArrangedSubview(updateButton)
container.addArrangedSubview(deleteButton)
view.addSubview(routeButton)
routeButton.snp.makeConstraints {
$0.top.equalTo(container.snp.bottom).offset(50)
$0.centerX.equalToSuperview()
}
}
}
SecondViewController
이번에는 EnvironmentKey, EnvironmentValue를 통해 @Environment형태로 사용 가능하도록 구현해 보았습니다.
TCA로 치면, @Dependency 닮아 있습니다.
import UIKit
import SwiftUI
import SnapKit
import RxSwift
import RxCocoa
import CoreData
final class SecondViewController: UIViewController {
private var diposeBag = DisposeBag()
var memoDiskCache: MemoDiskCache?
private func bind() {
createButton.rx.tap
.debug("createButton Tapped")
.bind(with: self) { this, _ in
this.memoDiskCache?.create("🎄 크리스마스", "")
}.disposed(by: diposeBag)
readButton.rx.tap
.debug("readButton Tapped")
.bind(with: self) { this, _ in
let memos = this.memoDiskCache?.read()
memos?.forEach {
print("\($0.title)")
}
}.disposed(by: diposeBag)
updateButton.rx.tap
.debug("updateButton Tapped")
.bind(with: self) { this, _ in
this.memoDiskCache?.update("🎄 크리스마스")
}.disposed(by: diposeBag)
deleteButton.rx.tap
.debug("deleteButton Tapped")
.bind(with: self) { this, _ in
this.memoDiskCache?.delete()
}.disposed(by: diposeBag)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
configureLayout()
bind()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - UIComponents
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 20, weight: .bold)
label.text = String(describing: Self.self)
return label
}()
private lazy var countLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 30, weight: .bold)
return label
}()
private lazy var container: UIStackView = {
let stackView = UIStackView()
stackView.distribution = .fillProportionally
stackView.spacing = 3
return stackView
}()
private lazy var createButton: UIButton = {
let button = makeButton("create")
return button
}()
private lazy var readButton: UIButton = {
let button = makeButton("read")
return button
}()
private lazy var updateButton: UIButton = {
let button = makeButton("update")
return button
}()
private lazy var deleteButton: UIButton = {
let button = makeButton("delete")
return button
}()
private func makeButton(_ title: String) -> UIButton {
let button = UIButton()
button.setTitle(title, for: .normal)
button.backgroundColor = .blue
button.layer.cornerRadius = 6
return button
}
// MARK: - Layout
private func configureLayout() {
view.backgroundColor = .systemBackground
view.addSubview(titleLabel)
titleLabel.snp.makeConstraints {
$0.bottom.equalToSuperview().dividedBy(4)
$0.centerX.equalToSuperview()
}
view.addSubview(countLabel)
countLabel.snp.makeConstraints {
$0.top.equalTo(titleLabel.snp.bottom).offset(10)
$0.centerX.equalToSuperview()
}
view.addSubview(container)
container.snp.makeConstraints {
$0.leading.trailing.equalToSuperview()
$0.center.equalToSuperview()
}
container.addArrangedSubview(createButton)
container.addArrangedSubview(readButton)
container.addArrangedSubview(updateButton)
container.addArrangedSubview(deleteButton)
}
}
import Combine
struct MemoDiskCache: EnvironmentKey {
var create: (_ title: String, _ contents: String) -> ()
var read: () -> [Memo]
var update: (_ title: String) -> ()
var delete: () -> ()
enum MemoDiskCacheError: Error {
case create
case read
case update
case delete
}
static let defaultValue: MemoDiskCache = .init { (title, contents) in
var persistentContainer: NSPersistentContainer? {
(UIApplication.shared.delegate as? AppDelegate)?.persistentContainer
}
guard let context = persistentContainer?.viewContext else { return }
var memoContext = Memo(context: context)
memoContext.title = title
memoContext.contents = contents
let date = Date()
memoContext.createAt = date
memoContext.updateAt = date
do {
try context.save()
} catch {
}
} read: {
var persistentContainer: NSPersistentContainer? {
(UIApplication.shared.delegate as? AppDelegate)?.persistentContainer
}
guard let context = persistentContainer?.viewContext else { return [] }
let request = Memo.fetchRequest()
guard let memos = try? context.fetch(request) else { return [] }
return memos
} update: { title in
var persistentContainer: NSPersistentContainer? {
(UIApplication.shared.delegate as? AppDelegate)?.persistentContainer
}
guard let context = persistentContainer?.viewContext else { return }
let request = Memo.fetchRequest()
guard let memos = try? context.fetch(request) else { return }
do {
let filteredMemos = memos.filter { $0.title == title }
filteredMemos.forEach {
$0.title = "🎁 산타의 선물"
$0.updateAt = Date()
}
try context.save()
} catch {
}
} delete: {
var persistentContainer: NSPersistentContainer? {
(UIApplication.shared.delegate as? AppDelegate)?.persistentContainer
}
guard let context = persistentContainer?.viewContext else { return }
let request = Memo.fetchRequest()
guard let memos = try? context.fetch(request) else { return }
let filteredMemos = memos
.filter { $0.title == "🎁 산타의 선물" }
filteredMemos.forEach { context.delete($0) }
try? context.save()
}
}
extension EnvironmentValues {
var memoDiskCache: MemoDiskCache {
get { self[MemoDiskCache.self] }
set { self[MemoDiskCache.self] = newValue }
}
}
액션에 따른 흐름도.