UIView с подклассами из NIB-файла не введен в подкласс - PullRequest
4 голосов
/ 05 апреля 2011

У меня есть файл NIB, с некоторым классом (SomeClassView: UIView), установленным в качестве пользовательского класса представления верхнего уровня NIB. SomeClass имеет IBOutlets, которые я использую для подключения подпредставлений SomeClass, которые я выкладываю в Интерфейсном Разработчике.

Я создаю экземпляр SomeClass следующим образом:

- (id)initWithFrame:(CGRect)frame {
    self = [[[[NSBundle mainBundle] loadNibNamed:@"SomeClassView" owner:nil options:nil] objectAtIndex:0] retain]; 
    // "SomeClassView" is also the name of the nib
    if (self != nil) {
        self.frame = frame;
    }
    return self;
}

Теперь скажите, что я подкласс SomeClass с SubClassView. Я добавляю метод в SubClassView с именем - (void) foo:

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self != nil) {
        [self foo];
    }
    return self;
}

В этот момент я получаю ошибку времени выполнения: * Завершение приложения из-за необработанного исключения «NSInvalidArgumentException», причина: '- [SomeClassView foo:]: нераспознанный селектор, отправленный экземпляру 0xAAAAAA'

Кажется, что "self" в initWithFrame SubClassView по-прежнему установлено в супер, SomeClassView. Чтобы быстро обойти эту проблему, нужно изменить указатель isa в SubClassView initWithFrame:

- (id)initWithFrame:(CGRect)frame {
    Class prevClass = [self class];
    self = [super initWithFrame:frame];
    if (self != nil) {
        isa = prevClass;
        [self foo]; // works now
    }
 }

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

1) В идеале, есть ли простой способ исправить это, просто с помощью кода? Может быть, с тем, как я устанавливаю self = загруженный кончик?

2) Есть ли альтернативная архитектура, которая лучше работает для того, что я пытаюсь сделать? Одним из примеров является установка владельца на себя, но тогда вам нужно будет вручную установить все сопоставления свойств / розеток.

Ответы [ 2 ]

3 голосов
/ 06 апреля 2011

Замена указателей isa является проблемой, если ваши подклассы имеют переменные экземпляра, отличные от объявленных в SomeClassView.Обратите внимание, что у вашего файла пера есть объект типа SomeClassView, что означает, что при загрузке файла пера загрузчик пера выделит объект этого типа и демонтирует его из файла пера.Изменение указателя isa на [SubViewClass class] временно не сделает его объектом типа SubViewClass как такового, поскольку то, что выделяет загрузчик пера, является SomeClassView объектом.

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

Что вы можете сделать, это сделать так, чтобы ваши SomeClassView объекты объявляли делегата, соответствующего некоторому протоколу.Этот протокол определит методы поведения в SomeClassView, которые потенциально могут быть расширены.Например,

@protocol SomeClassViewDelegate
@optional
- (void)someClassViewDidAwakeFromNib:(SomeClassView *)someClassView;
@end

Вместо подкласса SomeClassView у вас будут произвольные объекты, выполняющие любое пользовательское поведение, которое вы в настоящее время используете в SubClassView.Например, объект

@interface SubClassViewBehaviour : NSObject <SomeClassViewDelegate>
…
@end

@implementation SubClassViewBehaviour
- (void)someClassViewDidAwakeFromNib:(SomeClassView *)someClassView {
    // whatever behaviour is currently in -[SubClassView foo]
}
@end

A SubClassViewBehaviour будет создан в коде и назначен владельцем файла пера после загрузки файла пера или любого другого прокси-объекта IB в этом отношении.SomeClassView будет иметь выход делегата, связанный с объектом владельца / прокси, и будет вызывать методы делегата в соответствующих местах.Например,

@implementation SomeClassView
- (void)awakeFromNib {
    SEL didAwakeFromNib = @selector(someClassViewDidAwakeFromNib:);
    if ([[self delegate] respondsToSelector:didAwakeFromNib]) {
        [[self delegate] performSelector:didAwakeFromNib withObject:self];
    }
}
@end

Еще одно замечание: ваш код в настоящее время пропускает объект представления, поскольку создаются два объекта: один через +alloc в вашем коде, а другой через загрузку пера.Вы назначаете последний на self, следовательно, тот, который создан с помощью +alloc, протекает.Кроме того, я полагаю, что в третьем фрагменте кода вы пропустили вызов super.

0 голосов
/ 05 апреля 2011

Вместо того, чтобы делать это изнутри самого подкласса, почему бы не гарантировать, что это правильный класс, когда вы впервые создаете его из-за пределов класса:

SubClass *instance = [[SubClass alloc] initWithNibName:@"SomeClassView" bundle:nil];
...