article thumbnail image
Published 2023. 10. 25. 17:11

"Core Data"는 Database가 아니라 Object Graph Management이다. 

Core Data는 다양한 기능들을 제공하는데, 

제공하는 기능에는 Persistent, Change Tracking과 같은 기능들이 있다. 

 

즉, Persistent는 제공하는 기능 중 하나일 뿐이지, Core Data는 Database가 아니다

 

해당 블로그에서 사용하는 예시는 아래 링크에서 자세하기 볼 수 있다.

https://github.com/jungseokyoung-cloud/iOS-Study/tree/main/CoreData-Demo

 

 

Core Data Model

Core Data를 사용하기 위해선,

App의 Object구조를 정의하는 "Core Data Model File"을 생성해야 한다.

"Core Data Model File"은 App의 Object들을 Entity를 통해서 정의하고,

이러한 Entity에 Property, Relationship등을 정의하여 Object Graph를 정의한다.

 

"Core Data Model"은 새로운 프로젝트를 생성할 때, storage에서 Core Data를 선택하거나,

다음과 같이 "Data Model"을 추가해주면 된다.

 

그러면 ".xcdatamodeld" 확장자의 파일이 추가된다.

 

Data Modeling

App의 Object는 Entity로 표현된다. 

이후 Entity의 이름을 정의하고, Attribute들의 이름과 타입을 정의한다.

또한 Relationship을 통해 관계들을 정의할 수 있다.

 

CodeGan

이렇게 Entity와 Attribute를 정의하면 

Swift에선 Entitiy에 관한 파일과 Attribute에 대한 파일을 생성해 준다. 

 

Entity는 NSMangedObject의 서브클래스로 구현된다. 

import Foundation
import CoreData

@objc(Photo)
public class Photo: NSManagedObject {

}
import Foundation
import CoreData


extension Photo {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Photo> {
        return NSFetchRequest<Photo>(entityName: "Photo")
    }

    @NSManaged public var author: String?
    @NSManaged public var id: Int64
    @NSManaged public var image: Data?

}

extension Photo : Identifiable {

}

Swift에선 이 2개의 소스파일을 관리하는데 있어서 3가지 옵션을 제공한다.

 

Class Definition 

디폴트로 선택되어 있는 옵션이다. 

Class Definition은 Swift가 두 소스파일을 알아서 관리해 주는 옵션으로, 

소스파일에 대해 직접적인 수정이 필요하지 않는 경우 사용한다.

 

해당 옵션을 선택하게 되면 Xcode의 Source File List에 보이지 않는데, 

이는 Xcode가 이 두 파일을 프로젝트에 build 디렉토리에 생성하기 때문이다.

 

Category/Extension

해당 옵션은 Property File만 Swift가 알아서 관리하는 방식이다. 

즉, Entity Class 파일은 유저가 관리할 수 있게 된다. 

 

우선적으로 Entity Class파일을 생성할 필요가 있는데, 

이를 위해서 Xcode 메뉴바에서 "Editor" > "Create NSManagedObject Subclass" 를 클릭해 주면 된다.

 

생성하게 되면, 2개의 파일이 생성되는데, 이중 CoreDataProperties파일을 지워주면 된다.

 

Manual/None

마지막으로 해당 옵션은 Entitiy과 Attribute관련 파일을 모두 직접 수정할 수 있다.

해당 옵션은 위와 마찬가지로 Xcode 메뉴바에서 "Editor" > "Create NSManagedObject Subclass" 를 클릭해 주면 된다.

 

 

Setup

이제 본격적으로 CoreData의 기능을 사용해보자! 

 

이전 포스팅에서 말했지만, Core Data의 기능을 사용하기 앞서 Core Data Stack을 생성해야 한다.

Core Data Stack은 다음과 같이 구성된다. 

  • NSManagedObjectModel
  • NSManagedObjectContext
  • NSPersistentStoreCoordinator

기존에는 이들을 직접 생성해주어야 했지만, 

iOS10이후로는 NSPersistentContainer의 등장으로

Core Data Stack의 생성 및 관리를 단순화해 준다. 

 

PersistentContainer

우선 PersistentManager라는 Class를 생성하고, 

final class PersistentManager {
    
   	 ...
    
}

내부에 PersistentContainer를 생성해주자.

final class PersistenceManager {
	lazy var persistentContainer: NSPersistentContainer = {
		let container = NSPersistentContainer(name: "Model")
		
		container.loadPersistentStores { storeDesciption, error in
			if let error = error as NSError? {
				fatalError("Unresolved error \(error), \(error.userInfo)")
			}
		}
		
		return container
	}()
	
	...
	
}

우선적으로 생성 시, 다음과 같이 Data Model 파일의 이름을 적어주면 된다.

let container = NSPersistentContainer(name: "Model")

 

이후, 해당 코드를 통해 저장소를 불러온다.

container.loadPersistentStores { storeDesciption, error in
	if let error = error as NSError? {
		fatalError("Unresolved error \(error), \(error.userInfo)")
	}
}

 

이전 포스팅에서 말했듯이, Swift에선 다양한 타입의 Storage를 제공하는데

디폴트는 SQLite이다. 

 

만약 다른 타입의 저장소를 원한다면 아래와 같이 진행하면 된다. 

