Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
SwiftUI/SwiftUI(Intermediate)

3. CoreData Relationship/ 관계형 데이터 모델

by Toughie 2023. 5. 22.

⭐️CoreData Relationship⭐️

Swift의 CoreData는 관계형 데이터 모델을 구성할 수 있는 기능이 있다.

관계형 데이터 모델에서 엔티티들의 관계를 나타내기 위해 'Relationship'을 사용한다.

 

Relationshipdms 엔티티 간의 연결을 나타내는 속성으로, 1 : 1 /  1 : N / N : N 관계를 설정할 수 있다.

 

Xcode의 모델 파일에서 inspector를 보면 Relationship type을 설정할 수 있다. (단일 관계 / 다중 관계)

Xcode14 이전까지는 여러 엔티티들의 Relationship을 도식화 해서 보여주는 기능이 있었는데

무슨 이유인지 갑자기 사라졌다..;;

학습 예시로 사용했던 엔티티의 관계도는 아래와 같다.

각 엔티티의 attribute가 어떤 것들이 있는지, 어떤 엔티티와 관계를 가지고 있는지 한 눈에 보인다.

또한 단방향/양방향도 화살표를 통해 알 수 있다. (위 사진에서 Inverse 옵션)

대체 왜 없앤거야 애플..?

학습을 하면서 느꼈던 점은 그냥 쿼리를 하는 느낌이었다.

데이터테이블을 관리하고 조인을 해서 원하는 데이터를 뽑아내는 그런 느낌.. 사실 근데 이 느낌이 맞다 ㅋㅋ

 

먼저 여기서 다룰 엔티티는 회사 / 부서 / 직원 총 세 개이다.

 

 

회사(BusinessEntity)

회사는 부서와 양방향 관계를 가지고 있고 회사 안에는 여러 부서가 있다.(To Many 타입)

회사는 직원과도 양방향 관계를 가지고 있고, 여러 직원이 있다.(To Many 타입)

-> 회사 안에는 여러 부서가 있고, 여러 직원이 있다. 당연한 말이다.

 

부서(DepartmentEntity)

부서는 회사와 양방향 관계를 가지고 있고, 다양한 회사에 속할 수 있다.

->즉 개발부서가 있으면 이 회사 저 회사에 파견갈 수 있다는 뜻.(예시를 위한 예시다)

부서는 직원과 양방향 관계를 가지고 있고 여러 직원이 속할 수 있다. (To Many 타입)

 

직원(EmployeeEntity)

직원은 부서/회사와 양방향 관계를 가지고 있고 한 곳에만 속할 수 있다.(To One 타입)
*여러 부서/ 여러 회사에 발 걸치는 것이 안된다는 의미.

 

예시 뷰

차례대로 Business Entity, DepartmentEntity, EmployeeEntity를 생성하고 있다.

처음에는 Business name만 나오다가 점차 데이터들이 Department, Employee와 연결되는 것을 볼 수 있다.

 

여기서는 반대로 Employee Entity를 만들고 새로운 Department Entity를 만들고, 새로운 Business Entity를 만들고 연결하고 있다.

 

싱글톤 패턴을 활용한 코어데이터 매니저 예시 코드

* 싱글톤: 앱 전체에서 단 하나의 인스턴스만 생성하는 클래스를 구현하는 방법.

해당 인스턴스는 전역적으로 접근 가능하며, 여러 곳에서 공유해서 사용할 수 있다.

 

다만 전역 상태 관리가 필요하기 때문에 의존성/결합성 문제가 있을 수 있고 라이프 사이클 내내 유지되기 때문에 메모리 누수 가능성도 있다.또한 멀티 스레드 환경에서 경합 문제가 발생할 수도 있다. 장점도 있지만 단점도 명확하기에 적절하게 사용할 필요가 있다 :)

 

final class CoreDataManager {
    //singleton
    static let shared = CoreDataManager()
    
    let container: NSPersistentContainer
    let context: NSManagedObjectContext
    
    private init() {
        container = NSPersistentContainer(name: "CoreDataContainer")
        container.loadPersistentStores { description, error in
            if let error = error {
                print("Error loading Core data: \(error)")
            }
        }
        context = container.viewContext
    }
    
    func save() {
        do {
            try context.save()
            print("Saved successfully")
        } catch let error {
            print("Error saving Core Data: \(error)")
        }
    }
}

 

뷰모델 코드 예시

class CoreDataRelationshipViewModel: ObservableObject {
	//싱글톤 객체 
    let manager = CoreDataManager.shared
    
    //엔티티 배열로 관리
    @Published var businesses: [BusinessEntity] = []
    @Published var departments: [DepartmentEntity] = []
    @Published var employees: [EmployeeEntity] = []
    
    //READ fetch 
    init() {
        getBusinesses()
        getDepartments()
        getEmployees()
    }
    
    //엔티티 추가
    func addBusiness() {
        let newBusiness = BusinessEntity(context: manager.context)
        newBusiness.name = "Apple"
        
        //기존 부서를 새로운 회사에 추가하는 경우 
		//배열 인덱싱으로 직접 추가도 가능하고, .add~ 메서드 활용도 가능.
        // add existing departments to the new business
//        newBusiness.departments = []
        
        // add existing employees to the new business
        // newBusiness.employees = []
        
        // add new business to existing department
//        newBusiness.addToDepartments(<#T##value: DepartmentEntity##DepartmentEntity#>)
        
        // add new business to existing employy
//        newBusiness.addToEmployees(<#T##value: EmployeeEntity##EmployeeEntity#>)
        
        //저장 
        save()
    }
    
    //READ. fetch 코드
    func getBusinesses() {
        let request = NSFetchRequest<BusinessEntity>(entityName: "BusinessEntity")
        
        //정렬이 필요한 경우
        let sort = NSSortDescriptor(keyPath: \BusinessEntity.name, ascending: true)
        request.sortDescriptors = [sort]
        
        //필터링이 필요한 경우
        //filter
        // NSPredicate 클래스는 데이터 필터링 및 쿼리 수행을 위한 Cocoa 프레임워크의 일부임
        //아래 조건식은 name이라는 속성이 "Apple"과 일치한다는 조건
//        let filter = NSPredicate(format: "name == %@", "Apple")
//        request.predicate = filter
        
        do {
            businesses = try manager.context.fetch(request)
        } catch let error {
            print("Error fetching: \(error.localizedDescription)")
        }
    }
    
    ...
    
 
        func save() {
        businesses.removeAll()
        departments.removeAll()
        employees.removeAll()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.manager.save()
            
            self.getBusinesses()
            self.getDepartments()
            self.getEmployees()
        }
    }
}

아마 실제 비즈니스앱에서 코어데이터를 사용한다면 엔티티, 어트리뷰트도 어마하게 많고 복잡할 것 같다..
엔티티간 relationship을 잘 설정하고 관리하는 것도 꽤 어려운 작업이 될 것 같다.🤔