Нестандартные персистентные атрибуты

Базовая Информационная поддержка диапазон общих типов для значений персистентных атрибутов, включая строку, дату и число. Иногда, однако, Вы хотите, чтобы значение атрибута было типом, не поддерживающимся непосредственно. Например, в графическом приложении Вы могли бы хотеть определить Прямоугольный объект, имеющий атрибуты color и bounds это - экземпляр NSColor и NSRect структура соответственно. Эта статья описывает эти два пути, которыми можно использовать нестандартные типы атрибута: использование поддающихся преобразованию атрибутов, или при помощи переходного свойства для представления нестандартного атрибута, поддержанного поддерживаемым персистентным свойством.

Введение

Персистентные атрибуты должны иметь тип, распознанный Базовой платформой Данных так, чтобы они могли быть должным образом сохранены к и получены от персистентного хранилища. Базовые Данные предоставляют поддержку для диапазона общих типов для персистентных значений атрибута, включая строку, дату и число (см. NSAttributeDescription для полного изложения). Иногда, однако, Вы хотите использовать типы, не поддерживающиеся непосредственно, такие как цвета и структуры C.

Можно использовать нестандартные типы для персистентных атрибутов или при помощи поддающихся преобразованию атрибутов или при помощи переходного свойства для представления нестандартного атрибута, поддержанного поддерживаемым персистентным свойством. Принцип позади двух подходов является тем же: Вы представляете потребителям Вашего объекта атрибут типа, который Вы хотите, и «негласно» это преобразовывается в тип, которым могут управлять Базовые Данные. Различие между подходами - то, что с поддающимися преобразованию атрибутами Вы указываете всего один атрибут, и преобразование обрабатывается автоматически. Напротив, с переходными свойствами Вы указываете два атрибута, и необходимо записать код для выполнения преобразования.

Поддающиеся преобразованию атрибуты

Идея позади поддающихся преобразованию атрибутов состоит в том, что Вы получаете доступ к атрибуту как к нестандартному типу, но негласно Базовые Данные используют экземпляр NSValueTransformer преобразовать атрибут в и от экземпляра NSData. Базовые Данные тогда хранят экземпляр данных к персистентному хранилищу.

По умолчанию Базовые Данные используют NSKeyedUnarchiveFromDataTransformerName если Вы хотите, преобразователь, однако можно указать собственный преобразователь. При указании пользовательского преобразователя он должен преобразовать экземпляр нестандартного типа данных в экземпляр NSData и поддержка инвертирует трансформацию. Вы не должны указывать имя при использовании преобразователя по умолчанию.

Вы указываете, что атрибут является поддающимся преобразованию и имя преобразователя для использования в образцовом редакторе в XCode или программно:

В принципе Вы не должны делать ничего больше. На практике для подавления предупреждений компилятора необходимо объявить свойство для атрибута как показано в следующем примере (уведомление favoriteColor):

@interface Person : NSManagedObject
 
@property (nonatomic) NSString *firstName;
@property (nonatomic) NSString *lastName;
 
@property (nonatomic) NSColor *favoriteColor;
 
@end

Для подавления предупреждений компилятора можно также добавить директиву реализации:

@implementation Person
 
@dynamic firstName;
@dynamic lastName;
@dynamic favoriteColor;
 
@end

Можно теперь использовать атрибут, поскольку Вы были бы любой другой стандартный атрибут, как проиллюстрировано в следующем фрагменте кода:

Employee *newEmployee =
    [NSEntityDescription insertNewObjectForEntityForName:@"Employee"
        inManagedObjectContext:myManagedObjectContext];
 
newEmployee.firstName = @"Captain";
newEmployee.lastName = @"Scarlet";
newEmployee.favoriteColor = [NSColor redColor];

Пользовательский код

Следующие разделы иллюстрируют реализации для объектных и скалярных значений. Оба запускаются, однако, с общей задачей — необходимо указать персистентный атрибут.

Основной подход

Для использования неподдерживаемых типов в модели управляемого объекта, Вы определяете два атрибута. Каждый - атрибут, который Вы фактически хотите (его значение является, например, цветовым объектом или прямоугольной структурой). Этот атрибут является переходным. Другой «теневое» представление того атрибута. Этот атрибут является персистентным.