final class PersistenceManager {
	enum StorageType {
		case sqlite
		case binary
		case inMemory
	}
	
	let storageType: StorageType = .sqlite
	
	lazy var persistentContainer: NSPersistentContainer = {
		let container = NSPersistentContainer(name: "Model")
		
		if self.storageType != .sqlite {
			let description = NSPersistentStoreDescription()
			description.type = convertNSStoreType()
			container.persistentStoreDescriptions = [description]
		}
		
		container.loadPersistentStores { storeDesciption, error in
			if let error = error as NSError? {
				fatalError("Unresolved error \(error), \(error.userInfo)")
			}
		}
		
		return container
	}()
	
	func convertNSStoreType() -> String {
		switch storageType {
		case .sqlite:
			return NSSQLiteStoreType
		case .binary:
			return NSBinaryStoreType
		case .inMemory:
			return NSInMemoryStoreType
		}
	}
	...
}

 

다음으로 

Github의 원격 저장소 이전 로컬저장소와 같은 역할을 담당하는

NSManagedObjectContext 역시 자주 쓰이기 때문에 다음과 같이 선언해 주자.

var context: NSManagedObjectContext {
	return self.persistentContainer.viewContext
}

 

 

CRUD

Create

우선 생성을 위한 Entity를 생성해야 한다. 

Entitiy를 생성할 때 NSManagedObjectContext를 파라미터로 전달해야 한다.

let photo = Photo(context: context)

이는 이전 포스팅에서 말했듯, Context는

Git의 원격 저장소 이전에 로컬 저장소와 같은 역할을 담당한다.

즉, PersistentStorage에 저장하기 전 Context영역을 한번 거치게 된다.

 

하지만, 이 NSManagedObject(Entity)는 외부에선 context를 모르기 때문에, 
중간 단계의 Entity를 생성해 주자. 

struct ImageInfo {
	let id: Int
	let author: String
	let image: UIImage
}
photo.id = Int64(info.id)
photo.author = info.author
photo.image = info.image.pngData()

위와 같이 photo에 값을 저장해 주고, 

context.save()를 하게 되면 context에 있던 내용이 PersistentManager로 반영이 된다.

context.save()

 

전체 코드는 다음과 같다. 

@discardableResult
func createPhoto(_ info: ImageInfo) -> Bool {
	let photo = Photo(context: context)
	
	photo.id = Int64(info.id)
	photo.author = info.author
	photo.image = info.image.pngData()
	
	do {
		try self.context.save()
		return true
	} catch {
		print(error.localizedDescription)
		return false
	}
}

 

Read

Read를 하기 위해선 NSFetchReqeust에 entity의 이름을 전달해 주어 Request를 생성해야 한다.

func read() -> [NSManagedObject]? {
	let readRequest = NSFetchRequest<NSManagedObject>(entityName: "Photo")
	
	return try? context.fetch(readRequest)
}

이후, context를 통해 해당 Request를 수행한다.

 

만약 특정 NSManagedObject가 필요하다면 다음과 같이 수행해 주면 된다.

func read(at id: Int) -> NSManagedObject? {
	guard let imageInfoData = read() else { return nil }
	
	return imageInfoData
		.filter { ($0.value(forKey: "id") as? Int) == id }
		.first
}

하지만 리턴되는 타입이 NSManagedObject타입이기 때문에, 

필요하다면 다음과 같은 전환 과정이 필요하다.

func convertToImageInfo(from managedObject: NSManagedObject) -> ImageInfo? {
	guard
		let id = managedObject.value(forKey: "id") as? Int,
		let author = managedObject.value(forKey: "author") as? String,
		let imageData = managedObject.value(forKey: "image") as? Data,
		let image = UIImage(data: imageData)
	else {
		return nil
	}
	return ImageInfo(id: id, author: author, image: image)
}

 

Update

업데이트는 변경할 NSManagedObject를 가져와, 

func updateCoreData(from id: Int, to info: ImageInfo) {
	guard
		let managedObject = read(at: id),
		let imageData = info.image.pngData()
	else { return }

 프로퍼티의 값을 변경해 주고,

func updateCoreData(from id: Int, to info: ImageInfo) {
	
	...
	
	managedObject.setValue(info.id, forKey: "id")
	managedObject.setValue(info.author, forKey: "author")
	managedObject.setValue(imageData, forKey: "image")
	
	...
}

context.save()를 통해 Persistent Storage에 반영해 주면 된다.

context.save()

 

Delete

Delete는 삭제할 NSManagedObject를 가져와,

func delete(for id: Int) {
	guard
		let managedObject = read(at: id)
	else { return }
	
	...
}

context에서 해당 NSManagedObject를 삭제해 주고, 

context.delete(managedObject)

이를 다시 PersistentStorage에 반영해 주면 된다.

context.save()

 

 

Reference

Apple Documentation

'iOS > iOS' 카테고리의 다른 글

[iOS] UIResponder(1)  (0) 2023.11.10
[iOS] Custom Drop Down  (1) 2023.10.31
[iOS] Local DB(2) - Core Data Concept  (0) 2023.10.09
[iOS] Local DB(1) - UserDefaults, Keychain  (0) 2023.10.04
[iOS] Infinite Carousel  (0) 2023.07.23
복사했습니다!