앱에서 데이터를 관리하는데 사용하는 프레임워크로 userDefault와 달리 좀 더 복잡한 데이터를 저장하는데 사용한다. 얼핏 DB라고 생각할 수 있으나 DB가 아니다.
CoreData는 앱이 설치된 해당 기기에서 저장된 데이터를 사용하므로 앱이 삭제되면 데이터도 삭제 되지만,
DB는 데이터를 관리하는 시스템으로 여러 사용자나 응용프로그램과 공유 및 동시 접근이 가능하다.
*raywenderlich의 Core Data 공부 내용을 바탕으로 작성되었습니다.*
- 기본적으로, 코어데이터는 SQLite database를 영구적인 저장소로 사용한다.
- Xcode에서 프로젝트를 생성할 때 'Use Core Data' 박스를 체크하면 AppDelegate.swift에서 `NSPersistentContainer` 코드를 생성한다.
NSManagedObject
: 코어데이터에 저장된 단일 객체를 나타내며, 데이터의 생성/수정/저장/삭제 하는데 사용된다.
NSManagedObjectModel
: 데이터 모델에 있는 각 객체 유형, 속성, 관계를 나타낸다.
NSPersistentStore
: 데이터를 읽고 쓰는데 사용된다.
NSPersistentStoreCoordinator
: NSManagedObjectModel과 NSPersistentStore를 연결해주는 역할을 한다.
NSPersistentStore 구성 방식에 대한 세부 정보를 숨기고 NSManagedObjectContext에 대한 간단한 인터페이스를 제공한다.
데이터모델을 이해하고 데이터를 주고받는 실제 DB 작업이 이루어진다.
NSManagedObjectContext
: 데이터 객체가 존재하는 영역으로 객체의 생명주기를 관리하는 역할을 한다.
fetch, 편집, 유효성 검사 등 보다 강력한 기능을 담당한다.
NSPersistentContainer
: 코어데이터에서 정보를 저장하고 가져오는 일을 수월하게 해주는 객체의 집합으로 이루어져 있다.
----- BASIC -----
사용
Person이라는 객체의 name 속성을 이용한다면,
1. xcdatamodeld파일에서 ENTITIES는 Person으로, Attributes에는 String 타입의 name을 추가해준다.
2. name에 데이터를 넣자.
새로운 데이터의 저장이 필요한 곳에 save()를 호출하여 managed object context에 저장한다.
// CoreData import 한다.
inport CoreData
class ViewController: UIViewController {
// 데이터를 가질 프로퍼티를 생성한다.
var people: [NSManagedObject] = []
// 들어오는 name 문자열을 저장할 함수
func save(name: String) {
// 1
// coredata를 사용하려면 먼저, NSManageObjectContext가 필요하다.
// 코어데이터에 데이터를 저장하려면, 새로운 객체를 'managed object context'에 넣어야 한다.
// (이 managed object context는 NSPersistentContainer 프로퍼티 안에 존재 한다)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
// 2
// 코어데이터의 엔티티를 연결
let entity = NSEntityDescription.entity(forEntityName: "Person", in: managedContext)!
let person = NSManagedObject(entity: entity,insertInto: managedContext)
// 3
// NSManagedObject를 연결하고 나면 KVC key를 정확히 넣어주어야 한다.
person.setValue(name, forKeyPath: "name")
// 4
// managed object context에 새로운 데이터를 저장
// 저장에 실패할 경우 error를 프린트한다.
do {
try managedContext.save()
people.append(person)
} catch let error as NSError {
print("error : \(error), \(error.userInfo)")
}
}
}
3. 저장되어 있는 데이터를 가져온다.
person의 name에 접근(이 필요한 위치에 작성)
// Person entities에 저장되어 있는 name 속성에 접근하여 각 인덱스 자리에 해당하는 값을 가져온다.
let person = poeple[인덱스넘버]
person.value(forKeyPath: "name") as? String
4. 저장된 데이터에 항상 접근되도록 fetch 해준다.
왜 viewWillAppear에서 작업하는지 이해가 되지 않는다면
view의 생명주기에 대해 공부해보면 된다.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//1
// manageObjectContext에 접근하기 위해 위와 동일하게 진행해준다
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
//2
// NSFetchRequest는 코어데이터에서 fetch 해주는 클래스
// init(entityName:)으로 초기화하면 특정 엔터티의 모든 개체를 가져옵니다.
// 여기에선 Person을 가져오기 위한 작업
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person")
//3
// 가져온 Person을 managed object context에 fetch해준다.
// fetch() 또한 error를 발생 시킬 수 있으니 do-catch문을 사용해준다.
do {
people = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
----- STARTER -----
BASIC에서 엔티티의 키값에 접근하기 위해 KVC방법을 이용했지만 이는 오타 발생시 에러의 위험성을 야기한다.
따라서, Person.name 처럼 프로퍼티로 접근하기 위한 방법을 알아본다.
먼저, 다양한 Type을 다루면서 방법을 알아보려 한다.
- 이미지
데이터의 타입을 이미지로 설정하고 싶다면 'Binary Data'를 사용한다.
Binary Data는 이지미 뿐만 아니라 PDF파일도 될 수있다. (0과 1로 시리얼라이즈 가능한 모든 것)
하지만, Binary Data타입은 엔티티에 액세스할 때마다 메모리에 로드되어 많은 양이 저장되면 앱 성능을 떨어 뜨린다. 때문에, Binary Data type의 속성에는 'Allows External Storage' 옵션을 활성화 해주어야 한다.
이 옵션을 활성화 하면, 코어데이터는 데이터를 데이터베이스에 직접 저장해야 하는지 별도의 URI를 저장해야 하는지 결정해준다.
- 색상
UIColor로 타입을 지정해도 되지만,
UIColor는 Data도 Binary Data타입으로도 시리얼라이즈 되지만,
UIColor <-> Data <-> Binary Data 와 같은 작업이 필요하다면 Transformable 타입이 적합하다.
다만, Transformable을 이용하기 위해서는 해줘야 하는 작업이 있다.
본론,
엔티티.프로퍼티 접근 ( color 기준 )
1. 엔티티 속성의 Codegen 값을 변경한다.
엔티티를 추가하고 컴파일을 한번 한 후에 작업 해주어야 한다.
Manual/None으로
2. Cocoa Touch template의 subclass of: NSSecureUnarchiveFromDataTransformer 파일을 새로 생성한다.
3. '엔티티명+CoreDataProperties.swift'가 생성이 된다.
4. 이 파일에 아래의 소스를 넣어준다.
import UIKit
class 클래스명: NSSecureUnarchiveFromDataTransformer {
//1
// 디코드 할 수 있는 클래스 목록을 리턴해준다.
// UIColor를 디코드 하기 위해 UIColor를 배열로 갖도록 추가해준다.
override static var allowedTopLevelClasses: [AnyClass] {
[UIColor.self]
}
//2
// ValueTransformer를 통해 이 클래스의 등록이 가능하다.
// ValueTransformer는 키-값을 매핑해주는 역할을 한다.
static func register() {
let className =
String(describing: 클래스명.self)
let name = NSValueTransformerName(className)
let transformer = 클래스명()
ValueTransformer.setValueTransformer(
transformer, forName: name)
}
}
5. 앱 실행 시 등록 작업이 이루어 지도록 AppDelegate의 didFinishLaunchingWithOptions 함수에 호출해준다.
func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
ColorAttributeTransformer.register()
return true
}
6. 그리고 이 클래스가 'color'에 적용 되도록 속성을 변경 해준다.
7. Xcode의 메뉴에서 Editor - Create NSManagedObject Subclass... - 엔티티 명이 체크되어 있는지 확인 후 Create!
8. '엔티티명+CoreDataClass.swift' 파일이 생성된다.
CoreData를 import 해준다.
import CoreData
이제 생성한 엔티티를 적용해본다.
데이터를 segmentControl을 사용하여 각 버튼별로 각 데이터가 보이도록 해본다.
1. ViewController.swift 파일에
import CoreData
// 전역변수 선언
var managedContext: NSManagedObjectContext!
2. 화면에 보여줄 데이터를 Property List 파일을 생성하여 작성해준다.
(2개를 보여준다는 가정하에 ->)
3. 코어데이터에 데이터를 넣을 수 있도록 별수 함수를 작성해준다.
func inserData() {
let fetch = Person.fetchRequest()
// NSPredicate(format: )은 코어데이터에서 데이터를 검색(fetch)할 때 사용하는 방식
fetch.redicate = NSPredicate(format: "searchKey != nil")
let dataCount = (try? managedContext.count(for: fetch)) ?? 0
if dataCount > 0 { return }
// forResource에는 아까 생성한 propertyList 파일명을 넣어준다.
let path = Bundel.main.path(forResource: "PersonData", ofType: "plist")
let dataArr = NSArray(contentsFile: path!)!
for item in dataArr {
let entity = NSEntityDescription.entity)forEntityName: "Person", in: managedContext)!
let perston = Person(entity: entity, inserInto: managedContext)
let dict = item as! [String:Any]
let color = dict["color"] as! [String:Any]
let imageName = dict["img"] as? String
let img = UIImage(named: imageName!)
person.name = dict["name"] as? String
person.searchKey = dict[searchKey"] as? String
person.color = UIColor.color(dict: color)
person.photo = img?.pngData()
}
try? managedContext.save()
}
4. UIColor에 대한 extenstion을 작성해준다.
private extension UIColor {
static func color(dict: [String: Any]) -> UIColor? {
guard
let red = dict["red"] as? NSNumber,
let green = dict["green"] as? NSNumber,
let blue = dict["blue"] as? NSNumber else {
return nil
}
return UIColor(
red: CGFloat(truncating: red) / 255.0,
green: CGFloat(truncating: green) / 255.0,
blue: CGFloat(truncating: blue) / 255.0,
alpha: 1)
}
}
5. 데이터 fetch 해오기
override func viewDidLoad() {
super.viewDidLoad()
// entity 접근 준비
let appDelegate = UIApplication.shared.delegate as? AppDelegate
managedContext = appDlegate?.persistentContainer.viewContext
// 데이터 넣어주기
inserData()
// searchKey를 통해 데이터 요청
let request = Person.fetchRequest()
let firstTitle = segmentedControl.titleForSegment(at:0) ?? ""
request.predicate = NSPredicate(format: "%K = %@", argumentArray: [#keyPath(Person.searchKey), firstTitle])
do {
let results = try managedContext.fetch(request)
if let person = results.first {
populate(person: person)
} catch let error as NSError {
print("fetch error: \(error),\(error.userInfo)")
}
}
func populate(person: Person) {
guard
let imageData = person.photo as Data?,
let color = person.color else { return }
이미지뷰.image = UIImage(data: imageData)
이름레이블.text = person.name
self.view.backgroundColor = color
}
6. 현재 눌려있는 버튼의 데이터를 가지고 있을 전역 변수를 선언한다.
var current: Person!
7. populate 호출부 위에 현재 데이터를 대입해준다.
do {
let results = try managedContext.fetch(request)
if let person = results.first {
current = person
populate(person: person)
} catch let error as NSError {
print("fetch error: \(error),\(error.userInfo)")
}
8. segmentedControl의 버튼이 눌리면 해당 버튼의 데이터가 보이도록 해준다.
@IBAction func segmentedControl(_ sender: UISegmentedControl) {
guard let selectedValue = sender.titleForSegment(
at: sender.selectedSegmentIndex) else {
return
}
let request = Person.fetchRequest()
request.predicate = NSPredicate(
format: "%K = %@",
argumentArray: [#keyPath(Person.searchKey), selectedValue])
do {
let results = try managedContext.fetch(request)
current = results.first
populate(person: current)
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
'iOS > iOS' 카테고리의 다른 글
설정앱의 특정 화면으로 이동?? (0) | 2023.01.06 |
---|---|
CustomView의 super (0) | 2023.01.04 |
UIViewController PopUp (0) | 2022.12.21 |
UITableView (0) | 2022.12.19 |
UIDatePicker (0) | 2022.12.08 |
댓글