CoreData — хранить массив трансформируемых объектов

У меня есть объект с трансформируемым свойством. Это массив пользовательских объектов, Reminder, который подтверждает NSSecureCoding.

@objc(Reminder)
public class Reminder: NSObject, NSSecureCoding {
    public static var supportsSecureCoding: Bool = true
    
    
    public var date: Date
    public var isOn: Bool
    
    public  init(date: Date, isOn: Bool) {
        self.date = date
        self.isOn = isOn
    }
    
    struct Keys {
        static var date: String = "date"
        static let isOn: String = "isOn"
    }
    
    public func encode(with aCoder: NSCoder) {
        aCoder.encode(date as NSDate,forKey: Keys.date)
        aCoder.encode(isOn,forKey: Keys.isOn)
    }
    
    required public init?(coder aDecoder: NSCoder) {
        guard let date = aDecoder.decodeObject(of: NSDate.self, forKey: Keys.date) as Date? else {
            return nil
        }
        
        self.date = date
        self.isOn = aDecoder.decodeBool(forKey: Keys.isOn)
    }
}

И следующий код — это мой NSSecureUnarchiveFromDataTransformer.

class ReminderDataTransformer: NSSecureUnarchiveFromDataTransformer {

    override class func allowsReverseTransformation() -> Bool {
        return true
    }

    override class func transformedValueClass() -> AnyClass {
        return Reminder.self
    }

    override class var allowedTopLevelClasses: [AnyClass] {
        return [Reminder.self]
    }

    override func transformedValue(_ value: Any?) -> Any? {
        guard let data = value as? Data else {
            fatalError("Wrong data type: value must be a Data object; received \(type(of: value))")
        }
        return super.transformedValue(data)
    }

    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let reminder = value as? [Reminder] else {
            fatalError("Wrong data type: value must be a Reminder object; received \(type(of: value))")
        }
        return super.reverseTransformedValue(reminder)
    }
}



extension NSValueTransformerName {
    static let reminderToDataTransformer = NSValueTransformerName(rawValue: "ReminderToDataTransformer")
}

Я зарегистрировал ReminderDataTransformer, используя следующий код перед инициализацией NSPersistantContainer.

ValueTransformer.setValueTransformer(ReminderDataTransformer(), forName: .reminderToDataTransformer)

Я использовал ReminderToDataTransformer в качестве преобразователя в инспекторе модели данных xCode.


Но не работает из-за следующей ошибки, возникающей при сохранении сущностей.

 [error] error: SQLCore dispatchRequest: exception handling request: <NSSQLSaveChangesRequestContext: 0x282ec0780> , <shared NSSecureUnarchiveFromData transformer> threw while encoding a value. with userInfo of (null)
CoreData: error: SQLCore dispatchRequest: exception handling request: <NSSQLSaveChangesRequestContext: 0x282ec0780> , <shared NSSecureUnarchiveFromData transformer> threw while encoding a value. with userInfo of (null)
2020-12-31 21:44:09.300394+0100 ReminderApp[26406:6247995] [error] error: -executeRequest: encountered exception = <shared NSSecureUnarchiveFromData transformer> threw while encoding a value. with userInfo = (null)

Обновлять

Нет никаких исключений. При повторном запуске приложения эта ошибка логируется в консоли.

[error] fault: exception raised during multi-threaded fetch <shared NSSecureUnarchiveFromData transformer> threw while decoding a value. ({
    NSUnderlyingError = "Error Domain=NSCocoaErrorDomain Code=4864 \"value for key 'root' was of unexpected class 'NSArray (0x1fa392238) [/System/Library/Frameworks/CoreFoundation.framework]'. Allowed classes are '{(\n    \"Reminder (0x100fb6920) [/private/var/containers/Bundle/Application/306C3F0B-75AA-4A2D-A934-260B2EB63313/ReminderApp]\”\n)}’.\” UserInfo={NSDebugDescription=value for key 'root' was of unexpected class 'NSArray (0x1fa392238) [/System/Library/Frameworks/CoreFoundation.framework]'. 

Я предполагаю, что я не могу правильно кодировать/декодировать массив напоминаний, поскольку он работает, если я изменю код для хранения Reminder вместо [Reminder].

Просто для ясности, я могу сохранить Reminder, но не [Reminder].


Как сохранить [Reminder] как Transformable?



person mahan    schedule 31.12.2020    source источник
comment
Можете ли вы установить точку останова и посмотреть на данные при возникновении исключения?   -  person Rahul Iyer    schedule 03.01.2021
comment
@RahulIyer Я обновил свой вопрос. Прочитайте часть обновления.   -  person mahan    schedule 03.01.2021


Ответы (2)


Если вы собираетесь разархивировать массив, вам нужно добавить NSArray к классам верхнего уровня, вот как я понимаю сообщение об ошибке

override class var allowedTopLevelClasses: [AnyClass] {
    return [NSArray.self, Reminder.self]
}

Кстати, вместо трансформируемого подумайте о том, чтобы закодировать массив в JSON и сохранить его как простую строку. Преобразование может быть выполнено вычисляемым свойством.

Класс может быть структурой со значительно меньшим количеством кода.

public struct Reminder : Codable {
    public var date: Date
    public var isOn: Bool
}

В подклассе NSManagedObject создайте атрибут String

@NSManaged public var cdReminders : String

и вычисляемое свойство

var reminders : [Reminder] {
    get {
       return (try? JSONDecoder().decode([Reminder].self, from: Data(cdReminders.utf8))) ?? []
    }
    set {
       do {
           let reminderData = try JSONEncoder().encode(newValue)
           cdReminders = String(data: reminderData, encoding:.utf8)!
       } catch { cdReminders = "" }
    }
}
person vadian    schedule 03.01.2021
comment
Спасибо большое. - person mahan; 04.01.2021
comment
Спасибо! Это очень помогло. - person Tim Newton; 14.01.2021

Добавляя к ответу @vadian, если вы хотите включить разрешенные классы верхнего уровня из родительского преобразователя данных, попробуйте

override static var allowedTopLevelClasses: [AnyClass] {
    var allowed = super.allowedTopLevelClasses
    allowed(contentsOf: [Reminder.self])
    
    return allowed
}
person Tim Newton    schedule 13.01.2021