Нужна помощь в понимании переходных свойств в Core Data - PullRequest
15 голосов
/ 21 сентября 2011

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

@interface Board : NSManagedObject
{
    NSMutableArray *_grid;
}

// Core Data to-many relationship
@property (nonatomic, retain) NSSet *pieces;

@property (nonatomic, readonly) NSArray *grid;

-(void)awake;

-(void)movePiece:(PieceState *)piece to_x:(int)x y:(int)y;

@end


@implementation Board

@dynamic pieces;

-(void)awakeFromInsert {
    [super awakeFromInsert];
    [self awake];
}

-(void)awakeFromFetch {
    [super awakeFromFetch];
    [self awake];
}

-(void)awake {
    _grid = nil; // probably not necessary
}

-(NSArray *)grid {
    if (!_grid) {
        _grid = [[NSMutableArray alloc] initWithCapacity:10];
        for (int i = 0; i < 10; i++) {
            NSMutableArray *column = [[NSMutableArray alloc] initWithCapacity:10];
            [_grid addObject:column];
            for (int j = 0; j < 10; j++)
                [column addObject:[NSNull null]];
            [column release];
        }

        for (PieceState *piece in self.pieces)
            if (piece.x >= 0 && piece.y >= 0)
                [[_grid objectAtIndex:piece.x] replaceObjectAtIndex:piece.y withObject:piece];
    }

    return _grid;
}

-(void)movePiece:(PieceState *)piece to_x:(int)x y:(int)y {
    if (x >= 0 && y >= 0) {
        NSObject *capturedPieceObject = [[self.grid objectAtIndex:x] objectAtIndex:y];
        if ([capturedPieceObject isKindOfClass:[PieceState class]]) {
            PieceState *capturedPiece = (PieceState *)capturedPieceObject;
            [self removePiecesObject:capturedPiece];
            [[self managedObjectContext] deleteObject:capturedPiece];
            capturedPiece = nil;
        }
    }
    if (_grid) {
        if (piece.x >= 0 && piece.y >= 0)
            [[_grid objectAtIndex:piece.x] replaceObjectAtIndex:piece.y withObject:[NSNull null]];
        if (x >= 0 && y >= 0)
            [[_grid objectAtIndex:x] replaceObjectAtIndex:y withObject:piece];
    }

    [piece setX:x];
    [piece setY:y];
}

- (void)didTurnIntoFault {
    [_grid release];
    _grid = nil;

    [super didTurnIntoFault];
}

@end

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

Я не объявляю grid как временное свойство, и все работает нормально. Мне просто интересно, может ли возникнуть какое-то необычное условие, которое может вызвать ошибку, если я не объявлю временное свойство.

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

Так получу ли я какую-либо выгоду, если объявлю сетку временным свойством?

Дополнительный вопрос. Что делать, если у меня есть такой код:

