Вот чего я хочу достичь:
Я хочу, чтобы подкласс UIScrollView имел дополнительную функциональность. Этот подкласс должен иметь возможность реагировать на прокрутку, поэтому мне нужно настроить свойство делегата на себя для получения таких событий, как:
- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView { ... }
С другой стороны, другие классы также должны иметь возможность получать эти события, например, они использовали базовый класс UIScrollView.
Итак, у меня были разные идеи, как решить эту проблему, но все это не совсем меня удовлетворяет: (
Мой основной подход - использование собственного свойства делегирования следующим образом:
@interface MySubclass : UIScrollView<UIScrollViewDelegate>
@property (nonatomic, assign) id<UIScrollViewDelegate> myOwnDelegate;
@end
@implementation MySubclass
@synthesize myOwnDelegate;
- (id) initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.delegate = self;
}
return self;
}
// Example event
- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// Do something custom here and after that pass the event to myDelegate
...
[self.myOwnDelegate scrollViewDidEndDecelerating:(UIScrollView*)scrollView];
}
@end
Таким образом, мой подкласс может сделать что-то особенное, когда унаследованная прокрутка прокрутки заканчивается, но все же информирует внешнего делегата о событии. Это работает до сих пор. Но поскольку я хочу сделать этот подкласс доступным для других разработчиков, я хочу ограничить доступ к свойству делегата базового класса, поскольку он должен использоваться только подклассом. Я думаю, что наиболее вероятно, что другие разработчики интуитивно используют свойство делегирования базового класса, даже если я прокомментирую проблему в файле заголовка. Если кто-то изменяет свойство делегата, подкласс не будет делать то, что он должен делать, и я не могу ничего сделать, чтобы это не произошло прямо сейчас. И тот момент, когда я не знаю, как его решить.
То, что я пробовал, пытается переопределить свойство delegate, чтобы сделать его только для чтения следующим образом:
@interface MySubclass : UIScrollView<UIScrollViewDelegate>
...
@property (nonatomic, assign, readonly) id<UIScrollViewDelegate>delegate;
@end
@implementation MySubclass
@property (nonatomic, assign, readwrite) id<UIScrollViewDelegate>delegate;
@end
Это приведет к предупреждению
"Attribute 'readonly' of property 'delegate' restricts attribute 'readwrite' of property inherited from 'UIScrollView'
Хорошо, плохая идея, поскольку я, очевидно, нарушаю принцип замены Лискова.
Следующая попытка → Попытка переопределить набор делегатов следующим образом:
...
- (void) setDelegate(id<UIScrollViewDelegate>)newDelegate {
if (newDelegate != self) self.myOwnDelegate = newDelegate;
else _delegate = newDelegate; // <--- This does not work!
}
...
Как прокомментировано, этот пример не компилируется, поскольку кажется, что _delegate ivar не был найден?! Поэтому я просмотрел файл заголовка UIScrollView и нашел это:
@package
...
id _delegate;
...
Директива @package ограничивает доступ к ivarate для доступа только к самой структуре. Поэтому, когда я хочу установить _delegate ivar, я должен использовать синтезированный сеттер. Я не вижу способа переопределить его каким-либо образом: (Но я не могу поверить, что вокруг этого не обойтись, может быть, я не вижу дерева для деревьев.
Я ценю любой намек на решение этой проблемы.
Решение:
Теперь он работает с решением @rob mayoff. Как я заметил ниже, возникла проблема с вызовом scrollViewDidScroll:. Я наконец выяснил, в чем проблема, даже я не понимаю, почему это так:/
В тот момент, когда мы устанавливаем супер-делегат:
- (id) initWithFrame:(CGRect)frame {
...
_myDelegate = [[[MyPrivateDelegate alloc] init] autorelease];
[super setDelegate:_myDelegate]; <-- Callback is invoked here
}
есть обратный вызов _myDelegate. Отладчик разбивается на
- (BOOL) respondsToSelector:(SEL)aSelector {
return [self.userDelegate respondsToSelector:aSelector];
}
с параметром "scrollViewDidScroll:" в качестве аргумента.
Забавно, что в это время self.userDelegate еще не установлен и указывает на ноль, поэтому возвращаемое значение НЕТ! Вероятно, это приведет к тому, что методы scrollViewDidScroll: далее не будут запущены. Это похоже на предварительный запрос, если этот метод реализован, и если он не работает, этот метод вообще не будет запущен, даже если после этого мы установим свойство userDelegate. Я не знаю, почему это так, поскольку большинство других методов делегата не имеют этого предварительного запроса.
Итак, мое решение для этого - вызвать метод [super setDelegate...] в методе setDelegate PrivateDelegate, так как это место, я уверен, что мой метод userDelegate установлен.
Итак, я вернусь к этому фрагменту реализации:
MyScrollViewSubclass.m
- (void) setDelegate:(id<UIScrollViewDelegate>)delegate {
self.internalDelegate.userDelegate = delegate;
super.delegate = self.internalDelegate;
}
- (id) initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.internalDelegate = [[[MyScrollViewPrivateDelegate alloc] init] autorelease];
// Don't set it here anymore
}
return self;
}
Остальная часть кода остается нетронутой. Я все еще не очень доволен этим обходным решением, потому что он требует хотя бы один раз вызвать метод setDelegate, но он работает для моих нужд на данный момент, хотя он чувствует себя очень взломанным:/
Если у кого-то есть идеи, как улучшить это, я был бы признателен.
Спасибо @rob за ваш пример!