Handling Different Data Types in Core Data

betterhee 2023. 9. 4. 13:06

Transient Attribute

Transient Attribute(임시 속성)을 사용하여 비지속적 값 파생

  • 동일한 엔티티에 저장된 하나 이상의 속성에서 파생됨
  • 이름에서 알 수 있듯이 저장소에 유지되지 않
  • 저장 공간을 아낄 수 있다

예제 코드

// publishMonthID는 publishDate로부터 파생된 임시 속성

@objc public var publishDate: Date? {
    get {
        willAccessValue(forKey: Name.publishDate)
        defer { didAccessValue(forKey: Name.publishDate) }
        return primitivePublishDate
    }
    set {
        willChangeValue(forKey: Name.publishDate)
        defer { didChangeValue(forKey: Name.publishDate) }
        primitivePublishDate = newValue
        primitivePublishMonthID = nil // publishDate의 setter 메서드는 primitivePublishMonthID를 무효화합니다. 이를 통해 publishMonthID의 getter 메서드가 현재 publishDate를 기반으로 값을 다시 계산할 수 있습니다.
    }
}

@objc public var publishMonthID: String? {
    willAccessValue(forKey: Name.publishMonthID)
    defer { didAccessValue(forKey: Name.publishMonthID) }

    // publishMonthID의 getter 메서드가 현재 publishDate를 기반으로 값을 다시 계산
    guard primitivePublishMonthID == nil, let date = primitivePublishDate else {
        return primitivePublishMonthID
    }
    let calendar = Calendar(identifier: .gregorian)
    let components = calendar.dateComponents([.year, .month], from: date)
    if let year = components.year, let month = components.month {
        primitivePublishMonthID = "\(year * 1000 + month)"
    }
    return primitivePublishMonthID
}

// publishMonthID가 publishDate와 연결되고 항상 최신 상태를 유지할 수 있다.
// PublishMonthID가 Swift에서 Key-Value Observing을 사용하는 경우, 
// 다음 코드는 publishDate가 변경될 때 observation이 트리거되도록 보장
class func keyPathsForValuesAffectingPublishMonthID() -> Set<String> {
    return [Name.publishDate]
}

Derived Attribute

Derived Attribute(파생 속성)을 사용하여 다른 값에서 하나의 값 파생

  • 저장 공간보다 성능이 더 중요한 경우 사용
  • 사용자가 managed context를 저장할 때만 업데이트 됨

예제 코드

  • canonical: 함수는 대소문자와 발음 구별 부호를 구분하지 않는 문자열 값을 반환
  • 파생 속성은 사용자가 managed context를 저장할 때만 업데이트 되므로, title 속성을 저장하지 않고 변경하는 경우 canonicalTitle은 변경되지 않음

Predicate Programming Guide - String Comparisons
NSDerivedAttributeDescription | Apple Developer Documentation

extension ViewController: UISearchResultsUpdating {

    func updateSearchResults(for searchController: UISearchController) {
        let predicate: NSPredicate
        if let userInput = searchController.searchBar.text, !userInput.isEmpty {

            // Searching title with "diacritic insensitive" option gets the same result:
            //     predicate = NSPredicate(format: "title CONTAINS[cd] %@", userInput)
            // However, searching canonicalTitle avoids doing diacritic insensitive comparison every time,
            // and hence has better performance.
            //
            predicate = NSPredicate(format: "canonicalTitle CONTAINS[c] %@", userInput)
        } else {
            predicate = NSPredicate(value: true)
        }

        fetchedResultsController.fetchRequest.predicate = predicate
        do {
            try fetchedResultsController.performFetch()
        } catch {
            fatalError("###\(#function): Failed to performFetch: \(error)")
        }

        tableView.reloadData()
    }
}

Transformable Attribute

Transformable Attribute(변환 가능한 속성)을 사용하여 비표준 유형의 객체를 저장할 수 있음

예제 코드

  1. 타입을 Transformable로 설정하고, Data Model Inspector에서 Transformer 및 Custom Class 이름을 지정하여 구성
  2. 앱이 Core Data Stack을 로드하기 전에 코드로 Transformer을 등록
lazy var persistentContainer: NSPersistentContainer = {
     // Register the transformer at the very beginning.
     // .colorToDataTransformer is a name defined with an NSValueTransformerName extension.
     ValueTransformer.setValueTransformer(ColorToDataTransformer(), forName: .colorToDataTransformer)

     let container = NSPersistentContainer(name: "CoreDataAttributes")
     container.loadPersistentStores(completionHandler: { (_, error) in
         guard let error = error as NSError? else { return }
         fatalError("###\(#function): Failed to load persistent stores:\(error)")
     })

     container.viewContext.automaticallyMergesChangesFromParent = true
     SampleData.generateSampleDataIfNeeded(context: container.newBackgroundContext())

     return container
}()

Date Type

Decimal Type

let value = UInt64(arc4random_uniform(9999))
book.price = NSDecimalNumber(mantissa: value, exponent: -2, isNegative: false)

cell.price.text = book.price?.description(withLocale: Locale.current) // ex) 98.14