Board *board = someOtherManagedObject.board;
NSObject *boardContents = [[board.grid objectAtIndex:5] objectAtIndex:5];

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

  1. метод получения сетки с именем
  2. Сетка выделена
  3. self.pieces доступ к
  4. ошибка зажигается
  5. проснулся под названием
  6. _grid = nil
  7. возврат к получателю сетки
  8. [[_grid objectAtIndex:... доступ к нулевому значению, сбой или, по крайней мере, отсутствие операции
  9. сетка возвращает значение ноль
  10. сбой или неправильное поведение, когда ожидается, что BoardContents будет содержать значение

С другой стороны, может быть, если я объявлю grid как временное свойство, то ошибка сработает до того, как будет вызван мой метод получения сетки?

Из TechZen:

Ошибки - это объекты-заполнители, которые определяют граф объектов со связями, но не загружают значения атрибутов. Они будут регистрироваться как экземпляры NSManagedObject или частного _NSFault ... класса.

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

Подождите что? Я начинаю понимать, что мои объекты могут быть неисправны в любое время, но вы говорите мне, что они могут даже не быть экземплярами моего класса !? Или, если вы используете пользовательский подкласс, гарантированно они являются неисправностями, которые являются экземплярами NSManagedObject (особенно моего подкласса)?

Если они не являются экземплярами пользовательского класса, то что происходит с чем-то вроде этого:

@interface Foo : NSManagedObject {
    int data;
}

@property (nonatomic, retain) NSString *modeledProperty;

-(void)doSomething;

@end

@implementation Foo

@dynamic modeledProperty;

-(void)doSomething {
    data++;
}

@end

Что произойдет, если я позвоню doSomething по ошибке?

  1. Не отвечает на селектор, сбой
  2. Запускает мой код, но переменных моего экземпляра не существует, кто знает, что происходит, когда он обрабатывает данные ++
  3. данные существуют, просто modeledProperty не существует, потому что это ошибка

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

Хорошо, но что, если у меня есть метод экземпляра, который не метод доступа к свойствам, как doSomething выше?Как мне убедиться, что у меня есть реальный объект, прежде чем я его назову?Или я могу вызвать его, и первым делом в теле метода убедиться, что у меня есть реальный объект (например, путем доступа к смоделированному свойству)?

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

Я подумал, что если бы она зависела от значений смоделированных свойств, то она вызвала бы ошибку , когда оназависит от них , то есть строка for (PieceState *piece in self.pieces) вызывает ошибку, потому что она обращается к self.pieces, который является смоделированным свойством.Но вы говорите мне, что?

  1. Я даже не могу вызвать метод получения сетки по ошибке
  2. Я могу назвать это, но я не могу использовать _grid так, как я хочу

Кажется, если я понимаю, что вы говорите, и это правда, пользовательские подклассы NSManagedObject очень ограничены.

  1. Они не могут иметь никаких методов экземпляракоторые не являются смоделированными получателями или установщиками свойств, потому что нельзя гарантировать, что объект будет существовать в пригодном для использования состоянии, когда они вызываются.(Исключение: методы экземпляра, которые являются просто вспомогательными методами для методов доступа к свойствам, подойдут.)
  2. Они не могут иметь никаких переменных экземпляра для какой-либо полезной цели, кроме временных кэшей вычисляемых значений, поскольку эти переменные экземпляра могут бытьстерты в любой момент.Я знаю, что они не будут сохранены на диске, но я думал, что они, по крайней мере, будут сохраняться, пока я сохраняю объект в памяти.

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

1 Ответ

42 голосов
/ 22 сентября 2011

Преимущество переходных свойств обусловлено различием между смоделированными / наблюдаемыми свойствами и немоделированными / ненаблюдаемыми свойствами.

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

Моделируемые свойства не требуют специального подкласса NSManagedObject, но могут использовать универсальный экземпляр NSManagedObject, инициализированный для сущности.Доступ к смоделированному свойству ошибки (см. Ниже) приводит к полной загрузке ошибки.

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

Ошибки - это объекты-заполнители, которые определяют граф объектов со связями, но не загружают значения атрибутов.Вы можете думать о них как о «призрачных» объектах.Они будут регистрироваться как экземпляры NSManagedObject или частного _NSFault ... класса.Если это NSManagedObject, все атрибуты пусты.Когда ошибка «срабатывает» или «ошибается», объект-заполнитель заменяется полностью заполненным экземпляром NSManagedObject, атрибуты которого можно прочитать.

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

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

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

В вашем случае вы хотите использовать временное свойство для grid, если значение grid зависит от значений любых смоделированных свойств класса Board.Это единственный способ гарантировать принудительно заставить Базовые данные гарантировать, что grid всегда будет заполняться при обращении к нему.

[Редактировать: Последнее очень теоретическое.Использование переходного свойства гарантирует, что Core Data отслеживает свойство, так что доступ к свойству вызовет сбой и предоставит данные.Однако на практике доступ к любому смоделированному свойству надежно вызовет ошибку, и немоделируемые методы всегда доступны (см. Ниже).

Вы также можете использовать:

+[NSManagedObject contextShouldIgnoreUnmodeledPropertyChanges:]

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

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

Обновление:

Хорошо, но что, если у меня есть экземплярный метод, который не является средством доступа к свойству, как doSomething выше?Как мне убедиться, что у меня есть реальный объект, прежде чем я его назову?

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

Core Data решает все эти проблемы за вас. Я использовал Core Data до тех пор, пока были Core Data, и у меня никогда не было проблем. Базовые данные были бы бесполезны, если бы вам приходилось постоянно останавливаться и проверять, были ли объекты неисправны или нет.

Например, я настроил простую модель с такими классами:

Alpha:

@class Beta;

@interface Alpha : NSManagedObject {
@private
}
@property (nonatomic, retain) NSNumber * num;
@property (nonatomic, retain) NSString * aString;
@property (nonatomic, retain) NSSet *betas;

-(NSString *) unmodeledMethod;
@end

@interface Alpha (CoreDataGeneratedAccessors)

- (void)addBetasObject:(Beta *)value;
- (void)removeBetasObject:(Beta *)value;
- (void)addBetas:(NSSet *)values;
- (void)removeBetas:(NSSet *)values;

@end 

@implementation Alpha
@dynamic num;
@dynamic aString;
@dynamic betas;

-(NSString *) unmodeledMethod{
  return @"Alpha class unmodeledMethod return value";
}
@end

Бета:

@class Alpha;

@interface Beta : NSManagedObject {
@private
}
@property (nonatomic, retain) NSNumber * num;
@property (nonatomic, retain) NSSet *alphas;
-(NSString *) unmodeledMethod;
-(NSString *) accessModeledProperty;

@end

@interface Beta (CoreDataGeneratedAccessors)

- (void)addAlphasObject:(Alpha *)value;
- (void)removeAlphasObject:(Alpha *)value;
- (void)addAlphas:(NSSet *)values;
- (void)removeAlphas:(NSSet *)values;

@end
@implementation Beta
@dynamic num;
@dynamic alphas;

-(NSString *) unmodeledMethod{
  return [NSString stringWithFormat:@"%@ isFault=%@", self, [self isFault] ? @"YES":@"NO"];
}

-(NSString *) accessModeledProperty{
  return [NSString stringWithFormat:@"\n isFault =%@ \n access numValue=%@ \n isFault=%@", [self isFault] ? @"YES":@"NO", self.num,[self isFault] ? @"YES":@"NO"];

}
@end

Затем я создал граф объектов из Alpha объекта со связанным Beta объектом. Затем я перезапустил приложение и запустил выборку всех Alpha объектов. Тогда я записал следующее:

id aa=[fetchedObjects objectAtIndex:0];
id bb=[[aa valueForKey:@"betas"] anyObject];

NSLog(@"aa isFault= %@",[aa isFault] ? @"YES":@"NO");
//=> aa isFault= NO

NSLog(@"\naa = %@",aa);
//=> aa = <Alpha: 0x63431b0> (entity: Alpha; id: 0x6342780 <x-coredata://752A19D9-2177-45A9-9722-61A40973B1BC/Alpha/p1> ; data: {
//=>  aString = "name 2";
//=>  betas =     (
//=>      "0x63454c0 <x-coredata://752A19D9-2177-45A9-9722-61A40973B1BC/Beta/p7>"
//=>  );
//=>  // ignore fetchedProperty = "<relationship fault: 0x6153300 'fetchedProperty'>";
//=>  num = 0;
//=> })

NSLog(@"\nbb isFault= %@",[bb isFault] ? @"YES":@"NO");
//=> bb isFault= YES

NSLog(@"\nany beta = %@",[[bb  class] description]);
//=> any beta = Beta

NSLog(@"\n-[Beta unmodeledMethod] =\n \n %@",[bb unmodeledMethod]);
//=> -[Beta unmodeledMethod] =
//=>  <Beta: 0x639de70> (entity: Beta; id: 0x639dbf0 <x-coredata://752A19D9-2177-45A9-9722-61A40973B1BC/Beta/p7> ; ...
//=>...data: <fault>) isFault=YES

NSLog(@"\n-[Beta accessModeledProperty] = \n %@",[bb accessModeledProperty]);
-[Beta accessModeledProperty] = 
//=> isFault =NO 
//=> access numValue=2 
//=> isFault=YES

NSLog(@"\nbb = %@",bb);
//=>bb = <Beta: 0x6029a80> (entity: Beta; id: 0x6029460 <x-coredata://752A19D9-2177-45A9-9722-61A40973B1BC/Beta/p7> ; data: {
//=>    alphas = "<relationship fault: 0x60290f0 'alphas'>";
//=>    num = 2;
//=>}) 

Обратите внимание, что:

  1. И aa, и bb установлены в ожидаемый класс, хотя я и сделал общее назначение объекта. Контекст гарантирует, что выборка возвращает правильный класс.
  2. Даже класс bb равен Beta, он сообщает о сбое, означающем, что объект представляет экземпляр класса Beta, но ни одно из его смоделированных свойств не заполнено.
  3. Объект bb реагирует на селектор unmodeledMethod, хотя в методе он все еще сообщает о сбое.
  4. Доступ к смоделированному свойству Beta.num преобразует bb из ошибки даже до того, как вызов сделан (компилятор устанавливает его на запуск), но как только доступ сделан, он возвращается к ошибке.
  5. Объекты в отношениях - это не только ошибки, но не те же самые объекты, которые возвращаются при доступе к отношениям. В Alpha.betas объект Beta имеет адрес 0x63454c0, тогда как bb имеет адрес 0x639de70>, хотя это ошибка. После того, как он преобразуется из ошибки, а затем снова возвращается, это адрес 0x6029a80. Тем не менее, managedObjectID всех трех объектов одинаковы.

Мораль здесь:

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

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

Если у вас есть сомнения, просто проверьте себя на наличие ошибок, чтобы убедиться, что ваш класс работает.

...