Используя управляемые объекты

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

Доступ и изменение свойств

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

Большинство отношений по сути двунаправлено. Любые изменения, внесенные в отношения между объектами, должны поддержать целостность графа объектов. При условии, что Вы правильно смоделировали отношение в обоих направлениях и установили инверсии, изменение одного конца отношения автоматически обновляет другой конец — посмотрите Отношения Управления и Целостность Графа объектов.

Атрибуты и к - отношения

Вы получаете доступ к атрибутам и к - отношения управляемого объекта с помощью стандартных методов доступа или с помощью Objective C 2,0 точечных синтаксиса (см. Точечный Синтаксис), как проиллюстрировано в следующем фрагменте кода:

NSString *firstName = [anEmployee firstName];
Employee *manager = anEmployee.manager;

Точно так же можно использовать или стандартные методы доступа или точечный синтаксис для изменения атрибутов; например:

newEmployee.firstName = @"Stig";
[newEmployee setManager:manager];

В случаях и методов get и методов set, точечный синтаксис точно эквивалентен вызову стандартного метода. Например, следующие два оператора используют идентичные пути выполнения кода:

[[aDepartment manager] setSalary:[NSNumber numberWithInteger:100000]];
aDepartment.manager.salary = [NSNumber numberWithInteger:100000];

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

[newEmployee setValue:@"Stig" forKey:@"firstName"];
[aDepartment setValue:[NSNumber numberWithInteger:100000] forKeyPath:@"manager.salary"];

Необходимо, однако, изменить значения атрибута KVC-совместимым способом. Например, следующий обычно представляет программную ошибку:

NSMutableString *mutableString = [NSMutableString stringWithString:@"Stig"];
[newEmployee setFirstName:mutableString];
[mutableString setString:@"Laura"];

Для непостоянных значений необходимо или передать владение значения к Базовым Данным или реализовать пользовательские методы доступа всегда выполнить копию. Если класс, представляющий объект Сотрудника, объявил, предыдущий пример может не представлять ошибку firstName свойство (copy) (или реализованный пользовательское setFirstName: метод, скопировавший новое значение). В этом случае, после вызова setString: (в третьей строке кода) значение firstName тогда все еще был бы «Stig» и не «Лора».

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

К - много отношений

К доступу к - многие отношение (ли место назначения связи «один ко многим» или many-many отношения), Вы используете стандарт, получают метод доступа. К - многие отношение представлено набором, как проиллюстрировано в следующем фрагменте кода:

NSSet *managersPeers = [managersManager directReports];
NSSet *departmentsEmployees = aDepartment.employees;

При доступе к месту назначения отношения можно первоначально получить объект отказа (см. Faulting и Uniquing) — отказ стреляет автоматически при внесении каких-либо изменений в него. (Обычно нет никакой потребности знать, является ли отношение отказом, однако можно узнать использование NSManagedObject hasFaultForRelationshipNamed: метод.)

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

NSSet *newEmployees = [NSSet setWithObjects:employee1, employee2, nil];
[aDepartment setEmployees:newEmployees];
 
NSSet *newDirectReports = [NSSet setWithObjects:employee3, employee4, nil];
manager.directReports = newDirectReports;

Как правило, однако, Вы не хотите устанавливать все отношение, вместо этого Вы хотите добавить или удалить единственный элемент за один раз. Чтобы сделать это, необходимо использовать mutableSetValueForKey: или один из автоматически сгенерированных мутаторных методов отношения (см. Динамично сгенерированные Методы доступа):

NSMutableSet *employees = [aDepartment mutableSetValueForKey:@"employees"];
[employees addObject:newEmployee];
[employees removeObject:firedEmployee];
 
// or
[aDepartment addEmployeesObject:newEmployee];
[aDepartment removeEmployeesObject:firedEmployee];

Важно понять различие между значениями, возвращенными точечным средством доступа и mutableSetValueForKey:. mutableSetValueForKey: возвращает непостоянный объект прокси. При видоизменении его содержания это испустит надлежащие уведомления изменения наблюдения значения ключа (KVO) для отношения. Точечное средство доступа просто возвращает набор. Если Вы управляете набором как показано в этом фрагменте кода:

[aDepartment.employees addObject:newEmployee]; // do not do this!

тогда уведомления изменения KVO не испускаются, и обратная связь не обновляется правильно.

Вспомните, что точка просто вызывает метод доступа, таким образом, по тем же причинам:

[[aDepartment employees] addObject:newEmployee]; // do not do this, either!

Сохранение изменений

