Нужна некоторая помощь в понимании переходных свойств в 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

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

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

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

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

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

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

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

  • grid getter называется
  • _grid выделено
  • Доступ к self.pieces
  • сбои в неисправности
  • awake называется
  • _grid = nil
  • возврат в сетку getter
  • [[_grid objectAtIndex:... значение доступа nil, сбой или, по крайней мере, no-op
  • grid getter возвращает nil
  • сбой или неправильное поведение, когда ожидается, что boardContents будет содержать значение

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

От 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 по ошибке?

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

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

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

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

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

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

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

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

Если это так, вы не собираетесь ставить логику приложения в свои пользовательские подклассы NSManagedObject? Если логика приложения находится в других классах, имеющих ссылки на управляемые объекты, а управляемые объекты - это только тупые объекты, от которых вы читаете и записываете (только немного умный, с некоторыми возможностями для обеспечения согласованности данных)? Является единственной точкой подклассификации NSManagedObject для выполнения некоторых "трюков" с нестандартными типами данных?

Ответы

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

+[NSManagedObject contextShouldIgnoreUnmodeledPropertyChanges:]

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

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

Обновление:

Хорошо, но что, если у меня есть метод экземпляра, который не является свойством Accessor, как 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;
//=>}) 

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

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

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

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

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

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