Вы указываете тип переходного атрибута как неопределенный (NSUndefinedAttributeType). Так как Базовые Данные не должны хранить и получать переходные свойства, можно использовать любой тип объекта, который Вы хотите для атрибута в Вашей реализации. Базовые Данные действительно, тем не менее, отслеживают состояние переходных свойств так, чтобы они могли участвовать в управлении графом объектов (например, для отмены и восстановления).

Тип теневого атрибута должен быть одним из поддерживаемых типов «бетона». Вы тогда реализуете пользовательский класс управляемых объектов с подходящими методами доступа для переходного атрибута, получающими значение от и хранящими значение к персистентному атрибуту.

Основной подход для объектных и скалярных значений является тем же — необходимо найти способ представлять неподдерживаемый тип данных как один из поддерживаемых типов данных — однако, существует дальнейшее ограничение в случае скалярных значений.

Ограничения скалярного значения

Требование методов доступа, которые Вы пишете, - то, что они должны быть кодированием значения ключанаблюдение значения ключа) совместимый.

Если Вы хотите использовать скалярный тип или структуру, которая не является одним из поддерживаемых непосредственно Базовыми Данными и не одной из структур, поддерживаемых кодированием значения ключа, необходимо сохранить его в управляемом объекте как объект — обычно NSValue экземпляр, несмотря на то, что можно также определить собственный класс. Вы тогда обработаете его как объектное значение, как описано позже в этой статье. Это до пользователей объекта извлечь требуемую структуру из NSValue (или пользовательский), возражают при получении значения, и преобразовать структуру в NSValue (или пользовательский), возражают при установке значения.

Персистентный атрибут

Для любого нестандартного типа атрибута Вы хотите использовать, необходимо выбрать поддерживаемый тип атрибута, который Вы будете использовать для хранения значения. То, которое поддерживало тип, который Вы выбираете, зависит от нестандартного типа и что средние значения, там имеют преобразование его в поддерживаемый тип. Во многих случаях можно легко преобразовать неподдерживаемый объект в NSData объект с помощью archiver. Например, можно заархивировать цветовой объект как показано в следующем примере кода. Тот же метод может использоваться при представлении атрибута как экземпляра NSValue или пользовательского класса (Ваш пользовательский класс должен был бы, конечно, принять NSCoding протокол или обеспечивает некоторые другие средние значения того, чтобы быть преобразованным в поддерживаемый тип данных).

NSData *colorAsData = [NSKeyedArchiver archivedDataWithRootObject:aColor];

Вы свободны использовать безотносительно средних значений Вы хотите произвести трансформацию. Например, Вы могли преобразовать NSRect структура в строковый объект (строки могут, конечно, использоваться в персистентном хранилище).

NSRect aRect; // Instance variable.
NSString *rectAsString = NSStringFromRect(aRect);

Можно преобразовать строку назад в прямоугольное использование NSRectFromString. Необходимо принять во внимание, однако, что, так как процесс трансформации может часто происходить, необходимо гарантировать, что это максимально эффективно.

Обычно Вы не должны реализовывать пользовательские методы доступа для персистентного атрибута. Это - подробность реализации, кроме значения не должен получать доступ сам объект. При изменении этого значения непосредственно возможно, что объект объекта войдет в противоречивое состояние.

Атрибут объекта

Если неподдерживаемый атрибут является объектом, то в модели управляемого объекта Вы указываете ее тип как неопределенный, и что это является переходным. При реализации пользовательского класса объекта нет никакой потребности добавить переменную экземпляра для атрибута — можно использовать частное внутреннее хранилище управляемого объекта. Момент, который необходимо отметить, о реализациях, описанных ниже, - то, что они кэшируют переходное значение. Это делает доступ к значению более эффективным — это также необходимо для управления изменениями. При определении пользовательских переменных экземпляра необходимо очистить эти переменные в didTurnIntoFault вместо dealloc.

