Пользовательские методы настройки в Core-Data

Мне нужно написать собственный метод setter для поля (мы будем называть его foo) в моем подклассе NSManagedObject. foo определяется в модели данных, и Xcode имеет автогенерированные поля @property и @dynamic в файлах .h и .m соответственно.

Если я напишу свой сеттер следующим образом:

- (void)setFoo: (NSObject *)inFoo {
    [super setFoo: inFoo];
    [self updateStuff];
}

то я получаю предупреждение компилятора при вызове super.

В качестве альтернативы, если я это сделаю:

- (void)setFoo: (NSObject *)inFoo {
    [super setValue: inFoo forKey: inFoo];
    [self updateStuff];
}

тогда я заканчиваю бесконечный цикл.

Итак, каков правильный подход к написанию пользовательского установщика для подкласса NSManagedObject?

Ответ 1

Согласно документации, это будет:

- (void) setFoo:(NSObject *)inFoo {
  [self willChangeValueForKey:@"foo"];
  [self setPrimitiveValue:inFoo forKey:@"foo"];
  [self didChangeValueForKey:@"foo"];
}

Это, конечно, игнорирует тот факт, что NSManagedObjects требуется только NSNumbers, NSDates, NSDatas и NSStrings в качестве атрибутов.

Однако это может быть не самый лучший подход. Поскольку вы хотите, чтобы что-то произошло, когда изменилось значение вашего свойства foo, почему бы просто не наблюдать его с помощью Key Value Observing? В этом случае это звучит как "KVO - путь".

Ответ 2

Вот как я делаю KVO в атрибуте id для Photo : NSManagedObject. Если идентификатор фотографии изменяется, загрузите новую фотографию.

#pragma mark NSManagedObject

- (void)awakeFromInsert {
    [self observePhotoId];
}

- (void)awakeFromFetch {
    [self observePhotoId];
}

- (void)observePhotoId {
    [self addObserver:self forKeyPath:@"id"
              options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change
                       context:(void *)context {
    if ([keyPath isEqualToString:@"id"]) {
        NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
        NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];        
        if (![newValue isEqualToString:oldValue]) {
            [self handleIdChange];
        }
    }
}

- (void)willTurnIntoFault {
    [self removeObserver:self forKeyPath:@"id"];
}

#pragma mark Photo

- (void)handleIdChange {
    // Implemented by subclasses, but defined here to hide warnings.
    // [self download]; // example implementation
}

Ответ 3

Я думаю, что есть небольшая ошибка: использовать

 [self setPrimitiveValue:inFoo forKey:@"foo"];

вместо

 [self setPrimitiveFoo:inFoo];

это работает для меня.

Ответ 4

Вот как вы это делаете 1-n (и я предполагаю n-m) отношения:

Предположим, что имя отношения называется "ученики" в объекте под названием "Школа".

Сначала вам нужно определить примитивные методы доступа для NSMutableSet. Xcode не будет автоматически генерировать их для вас.

@interface School(PrimitiveAccessors)
- (NSMutableSet *)primitiveStudents;
@end

Далее вы можете определить свой метод доступа. Здесь я буду переопределять сеттер.

- (void)addStudentsObject:(Student *)student
{
  NSSet *changedObjects = [[NSSet alloc] initWithObjects:&student count:1];

  [self willChangeValueForKey:@"students"
              withSetMutation:NSKeyValueUnionSetMutation
                 usingObjects:changedObjects];

  [[self primitiveStudents] addObject:value];

  [self didChangeValueForKey:@"students"
             withSetMutation:NSKeyValueUnionSetMutation
                usingObjects:changedObjects];
}