Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- Clean Code
- 리펙터링
- swiftUI
- 리팩토링
- HIG
- 리펙토링
- uiscrollview
- 클린 코드
- Refactoring
- SWIFT
- MVVM
- UITextView
- rxswift
- ribs
- combine
- Xcode
- ios
- 스위프트
- UICollectionView
- swift documentation
- Protocol
- RxCocoa
- Human interface guide
- uitableview
- collectionview
- 애니메이션
- tableView
- clean architecture
- map
- Observable
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] CoreData 사용 (protocol 이용, in-memory 개념) 본문
기본개념은 여기 참고
DataModel추가
- 새로 만들기에서 Data Model추가
- 완성된 화면
- "Add Entity"눌러서 엔터티 추가: 중요한 것은 Data형태의 updateDate를 추가
(이 데이터는 나중에 addData할 때 같은 데이터가 있으면 date만 업데이트하는 용도로 사용 될 것)
- Entity이름 수정
- Codegen을 Menual/None으로 변경: Class Definition으로 두면 오류 발생
"Mutiple commands produce '...' "
- 생성된 .xcdatamodeld 클릭 > Xcode의 menu바에서 Editor -> "Create NSMagedObject Subclass"선택하여 클래스 생성
- 두 가지 파일 생성 완료: Person+CoreDataClass, Person+CoreDataProperties
데이터 Layer
- Domain은 "-Store"라는 이름
- 구현체는 "-Repotory"라는 이름 사용하여, 사용할 경우 형태는 Store지만 주입하는 객체는 "-Repository"를 주입하여 사용
데이터가 저장될 자료구조 CoreDataStack구현 (싱글턴)
- 가장 핵심은, Container와 Context개념
- Container란 데이터 공간을 참조
- Context란 데이터 공간에서 CRUD하는 객체
// Domain
import Foundation
import CoreData
enum StoreType {
case persistent, inMemory
func NSStoreType() -> String {
switch self {
case .persistent:
return NSSQLiteStoreType
case .inMemory:
return NSInMemoryStoreType
}
}
}
class CoreDataStack {
static let shared = CoreDataStack(storeType: .persistent)
let storeType: StoreType
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Person")
if self.storeType == .inMemory {
let description = NSPersistentStoreDescription()
description.type = self.storeType.NSStoreType()
container.persistentStoreDescriptions = [description]
}
container.loadPersistentStores { (_, error) in
if let error = error as NSError? {
fatalError("Unable to load core data persistent stores: \(error)")
}
}
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy // in-memory와 영구 저장소 merge 충돌: in-memory우선
container.viewContext.shouldDeleteInaccessibleFaults = true // 접근 불가의 결함들을 삭제할 수 있게끔 설정
container.viewContext.automaticallyMergesChangesFromParent = true // parent의 context가 바뀌면 자동으로 merge되는 설정
return container
}()
var viewContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
init(storeType: StoreType) {
self.storeType = storeType
}
fileprivate func setBackgroundContext(_ context: NSManagedObjectContext) {
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy // in-memory와 영구 저장소 merge 충돌: in-memory우선
context.undoManager = nil // nil인 경우, 실행 취소를 비활성화 (iOS에 디폴트값은 nil, macOS에서는 기본적으로 제공)
}
func taskContext() -> NSManagedObjectContext {
let taskContext = persistentContainer.newBackgroundContext()
setBackgroundContext(taskContext)
return taskContext
}
func performBackgroundTask(task: @escaping (NSManagedObjectContext) -> Void) {
persistentContainer.performBackgroundTask { (context) in
self.setBackgroundContext(context)
task(context)
}
}
}
"-Store"정의
// Domain
import CoreData
public protocol PersonModel {
var id: String { get }
var name: String { get }
}
public protocol PersonStore {
func add(id: String, name: String)
func remove(id: String, name: String)
func removeAll()
func count() -> Int?
func removeLast()
}
"-Repository"정의
import CoreData
class PersonModel: PersonModelStore {
var id: String = ""
var name: String = ""
init(id: String, name: String) {
self.id = id
self.name = name
}
}
class PersonRepository: PersonStore {
let coreDataStack: CoreDataStack
let maxCount: Int
init(coreDataStack: CoreDataStack = CoreDataStack.shared, maxCount: Int = 10) {
self.coreDataStack = coreDataStack
self.maxCount = maxCount
}
func add(id: String, name: String) {
let context = coreDataStack.taskContext()
if let count = count(), count == maxCount {
removeLast()
}
if let savedPlace = fetch(id, name, in: context) {
savedPlace.updateDate = Date()
} else {
create(id, name, in: context)
}
context.performAndWait {
do {
try context.save()
} catch {
print("addPlace error: \(error)")
}
}
}
func remove(id: String, name: String) {
let context = coreDataStack.taskContext()
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "(id.length > 0 AND id == %@) OR (name == %@)", argumentArray: [id, name])
do {
let objects = try context.fetch(fetchRequest)
for object in objects {
context.delete(object)
}
try context.save()
} catch _ {
// error handling
}
}
fileprivate func fetch(_ id: String, _ name: String, in context: NSManagedObjectContext) -> Person? {
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "(id.length > 0 AND id == %@) OR (name == %@)", argumentArray: [id, name])
do {
return try context.fetch(fetchRequest).first
} catch {
print("fetch for update Person error: \(error)")
return nil
}
}
fileprivate func create(_ id: String, _ name: String, in context: NSManagedObjectContext) {
let place = Person(context: context)
place.id = id
place.name = name
place.updateDate = Date()
}
func removeAll() {
let context = coreDataStack.taskContext()
let deleteRequest = NSBatchDeleteRequest(fetchRequest: Person.fetchRequest())
do {
try context.execute(deleteRequest)
try context.save()
} catch {
print("removeAll Person error: \(error)")
}
}
func getPersons() -> [PersonModel] {
return fetchAll().map {
return PersonModel(id: $0.id ?? "1", name: $0.name ?? "2")
}
}
fileprivate func fetchAll() -> [Person] {
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "updateDate", ascending: false)]
do {
return try coreDataStack.viewContext.fetch(fetchRequest)
} catch {
print("fetch Person error: \(error)")
return []
}
}
func count() -> Int? {
let context = coreDataStack.taskContext()
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
do {
let count = try context.count(for: fetchRequest)
return count
} catch {
print("count of Person error: \(error)")
return nil
}
}
func removeLast() {
guard let removeTarget = fetchAll().last,
let id = removeTarget.id,
let name = removeTarget.name else {
return
}
remove(
id: id,
name: name
)
}
}
사용하는 쪽 예시)
- Add를 누르면 코어 데이터 안에 데이터의 갯수를 불러와서 (count, count)로 데이터 만든 후 삽입
- Delete를 누르면 코어 데이터 안에 removeLast하여 마지막 요소 삭제
- 사용하는 쪽의 주요 코드 샘플
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var dataSource = [PersonModel]()
let personUseCase = PersonRepository() // 실제로 사용할 땐 ViewModel에서 주입하는 형태로 사용
@IBOutlet weak var tbl: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tbl.delegate = self
tbl.dataSource = self
tbl.register(UITableViewCell.self, forCellReuseIdentifier: "TableViewCell")
}
@IBAction func btnDelete(_ sender: Any) {
personUseCase.removeLast()
dataSource = personUseCase.getPersons()
tbl.reloadData()
}
@IBAction func btnAdd(_ sender: Any) {
let count = String(describing: personUseCase.count() ?? 0)
personUseCase.add(id: count, name: count)
dataSource = personUseCase.getPersons()
tbl.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell")! as UITableViewCell
cell.textLabel?.text = dataSource[indexPath.row].id + ", " + dataSource[indexPath.row].name
return cell
}
}
'iOS 응용 (swift)' 카테고리의 다른 글
[iOS - swift] 버튼에 loading 넣기 (loader), MaterialComponents (0) | 2020.11.26 |
---|---|
[iOS - swift] View를 UIImage로 변환(캡쳐하기), 이미지 공유하기 기능 (카톡, 메세지, 메모 등) (0) | 2020.11.23 |
[iOS - swift] Firestore 연동, 사용 방법 (0) | 2020.11.21 |
[iOS - swift] Firebase/Database연동, Firebase 데이터베이스 (0) | 2020.11.21 |
[iOS - swift] Firebase 연동 방법, FirebaseSDK, GoogleService-info.plist (0) | 2020.11.21 |
Comments