Существует две стратегии и для получения и для установки переходного значения. Можно получить переходное значение любой «лениво» (по требованию — описанный в, По требованию Получают Средство доступа), или во время awakeFromFetch (описанный в Предрасчетном Добираются). Может быть предпочтительно получить его лениво, если значение может быть большим (если, например, это - битовый массив). Для персистентного значения можно или обновить его каждый раз, когда переходное значение изменяется (описанный в Средстве доступа Набора Незамедлительного обновления), или можно задержать обновление, пока объект не сохраняется (описанный в Средстве доступа Набора Задержанного обновления).

По требованию получают средство доступа

В получить средстве доступа Вы получаете значение атрибута от частного внутреннего хранилища управляемого объекта. Если значение nil, тогда возможно, что это еще не кэшировалось, таким образом, Вы получаете соответствующее персистентное значение, тогда если то значение не nil, преобразуйте его в надлежащий тип и кэшируйте его. (Вы не должны вызывать значение ключа, наблюдая методы уведомления изменения для метода установки, потому что это не представляет изменение в значении.) Следующий пример иллюстрирует, по требованию получают средство доступа для атрибута цвета.

- (NSColor *)color {
 
    [self willAccessValueForKey:@"color"];
    NSColor *color = [self primitiveColor];
    [self didAccessValueForKey:@"color"];
    if (color == nil) {
        NSData *colorData = [self colorData];
        if (colorData != nil) {
            color = [NSKeyedUnarchiver unarchiveObjectWithData:colorData];
            [self setPrimitiveColor:color];
        }
    }
    return color;
}

Предрасчетные добираются

Используя этот подход, Вы получаете и кэшируете персистентное значение в awakeFromFetch. (Вы не должны вызывать значение ключа, наблюдая методы уведомления изменения для метода установки, потому что это не представляет изменение в значении.)

- (void)awakeFromFetch {
 
    [super awakeFromFetch];
    NSData *colorData = [self colorData];
    if (colorData != nil) {
        NSColor *color = [NSKeyedUnarchiver unarchiveObjectWithData:colorData];
        [self setPrimitiveColor:color];
    }
}

В получить средстве доступа Вы тогда просто возвращаете кэшируемое значение.

- (NSColor *)color {
 
    [self willAccessValueForKey:@"color"];
    NSColor *color = [self primitiveColor];
    [self didAccessValueForKey:@"color"];
    return color;
}

Этот метод полезен, если Вы, вероятно, будете часто получать доступ к атрибуту — Вы избегаете условного оператора в получить средстве доступа.

Средство доступа набора незамедлительного обновления

В этом средстве доступа набора Вы устанавливаете значение и для переходного процесса и для персистентных атрибутов одновременно. Вы преобразовываете неподдерживаемый тип в поддерживаемый тип для установки как персистентное значение. Необходимо гарантировать, чтобы Вы вызвали значение ключа, наблюдая методы уведомления изменения, так, чтобы объекты, наблюдая управляемый объект — включая контекст управляемого объекта — были уведомлены относительно модификации. Следующий пример иллюстрирует средство доступа набора для атрибута цвета.

- (void)setColor:(NSColor *)aColor {
 
    [self willChangeValueForKey:@"color"];
    [self setPrimitiveValue:aColor forKey:@"color"];
    [self didChangeValueForKey:@"color"];
    [self setValue:[NSKeyedArchiver archivedDataWithRootObject:aColor]
                forKey:@"colorData"];
}

Основной недостаток с этим подходом - то, что персистентное значение повторно вычисляется каждый раз, когда переходное значение обновляется, который может быть проблемой производительности.

Средство доступа набора задержанного обновления

В этом методе в средстве доступа набора Вы только устанавливаете значение для переходного атрибута. Вы реализуете a willSave метод, обновляющий персистентное значение как раз перед объектом, сохраняется. (Вы не должны вызывать значение ключа, наблюдая методы уведомления изменения вокруг метода установки, потому что это не представляет изменение в значении.)

- (void)setColor:(NSColor *)aColor {
 
    [self willChangeValueForKey:@"color"];
    [self setPrimitiveValue:aColor forKey:@"color"];
    [self didChangeValueForKey:@"color"];
}
 
