Вызов `super` в категории Objective-C.

#objective-c #categories #superclass #super

#objective-c #Категории #суперкласс #супер

Вопрос:

У меня есть вызываемый класс Animal , который является подклассом BaseEntity . У меня есть протокол CoreDataConversions , который называется как Animal и BaseEntity категории, которые соответствуют. В протоколе у меня определен метод:

- (instancetype)initWithManagedObject:(NSManagedObject *)managedObject dataManager:(id<DataManager>)dataManager

BaseEntity категория прекрасно реализует это. Затем, потому Animal что это подкласс BaseEntity , к которому я обращаюсь [super initWithManagedObject:managedObject dataManager:dataManager]; в категории animal.

Я получаю сбой с сообщением:

[Animal setDataManager:] нераспознанный селектор отправлен экземпляру…

Я установил точку останова при вызове super и вышел из системы следующим образом:

 po [self class] // Prints Animal

po [self superclass] // Prints BaseEntity

po [super class] // Prints Animal

po [super superclass] // Prints BaseEntity
 

Итак: почему Animal категория super выполняет вызовы Animal , хотя вызовы superclass явно ссылаются на BaseEntity ?

Редактировать:

Вот код, который вызывает сбой:

 - (id)insertEntityForClass:(Class)class
{
    if (![class conformsToProtocol:@protocol(CoreDataConversions)]) {
        return nil;
    }

    // A class method defined in CoreDataConversions
    NSString *entityName = [class coreDataEntityName];

    // Insert core data entity
    NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:self.currentMainContext];

    // Init with the managed object
    id entity = [[class alloc] initWithManagedObject:managedObject dataManager:self];

    return entity;
}
 

У меня есть тест, который просто вызывает Animal *animal = [dataManager insertEntityForClass:[Animal class]]; , а затем подтверждает существование animal .

Animal реализует метод следующим образом:

 - (instancetype)initWithManagedObject:(NSManagedObject *)managedObject dataManager:(id<RHDataManager>)dataManager
{    
    self = [super initWithManagedObject:managedObject dataManager:dataManager];
    if (self) {
        // TODO
    }
    return self;
}
 

И BaseEntity реализует метод следующим образом:

 - (instancetype)initWithManagedObject:(id)managedObject dataManager:(id<RHDataManager>)dataManager
{
    self = [super init];
    if (self) {
        self.dataManager = dataManager;
    }
    return self;
}
 

Комментарии:

1. Можете ли вы опубликовать код вокруг сбоя, указав класс, в котором он находится. Просто чтобы прояснить вопрос.

2. @danh только что добавил код вокруг сбоя

3. Возможно, я слишком плотный, но я не вижу вызова setDataManager в опубликованном коде

4. Да, вы правы, оказывается, это связано с объявлениями свойств.

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

Ответ №1:

Оказывается, это на самом деле связано с объявлениями свойств. У меня были readonly свойства, т.Е. @property (nonatomic, strong, readonly) id<DataManager> dataManager; Определенные в исходном классе.

В категории я переопределил свойство следующим образом:

@property (nonatomic, strong) id<DataManager> dataManager;

Сбой был вызван тем, что для параметра не был установлен Animal параметр dataManager , поскольку он все еще считывал его как readonly .