Нестандартные постоянные атрибуты Core Data в среде Cocoa, Mac OS X, iOS

Cocoa

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

Введение

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

Вы можете использовать нестандартные типы постоянных атрибутов либо с помощью трансформируемых атрибутов или с помощью transient (переходных) свойств, представляющих нестандартный атрибут при поддержке поддерживаемого постоянного свойства. Принцип, лежащий в обоих подходах один и тот-же: вы представляете потребителям вашей сущности атрибут того типа, какой Вам необходим, и "за кулисами", он преобразуется в тип, которым может управлять Core Data. Разница между подходами в том, что с трансформируемым атрибутом указывается только один атрибут и преобразование выполняется автоматически. В противоположность этому, с преходящими свойствами указывается два атрибута, и вы должны написать код для выполнения преобразования.

Трансформируемые атрибуты

Идея трансформируемые атрибутов, получить доступ к атрибуту, как нестандартному типу, но за кулисами Core Data используется экземпляр NSValueTransformer для преобразования атрибутов в и из экземпляра NSData. Core Data затем сохраняет данные экземпляра в постоянном хранилище.

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

Можно указать, что атрибут является трансформируемым, а также название трансформатора, используя редактор модели Xcode или программно:

  • Если вы используете редактор модели в Xcode, выберите Transformable в типе атрибута и укажите наименование трансформатора в текстовом поле Value Transformer Name.
  • Если вы устанавливаете тип программно, используйте setAttributeType: и передайте в качестве параметра NSTransformableAttributeType, а затем (при необходимости) используйте setValueTransformerName:, чтобы указать название трансформатора.

В принципе, вы не должны больше ничего делать. На практике, для подавления предупреждений компилятора вы должны объявить свойство атрибута, как показано в следующем примере (уведомление 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];

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

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

Примечание: пример для значения объекта использует экземпляр NSColor, обычно вы должны использовать трансформируемые атрибуты.

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

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

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

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

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

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

Требование к методам доступа, которые вы пишете,- они должны быть совместимы с ключ-значение кодированием (и ключ-значение наблюдением) совместимыми. Ключ-значение кодирование поддерживает только ограниченное количество структур- NSPoint, NSSize, NSRect, и NSRange.

Если вы хотите использовать скалярный тип или структуру, которая не является одной из тех, которые поддерживаются непосредственно Core Data, и не одной из структур поддерживаемой кодированием ключ-значение, вы должны хранить его в управляемом объекте, как правило, в виде экземпляра NSValue, или вы можете также определить ваш собственный класс. Тогда вы будете относиться к нему как значению объекта, как описано далее. Это зависит только при использовании объектов при извлечении требуемой структуры из NSValue (или пользовательского) объекта при получении значения, и превращения структуры в NSValue (или пользовательский) объект при установке значения.

Постоянные атрибуты

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

NSData *colorAsData = [NSKeyedArchiver archivedDataWithRootObject:aColor];

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

NSRect aRect; // экземпляр переменной.

NSString *rectAsString = NSStringFromRect(aRect);

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

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

Объектные атрибуты

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

Получение по запросу Get акцессора

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

- (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;
}

Предварительно рассчитанные Get

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

- (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;
}

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

Немедленно обновляемый Set акцессор

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

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

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

Обновляемый Set акцессор с задержкой

В этой технике, в Set акцессоре вы устанавливаете только значение для переходных атрибутов. Вы реализуете willSave метод, который обновляет постоянное значение непосредственно перед тем, как объект будет сохранен. (Вам не нужно ссылаться на ключ-значение наблюдения уведомления об изменении методов по всему set методe, потому что pltcm не измененяется значение).

- (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, и перед вызовом willSave Core Data вызовет validateForUpdate: для проверки валидности, если атрибут является обязательным, т.е. не может быть nil, произойдет ошибка валидности.

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

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

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

  • Существуют накладные расходы процессора и памяти на создание и уничтожение объектов NSNumber для обертки для вашего скаляра;
  • Core Data оптимизирует время выполнения любых методов доступа вами не переопределенных, например, он встраивает при доступе и изменении вызовы уведомлений.

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

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

@property double length;

В файле реализации, вы реализуете акцессоры, которые вызывают соответствующие методы доступа и уведомление об изменении, и примитивные акцессоры. Core Data автоматически синтезирует примитивные методы доступа (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"];
}

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

Если не поддерживаемый атрибут является одной из структур поддерживаемой кодированием ключ-значение (NSPoint, NSSize, NSRect, или NSRange), то в управляемой модели объекта снова указывают его тип, как неопределенный, и что он преходящий. При реализации пользовательского класса сущности, обычно добавляют переменную экземпляра атрибута. Например, если вы хотите чтобы, атрибут 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;
}
 

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

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

Вы можете установить только значение имени класса в коде. В следующем примере показано, как можно изменить модель управляемого объекта включающего в себя значение имя класса для нестандартных атрибутов (favoriteColor), представленных в этом случае экземплярjv пользовательского класса, 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"];

Значение атрибута class должно реально существовать во время выполнения.

 
 
homeЗаметили ошибкукарта сайта 
   Made on a Mac