2015-01-20 3 views
2

При сохранении NSArray в трансформируемом атрибуте Core Data объект не будет доступен для доступа при последующем извлечении его объекта. Однако после этого он доступен для любой выборки. Что происходит?Основные данные Transformable Attributes (NSArray) пустые

Я могу установить и сохранить объект Core Data и его атрибуты из одного места в моем приложении iOS. Затем я читаю последний сохраненный объект. Доступны все атрибуты, кроме трансформируемых NSArrays. По какой-то причине массивы отображаются как пустые (при печати в журнале это выглядит так: route = "(\n)".Если приложение закрывается и затем снова открывается, атрибут больше не пуст. Любые идеи?

Я понимаю, что сохранение NSArray к трансформируемому атрибуту не лучшая практика. не могли бы вы объяснить, почему это происходит?


Update 1

NSArray заполнен объектами CLLocation.

На консоли отсутствуют ошибки или предупреждения. Они также не содержат никаких предупреждений или ошибок компилятора.


Update 2

Ниже приводится XCTest я написал для этого вопроса. Тест не прерывается до самого последнего утверждения (как и ожидалось).

- (void)testRouteNotNil { 
    // This is an example of a performance test case. 
    NSMutableArray *route; 
    for (int i = 0; i < 500; i++) { 
     CLLocation *location = [[CLLocation alloc] initWithLatitude:18 longitude:18]; 
     [route addObject:location]; 
    } 
    NSArray *immutableRoute = route; 

    // Save the workout entity 
    // Just use placeholder values for the XCTest 
    // The method below works fine, as the saved object exists when it is fetched and no error is returned. 
    NSError *error = [self saveNewRunWithDate:@"DATE01" time:@"TIME" totalSeconds:100 distance:[NSNumber numberWithInt:100] distanceString:@"DISTANCE" calories:@"CALORIES" averageSpeed:[NSNumber numberWithInt:100] speedUnit:@"MPH" image:[UIImage imageNamed:@"Image"] splits:route andRoute:immutableRoute]; 
    XCTAssertNil(error); 

    // Fetch the most recently saved workout entity 
    RunDataModel *workout = [[[SSCoreDataManager sharedManager] fetchEntityWithName:@"Run" withSortAttribute:@"dateObject" ascending:NO] objectAtIndex:0]; 
    XCTAssertNotNil(workout); 

    // Verify that the fetched workout is the one we just saved above 
    XCTAssertEqual(workout.date, @"DATE01"); 

    // Check that the any non-NSArray object stored in the entity is not nil 
    XCTAssertNotNil(workout.distance); 

    // Check that the route object is not nil 
    XCTAssertNotNil(workout.route); 
} 

Update 3

Как вы можете видеть ниже, это как базовая модель данных является установка в Xcode. Выбран атрибут маршрута. Обратите внимание, что я пробовал это как с временным свойством, так и без него. Нужно ли добавлять Value Transformer Name, что это?

enter image description here


Update 4

Сам код для управления основными данными исходит из моего GitHub репо, SSCoreDataManger (который хорошо работает, насколько мне известно).

Вот saveNewRunWithDate метод:

- (NSError *)saveNewRunWithDate:(NSString *)date time:(NSString *)time totalSeconds:(NSInteger)totalSeconds distance:(NSNumber *)distance distanceString:(NSString *)distanceLabel calories:(NSString *)calories averageSpeed:(NSNumber *)speed speedUnit:(NSString *)speedUnit image:(UIImage *)image splits:(NSArray *)splits andRoute:(NSArray *)route { 
    RunDataModel *newRun = [[SSCoreDataManager sharedManager] insertObjectForEntityWithName:@"Run"]; 
    newRun.date = date; 
    newRun.dateObject = [NSDate date]; 
    newRun.time = time; 
    newRun.totalSeconds = totalSeconds; 
    newRun.distanceLabel = distanceLabel; 
    newRun.distance = distance; 
    newRun.calories = calories; 
    newRun.averageSpeed = speed; 
    newRun.speedUnit = speedUnit; 
    newRun.image = image; 
    newRun.splits = splits; // This is also an issue 
    newRun.route = route; // This is an issue 
    return [[SSCoreDataManager sharedManager] saveObjectContext]; 
} 

И ниже является RunDataModel NSManagedObject Интерфейс:

/// CoreData model for run storage with CoreData 
@interface RunDataModel : NSManagedObject 

@property (nonatomic, assign) NSInteger totalSeconds; 
// ... 
// Omitted most attribute properties because they are irrelevant to the question 
// ... 
@property (nonatomic, strong) UIImage *image; 

/// An array of CLLocation data points in order from start to end 
@property (nonatomic, strong) NSArray *route; 

/// An array of split markers from the run 
@property (nonatomic, strong) NSArray *splits; 

@end 

В реализации этих свойств могут быть сконфигурированы с использованием @dynamic

+0

Что содержится в массиве? Какие объекты? – quellish

+0

@quellish Пожалуйста, ознакомьтесь с моим обновленным вопросом. NSArray заполнен объектами CLLocation. –

+1

Вы сохранили/зафиксировали свой контекст? вы получаете доступ к нему из одного потока? – nielsbot

ответ

1

@ quellish's answer содержит информацию о неисправностях Core Data и некоторых нюансах и трюках, которые лежат в нем. После некоторого рытья, и с помощью этого ответа я нашел решение.

перед получением желаемого (проблема) юридического лица, обновите NSManagedObject в NSManagedObjectContext:

[self.managedObjectContext refreshObject:object mergeChanges:NO]; 

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

15

А "трансформируемые" лица атрибутом является тот, который проходит через экземпляр NSValueTransformer. Имя класса NSValueTransformer для использования для определенного атрибута задается в модели управляемого объекта.Когда Core Data обращается к данным атрибута, он вызовет +[NSValueTransformer valueTransformerForName:], чтобы получить экземпляр преобразователя значений. Используя этот трансформатор значений, NSData сохраняется в хранилище для объекта, который будет преобразован в значение объекта, доступное через свойство экземпляра управляемого объекта.

Вы можете прочитать об этом в разделе Руководства по программированию Core Data Non-Standard Persistent Attributes

По умолчанию Core Data использует трансформатор значения для зарегистрированного имени NSKeyedUnarchiveFromDataTransformerName и использует его в обратном выполнить преобразование. Это произойдет, если в редакторе моделей основных данных не указано имя преобразователя значений и, как правило, это поведение, которое вы хотите. Если вы хотите использовать другой номер NSValueTransformer, вы должны зарегистрировать его имя в своем приложении, вызвав +[NSValueTransformer setValueTransformer:forName:] и установить имя строки в редакторе модели (или в коде, что является другим вопросом). Имейте в виду, что используемый вами трансформатор значения должен поддерживать как прямое, так и обратное преобразование.

Преобразователь по умолчанию может превращать любой объект, который поддерживает архивирование с ключом, в NSData. В вашем случае у вас есть NSArray (фактически, NSMutableArray, что плохо). NSArray поддерживает NSCoding, но поскольку это коллекция, содержащаяся в ней, должна также поддерживать ее, иначе они не могут быть заархивированы. К счастью, CLLocation поддерживает NSSecureCoding, более новый вариант NSCoding.

Вы можете проверить трансформацию NSArrayCLLocation с использованием трансформатора Core Data. Например:

- (void)testCanTransformLocationsArray { 
    NSValueTransformer *transformer  = nil; 
    NSData    *transformedData = nil; 

    transformer = [NSValueTransformer valueTransformerForName:NSKeyedUnarchiveFromDataTransformerName]; 
    transformedData = [transformer reverseTransformedValue:[self locations]]; 
    XCTAssertNotNil(transformedData, @"Transformer was not able to produce binary data"); 
} 

Я бы посоветовал вам написать такие тесты для трансформируемых атрибутов. Легко вносить изменения в ваше приложение, которые несовместимы с трансформатором по умолчанию (например, вставлять объекты, которые не поддерживают архивирование с ключами).

Используя набор тестов, подобных этому, я не могу воспроизвести какие-либо проблемы с архивированием NSArray из CLLocation.

Существует одна очень важная часть вашего вопроса:

почему массивы отображаются как пустые (при печати в журнал, это выглядит следующим образом:. Маршрут = «(\ п)» Если приложение закрывается, а затем снова открывается, атрибут больше не является пустым. Любые идеи?

Это указывает на то, что (по крайней мере, в вашем приложении, возможно, не ваш тест) данные является трансформируются и применяется к объект в хранилище. Когда приложение устанавливает значение routes, ar ray сохраняется в магазине - мы это знаем, потому что при следующем запуске приложения появляются данные.

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

В то же время есть места SSCoreDataManager использует -performBlock: для доступа к одному NSManagedObjectContext.performBlock: должен использовать только с контекстами, созданными с использованием типа параллелизма очереди. Используемый здесь контекст был создан с -init, который просто обертывает -initWithConcurrencyType: и передает значение NSConfinementConcurrencyType. Из-за этого у вас определенно возникают проблемы параллелизма в синглете, которые, скорее всего, вызывают некоторые из поведения, которое вы видите. Вы сохраняете значение атрибута для сущности, но позже не видите, что это значение отражается, когда свойство, обертывающее атрибут, вызывает ошибку в контексте управляемого объекта.

Если вы способны развить с Xcode 6.x и прошивкой 8, включить отладку ядра параллелизма данных, передавая Launch рассуждения

-com.apple.CoreData.ConcurrencyDebug 1

Для вашего приложения. Это должно сделать некоторые из проблем здесь более заметными для вас, хотя просто вызов performBlock: в контексте, созданном с помощью -init, должен вызывать исключение. Если ваше приложение делает что-то, чтобы проглотить исключения, которые могут скрывать эту и другие проблемы.

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

К сожалению, руководство по программированию основных данных barely mentions what a fault is и делает это бок о бок с уникальным. Неисправность является фундаментальной частью Core Data - это большая часть ее использования и почти не имеет ничего общего с уникальным. К счастью, несколько лет назад Incremental Store Programming Guide был обновлен многими сведениями о внутренних компонентах Core Data, включая ошибки.

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

+0

Фантастический, подробный ответ! Спасибо огромное! Однако, у меня есть вопрос. Что вы подразумеваете под «обвинением в ошибке по стоимости собственности»? Я не уверен, что значит «пристрелить ошибку» на что-то .... –

1

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

Вот что может происходить в тестах.

  1. В какой-то момент вы создали новый Run без маршрута и сохранены.
  2. Во время следующего тестового запуска вы создаете еще один объект запуска с той же датой DATE01.
  3. Вместо проверки свойства route объекта, который вы только что создали, вы делаете выборку, отсортированную по дате.
  4. Все ваши маршруты имеют ту же дату, поэтому сортировка по дате в основном не влияет на отсортированные результаты.
  5. Первым объектом ваших результатов выборки является старый объект, в котором вы не задали свойство route.

На всякий случай зарегистрируйте значение newRun.route внутри метода -saveNewRunWithDate:....

3
NSMutableArray *route = [NSMutableArray array]; 

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

1

У меня была схожая проблема с этим, что мне было очень трудно решить. В конце концов я разрешил это, но здесь не решалось это решение. Я хочу поделиться тем, что я нашел, работающим ради любого, кто сталкивается с тем же вызовом, что и я.

Решение для меня пришло отсюда: Core Data not saving transformable NSMutableDictionary

В моем случае проблема была, потому что я пытался использовать NSMutableArray как трансформируемой атрибут Core Data. Но теперь я понимаю, что вы не должны этого делать. Вместо этого вы должны использовать неизменяемый массив (например, NSArray), а затем, если вам нужно изменить значение в массиве, вы копируете массив основных данных в локальный изменяемый массив (то есть var NSArray в Swift), внесите изменение в локальный array, а затем запустите команду, чтобы сделать массив Core Data равным измененному локальному массиву. Затем сохраните Core Data как обычно.

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

Смежные вопросы