- (void)willSave {
 
    NSColor *color = [self primitiveValueForKey:@"color"];
    if (color != nil) {
        [self setPrimitiveValue:[NSKeyedArchiver archivedDataWithRootObject:color]
                forKey:@"colorData"];
    }
    else {
        [self setPrimitiveValue:nil forKey:@"colorData"];
    }
    [super willSave];
}

При принятии этого подхода необходимо заботиться при указании правил возможностей. Если цвет является требуемым атрибутом, то (если Вы не предпринимаете другие шаги) необходимо указать атрибут цвета как не дополнительный, и цветной атрибут данных как дополнительный. Если Вы не делаете, то первая работа сохранения может генерировать ошибку проверки.

Когда объект сначала создается, значение colorData nil. Когда Вы обновляете атрибут цвета, colorData атрибут незатронут (т.е. это остается nil ). Когда Вы сохраняете, validateForUpdate: вызывается прежде willSave. На этапе проверки, значении colorData тихо nil, и поэтому сбои проверки.

Скалярные значения

Можно объявить свойства, поскольку скалярные значения, но для Данных Ядра скалярных значений не может динамично генерировать методы доступа — необходимо обеспечить собственные реализации (см. Методы доступа Управляемого объекта). Базовые Данные автоматически синтезируют примитивные методы доступа (primitiveLength и setPrimitiveLength:), но необходимо объявить, что они подавляют предупреждения компилятора.

Для объектов, которые будут использоваться или в наборе Основы или в представлении AppKit, необходимо обычно позволять Базовым Данным использовать свое хранение по умолчанию вместо того, чтобы создать скалярные экземпляры для содержания значений свойств:

  • Существует CPU и память наверху в создании и уничтожении NSNumber объектные обертки для Ваших скаляров;

  • Базовые Данные оптимизируют во время выполнения любые методы доступа, которые Вы не переопределяете — например, это встраивает доступ и вызовы метода уведомления изменения.

Преимущества разрешения Базовых Данных управлять его собственным хранением обычно перевешивают любые преимущества взаимодействия непосредственно со скалярными значениями, несмотря на то, что, если Вы подозреваете, что это не истина для Вашего приложения, которое необходимо использовать инструменты анализа эффективности для проверки.

Можно объявить свойства как скалярные значения. Базовые Данные не могут, тем не менее, динамично генерировать методы доступа для скалярных значений — необходимо обеспечить собственные реализации. Если у Вас есть атрибут length это указано в модели как a double (NSDoubleAttributeType), в интерфейсном файле Вы объявляете length как:

@property double length;

В файле реализации Вы реализуете средства доступа, вызывающие соответствующий доступ и методы уведомления изменения и примитивные средства доступа. Базовые Данные автоматически синтезируют примитивные методы доступа (primitiveLength и setPrimitiveLength:), но необходимо объявить, что они подавляют предупреждения компилятора (можно объявить их использующий свойство).

@interface MyManagedObject (PrimitiveAccessors)
@property (nonatomic) NSNumber *primitiveLength;
@end
 
 
- (double)length {
 
    [self willAccessValueForKey:@"length"];
    NSNumber *tmpValue = [self primitiveLength];
    [self didAccessValueForKey:@"length"];
    return (tmpValue!=nil) ? [tmpValue doubleValue] : 0.0; // Or a suitable representation for nil.
}
 
- (void)setLength:(double)value {
 
    NSNumber *temp = @(value);
    [self willChangeValueForKey:@"length"];
    [self setPrimitiveLength:temp];
    [self didChangeValueForKey:@"length"];
}

Неатрибут объекта

Если неподдерживаемый атрибут поддерживается кодированием значения ключа, то в управляемом объекте моделируют Вас, снова указывают его тип как неопределенный, и что это является переходным. При реализации пользовательского класса объекта Вы обычно добавляете переменную экземпляра для атрибута. Например, учитывая атрибут вызывается bounds то, что Вы хотите представлять использование NSRect структура, Ваш интерфейс класса мог бы походить на показанный в следующем примере.

@interface MyManagedObject : NSManagedObject
{
    NSRect bounds;
}
@property (nonatomic) NSRect bounds;
@end

При использовании переменной экземпляра для содержания атрибута, необходимо также реализовать примитивный, получают и устанавливают средства доступа (см. Пользовательские Примитивные Методы доступа), как показано в следующем примере.

