Нужна помощь в понимании переходных свойств в Core Data

Я прочитал документацию по переходным свойствам, но я не могу понять их назначение. Может кто-нибудь сказать мне разницу между наличием и отсутствием переходного свойства, если у меня есть такой подкласс 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

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

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

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

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

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

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

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

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

С другой стороны, может быть, если я объявлю grid как временное свойство, то ошибка сработает до того, как будет вызван мой метод get 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 ответ

Решение

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

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

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

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

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

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

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

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

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

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

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

+[NSManagedObject contextShouldIgnoreUnmodeledPropertyChanges:]

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

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

Обновить:

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

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

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

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

Альфа:

@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… классы. С точки зрения программистов, все эти разные объекты взаимозаменяемы.
  • Даже если управляемый объект сообщает о сбое, он все равно будет реагировать на немоделированные селекторы.
  • Доступ к любому смоделированному свойству приводит к возникновению ошибки и объект становится полностью активным.
  • Базовые данные часто меняются объектами за кулисами, которые вы не можете контролировать и не должны беспокоиться.

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

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

Другие вопросы по тегам