【发布时间】:2012-04-16 05:20:31
【问题描述】:
这是我想要实现的目标:
我想子类化 UIScrollView 以获得额外的功能。这个子类应该能够对滚动做出反应,所以我必须将委托属性设置为 self 以接收如下事件:
- (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
这样,当继承的滚动视图结束滚动时,我的子类可以做一些特殊的事情,但仍会通知事件的外部委托。到目前为止有效。但由于我想让这个子类对其他开发人员可用,我想限制对基类委托属性的访问,因为它应该只由子类使用。我认为其他开发人员很可能直观地使用了基类的委托属性,即使我在头文件中评论了问题。如果有人改变了委托属性,子类将不会做它应该做的事情,我现在不能做任何事情来阻止它。这就是我不知道如何解决它的地方。
我尝试覆盖委托属性以使其只读,如下所示:
@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'
好吧,这是个坏主意,因为我在这里显然违反了 liskovs 替换原则。
下一次尝试 --> 尝试像这样覆盖委托设置器:
...
- (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 指令将 _delegate ivar 的访问权限限制为只能由框架本身访问。因此,当我想设置 _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还没有设置,指向nil,所以返回值是NO!这似乎导致 scrollViewDidScroll: 方法之后不会被触发。它看起来像是一个预检查该方法是否被实现,如果它失败了,这个方法根本不会被触发,即使我们之后设置了我们的 userDelegate 属性。我不知道为什么会这样,因为大多数其他委托方法都没有这个预检查。
所以我的解决方案是,在 PrivateDelegate setDelegate 方法中调用 [super setDelegate...] 方法,因为这是我很确定我的 userDelegate 方法已设置的地方。
所以我最终会得到这个实现 sn-p:
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 方法,但它目前可以满足我的需求,虽然感觉很hacky:/
如果有人知道如何改进它,我将不胜感激。
感谢@rob 你的例子!
【问题讨论】:
-
如果代理响应
scrollViewDidScroll:,似乎滚动视图缓存,可能是出于性能原因。要解决您必须设置用户委托的问题,请像以前一样在init中设置它,在setDelegate中您可以执行super.delegate = nil然后super.delegate = self.internalDelegate强制它重新检查。
标签: objective-c ios delegates uiscrollview subclass