@interface MyManagedObject : NSManagedObject
{
    NSRect myBounds;
}
@property (nonatomic, assign) NSRect bounds;
@property (nonatomic, assign) NSRect primitiveBounds;
@end

Примитивные методы просто получают и устанавливают переменную экземпляра — они не вызывают изменение наблюдения значения ключа или методы уведомления доступа — как показано в следующем примере.

- (NSRect)primitiveBounds
{
    return myBounds;
}
- (void)setPrimitiveBounds:(NSRect)aRect
    myBounds = aRect;
}

Какой бы ни стратегия, которую Вы принимаете, Вы тогда, реализует методы доступа главным образом, как описано для объектного значения. Для получить средства доступа можно принять или ленивый или предрасчетный метод, и для средства доступа набора можно принять или незамедлительное обновление или задержанный метод обновления. Следующие разделы иллюстрируют только прежние версии каждого.

Получить средство доступа

В получить средстве доступа Вы получаете значение атрибута от частного внутреннего хранилища управляемого объекта. Если значение еще не было установлено, то возможно, что это еще не кэшировалось, таким образом, Вы получаете соответствующее персистентное значение, то, если то значение не nil, преобразуйте его в надлежащий тип и кэшируйте его. Следующий пример иллюстрирует получить средство доступа для прямоугольника (этот пример делает предположение упрощения, что ширина границ не может быть 0, таким образом, если значение 0 тогда границы еще не были разархивированы).

- (NSRect)bounds
{
    [self willAccessValueForKey:@"bounds"];
    NSRect aRect = bounds;
    [self didAccessValueForKey:@"bounds"];
    if (aRect.size.width == 0)
    {
        NSString *boundsAsString = [self boundsAsString];
        if (boundsAsString != nil)
        {
            bounds = NSRectFromString(boundsAsString);
        }
    }
    return bounds;
}

Средство доступа набора

В средстве доступа набора необходимо установить значение и для переходного процесса и для персистентных атрибутов. Вы преобразовываете неподдерживаемый тип в поддерживаемый тип для установки как персистентное значение. Необходимо гарантировать, чтобы Вы вызвали значение ключа, наблюдая методы уведомления изменения, так, чтобы объекты, наблюдая управляемый объект — включая контекст управляемого объекта — были уведомлены относительно модификации. Следующий пример иллюстрирует средство доступа набора для прямоугольника.

- (void)setBounds:(NSRect)aRect
{
    [self willChangeValueForKey:@"bounds"];
    bounds = aRect;
    [self didChangeValueForKey:@"bounds"];
    NSString *rectAsString = NSStringFromRect(aRect);
    [self setValue:rectAsString forKey:@"boundsAsString"]; }

Проверка типа

При определении атрибута для использования нестандартного типа, можно также указать, что имя класса раньше представляло значение, с помощью setAttributeValueClassName:.

Можно только установить имя класса значения в коде. Следующий пример показывает, как можно изменить модель управляемого объекта для включения имени класса значения для нестандартного атрибута (favoriteColor) представленный в этом случае экземпляр пользовательского класса, MyColor.

myManagedObjectModel = <#Get a managed object context#>;
 
NSEntityDescription *employeeEntity =
    [[myManagedObjectModel entitiesByName] objectForKey:@"Employee"];
NSAttributeDescription *favoriteColorAttribute =
    [[employeeEntity attributesByName] objectForKey:@"favoriteColor"];
 
// Set the attribute value class to MyColor
[favoriteColorAttribute setAttributeValueClassName:@"MyColor"];

Во время выполнения должен фактически существовать класс значения атрибута. Если Вы пишете само имя класса c орфографическими ошибками (например, MyColour вместо MyColor), проверка успешно выполняется тихо.

Базовые Проверки данных класс любого набора значений как значение атрибута и выдает исключение, если это - экземпляр неправильного класса:

Employee *newEmployee =
        [NSEntityDescription insertNewObjectForEntityForName:@"Employee"
            inManagedObjectContext:aManagedObjectContext];
newEmployee.favoriteColor = [NSColor redColor]; // Exception thrown here.