Просто изменение управляемого объекта не заставляет изменения быть сохраненными к хранилищу. Контекст управляемого объекта действует как временная память. Можно создать и зарегистрировать управляемые объекты в нем, внести изменения в объекты, и отменить и восстановить изменения, как Вы желаете. При внесении изменений в управляемые объекты связанными с данным контекстом те изменения остаются локальными для того контекста, пока Вы не фиксируете изменений путем отправки контекста a save: сообщение. В той точке — при условии, что нет никаких ошибок проверки — изменения посвящают себя хранилищу. Как заключение, просто создавая управляемый объект не заставляет его быть сохраненным к персистентному хранилищу, и удаление управляемого объекта не заставляет запись быть удаленной из хранилища — необходимо сохранить контекст для фиксации изменения.

См., что также Гарантирующие Данные Актуальны.

Управляемый объект IDs и URIs

NSManagedObjectID объект является универсальным идентификатором для управляемого объекта и обеспечивает основание для uniquing в Базовой Платформе Данных. Управляемый объект ID однозначно определяет тот же управляемый объект и между контекстами управляемого объекта в отдельном приложении, и в многократных приложениях (как в распределенных системах). Как первичный ключ в базе данных, идентификатор содержит информацию, должен был точно описать объект в персистентном хранилище, несмотря на то, что не представлена подробная информация. Платформа полностью инкапсулирует «внешнюю» информацию и представляет чистый объектно-ориентированный интерфейс.

NSManagedObjectID *moID = [managedObject objectID];

Существует две формы идентификатора объекта. Когда управляемый объект сначала создается, Базовые Данные присваивают его временный ID; только если это сохраняется к персистентному хранилищу, действительно Удаляет сердцевину Данных, присваивают управляемый объект постоянный ID. Можно с готовностью обнаружить, является ли ID временным:

BOOL isTemporary = [[managedObject objectID] isTemporaryID];

Можно также преобразовать идентификатор объекта в представление URI:

NSURL *moURI = [[managedObject objectID] URIRepresentation];

Учитывая управляемый объект ID или URI, можно получить соответствующее использование управляемого объекта managedObjectIDForURIRepresentation: или objectWithID:.

Преимущество представления URI состоит в том, что можно заархивировать его — несмотря на то, что во многих случаях Вы не должны архивировать временный ID, так как это, очевидно, подвержено изменениям. Вы могли, например, сохранить заархивированный URIs в пользовательских значениях по умолчанию своего приложения для сохранения последней выбранной группы объектов в табличном представлении. Можно также использовать URIs для поддержки копии и операций вставки (см. Копирование и Копию и Вставку), и перетащите операции (см. Перетаскивание).

Можно использовать идентификаторы объектов для определения «слабых» отношений через персистентные хранилища (где никакое твердое соединение не возможно). Например, для слабого к - многие отношение Вы храните, как заархивировано URIs IDs объектов в месте назначения отношения и поддерживаете отношения как переходный атрибут, полученный из идентификаторов объектов.

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

Копирование и копия и вставка

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

Копирование атрибутов

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

Новое, несохраненный, управляемый объект имеет временный ID. Если пользователь выполняет работу копии, и затем работа сохранения, изменения ID управляемого объекта и ID, зарегистрированный в копии, будут недопустимы в последующей операции вставки. Для обхождения этого Вы используете “ленивую запись” (как описано в Копии и Вставке). В работе копии Вы объявляете свой пользовательский тип, но если ID управляемого объекта является временным, Вы не пишете данные — но Вы действительно сохраняете ссылку на исходный управляемый объект. В pasteboard:provideDataForType: метод Вы тогда пишете текущий ID для объекта.

Как дальнейшая сложность, возможно, что ID является все еще временным во время операции вставки, все же необходимо все еще допускать возможность будущих операций вставки после прошедшей работы сохранения. Необходимо поэтому повторно объявить, что тип на области монтажа устанавливает ленивую вставку снова, иначе область монтажа сохранит временный ID. Вы не можете вызвать addTypes:owner: во время pasteboard:provideDataForType:, таким образом, необходимо использовать задержанный, выполняют — например:

- (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
{
    if ([type isEqualToString:MyMOIDType]) {
        // assume cachedManagedObject is object originally copied
        NSManagedObjectID *moID = [cachedManagedObject objectID];
        NSURL *moURI = [moID URIRepresentation];
        [sender setString:[moURI absoluteString] forType:MyMOIDType];
        if ([moID isTemporaryID]) {
            [self performSelector:@selector(clearMOIDInPasteboard:)
                    withObject:sender afterDelay:0];
        }
    }
    // implementation continues...
}
 
- (void)clearMOIDInPasteboard:(NSPasteboard *)pb
{
    [pb addTypes:[NSArray arrayWithObject:MyMOIDType] owner:self];
}

Копирование отношений

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

Перетаскивание

Можно выполнить операции перетаскивания с управляемыми объектами — таким как, например, передав объект от одного отношения до другого — использование представления URI, как описано в Управляемом объекте IDs и URIs.

NSURL *moURI = [[managedObject objectID] URIRepresentation];

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

NSURL *moURL = // get it from the pasteboard ...
NSManagedObjectID *moID = [[managedObjectContext persistentStoreCoordinator]
    managedObjectIDForURIRepresentation:moURL];
// assume moID non-nil...
NSManagedObject *mo = [managedObjectContext objectWithID:moID];

Это предполагает, что перетаскивание «в единственном штабеле персистентности» — т.е. что, если существует больше чем один контекст управляемого объекта, включенный это, они используют совместно используемого персистентного координатора хранилища — или что перетаскиваемый объект (ы) находится в хранилище, на которое ссылаются персистентные координаторы хранилища.

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

Проверка

Базовая платформа Данных обеспечивает чистую инфраструктуру для поддержки проверки, и через логику, инкапсулировавшую в объектной модели и через пользовательский код. В модели управляемого объекта можно указать ограничения на значения, которые может иметь свойство (например, зарплата Сотрудника не может быть отрицательной, или что каждый сотрудник должен принадлежать Отделу). Существует две формы пользовательских методов проверки — те, которые следуют стандартным соглашениям кодирования значения ключа (см. Проверку Значения ключа) для проверки значения для единственного атрибута и специального набора (validateForInsert:, validateForUpdate:, и validateForDelete:) для проверки целого объекта на различных этапах его жизненного цикла (вставка, обновление и удаление). Последний может быть особенно полезен для проверки комбинаций значений — например, чтобы гарантировать, что сотрудник может быть введен в план покупки акций, только если их период службы превышает данную длину, и их ранг оплаты в или выше определенного уровня.

Основанные на модели ограничения проверяются, и методы проверки вызываются автоматически, прежде чем изменения посвящают себя внешнему хранилищу для предотвращения недопустимых сохраненных данных. Можно также вызвать их программно каждый раз, когда необходимо. Вы проверяете отдельное использование значений validateValue:forKey:error:. Управляемый объект сравнивает новое значение с ограничениями, указанными в модели, и вызывает любой пользовательский метод проверки (формы validate<Key>:error:) Вы реализовали. Даже при реализации пользовательских методов проверки Вы не должны обычно вызывать пользовательские методы проверки непосредственно. Это гарантирует, что применяются любые ограничения, определенные в модели управляемого объекта.

Управление отменой

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

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

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

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

Например, в некоторых ситуациях Вы хотите измениться — или, в частности, отключить — поведение отмены. Это может быть полезно, если Вы хотите создать набор по умолчанию объектов, когда новый документ создается (но хотят гарантировать, что документ не показан как являющийся грязным, когда это выведено на экран), или если необходимо объединить новое состояние от другого потока или процесса. В целом, для выполнения операций без регистрации отмены Вы отправляете менеджера по отмене a disableUndoRegistration передайте, внесите изменения, и затем отправьте менеджера по отмене enableUndoRegistration сообщение. Перед каждым Вы отправляете контекст a processPendingChanges сообщение, как проиллюстрировано в следующем фрагменте кода:

NSManagedObjectContext *moc = ...;
[moc processPendingChanges];  // flush operations for which you want undos
[[moc undoManager] disableUndoRegistration];
// make changes for which undo operations are not to be recorded
[moc processPendingChanges];  // flush operations for which you do not want undos
[[moc undoManager] enableUndoRegistration];

Отказы

Управляемые объекты обычно представляют данные, сохраненные в персистентном хранилище. В некоторых ситуациях управляемый объект может быть «отказом» — объект, значения свойств которого еще не были загружены из внешнего хранилища. При доступе к персистентным значениям свойств отказ «стреляет», и его персистентные данные получены автоматически от хранилища. При некоторых обстоятельствах можно явно превратить управляемый объект в отказ (обычно, чтобы гарантировать, что его значения актуальны, с помощью NSManagedObjectContext refreshObject:mergeChanges:). Более обычно Вы встречаетесь с отказами при пересечении отношений.

При выборке управляемого объекта Базовые Данные автоматически не выбирают данные для других объектов, к которым это имеет отношения (см. Дающие сбой Пределы Размер Графа объектов). Первоначально, отношения объекта представлены отказами (если целевой объект не был уже выбран — видят, что Uniquing Гарантирует Отдельный управляемый объект на Запись на Контекст). Однако, при доступе к целевому объекту или объектам отношения их данные получены автоматически для Вас. Например, предположите выборку единственного объекта Сотрудника от персистентного хранилища, когда приложение сначала запускается, тогда (предполагающий, что они существуют в персистентном хранилище), manager и department отношения представлены отказами. Можно, тем не менее, попросить фамилию менеджера сотрудника как показано в следующем примере кода:

NSString *managersName =
        [[anEmployee valueForKey:@"manager"] valueForKey:@"lastName];

или более легко использующие ключевые пути:

NSString *managersName =
        [anEmployee valueForKeyPath:@"manager.lastName"];

В этом случае данные для целевого объекта Сотрудника (менеджер) получены для Вас автоматически.

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

NSString *departmentName = [anEmployee valueForKeyPath:@"manager.manager.department.name"];

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

NSNumber *salaryOverhead = [anEmployee valueForKeyPath:@"department.employees.@sum.salary"];

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

Обеспечение данных актуально

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

Обновление объекта

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

Для обновления значений свойств управляемого объекта Вы используете метод контекста управляемого объекта refreshObject:mergeChanges:. Если mergeChanges флаг YES, метод объединяет значения свойств объекта с теми из объекта, доступного в персистентном координаторе хранилища; если флаг NO, метод просто возвращает объект в отказ, не объединяясь (который также заставляет сильные ссылки к другим связанным управляемым объектам быть поврежденными, таким образом, можно использовать этот метод для обрезки части графа объектов, Вы хотите содержать в памяти).

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

Слияние изменений с переходными свойствами

Если Вы используете refreshObject:mergeChanges: с mergeChanges флаг YES, тогда любые переходные свойства восстанавливаются их значению перед обновлением после awakeFromFetch вызывается. Это означает, что, если у Вас есть переходное свойство со значением, зависящим от свойства, обновляющегося, переходное значение может стать из синхронизации.

Рассмотрите заявление, в котором у Вас есть объект Лица с атрибутами firstName и lastName, и кэшируемый переходный процесс получил свойство, fullName (на практике это могло бы быть маловероятно это a fullName атрибут кэшировался бы, но пример просто понять). Предположим также это fullName вычисляется и кэшируется в пользовательском awakeFromFetch метод.

Лицо, в настоящее время называемое «Сэритом Смитом» в персистентном хранилище, редактируется в двух контекстах управляемого объекта:

  • В контексте один, соответствующий экземпляр firstName изменяется на «Фиону» (который вызывает кэшируемый fullName быть обновленным «Фионе Смит») и сохраненный контекст.

    В персистентном хранилище лицом является теперь «Фиона Смит».

  • В контексте два, соответствующий экземпляр lastName изменяется на «Джонса», вызывающего кэшируемый fullName быть обновленным «Сэриту Джонсу».

    Объект тогда обновляется с mergeChanges флаг YES. Обновление выбирает «Фиону Смит» от хранилища.

    • firstName не был изменен до обновления; обновление заставляет его быть обновленным к новому значению от персистентного хранилища, таким образом, это - теперь «Фиона».

    • lastName был изменен до обновления; таким образом, после обновления, это задержано к его измененному значению — «Джонс».

    • Переходное значение, fullName, был также изменен до обновления. После обновления его значение восстанавливается «Сэриту Джонсу» (чтобы быть корректным, это должна быть «Фиона Джонс»).

Пример показывает это, потому что значения перед обновлением применяются после awakeFromFetch, Вы не можете использовать awakeFromFetch гарантировать, что переходное значение должным образом обновляется после обновления (или если Вы делаете, значение будет впоследствии перезаписано). При этих обстоятельствах лучшее решение состоит в том, чтобы использовать дополнительную переменную экземпляра, чтобы отметить, что обновление произошло и что должно быть повторно вычислено переходное значение. Например, в классе Лица Вы могли объявить переменную экземпляра fullNameIsValid из типа BOOL и реализация didTurnIntoFault метод для установки значения в NO. Вы тогда реализуете пользовательское средство доступа для fullName атрибут, проверяющий значение fullNameIsValid— если это NO, тогда значение повторно вычисляется.