如何inheritanceUIScrollView并使委托属性保密

这是我想要达到的:

我想要一个UIScrollView子类有更多的function。 这个子类应该能够在滚动上作出反应,所以我必须将委托属性设置为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 

这样,当inheritance的滚动视图结束滚动时,我的子类可以做一些特殊的事情,但仍然通知事件的外部代表。 这工作到目前为止。 但是,因为我想让这个子类可供其他开发者使用,所以我想限制对基类委托属性的访问,因为它只能被子类使用。 我认为最有可能的是,其他开发人员直观地使用基类的委托属性,即使我在头文件中评论问题。 如果有人改变委托属性,那么子类将不会做它应该做的事情,我现在不能做任何事情来阻止它。 这就是我不知道如何解决这个问题。

我所尝试的是试图覆盖委托属性,使其只读取像这样:

 @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伊娃没有被发现? 所以我查了一下UIScrollView的头文件,发现这个:

 @package ... id _delegate; ... 

@package指令限制_delegate ivar的访问只能由框架本身访问。 所以当我想要设置_delegate ivar我必须使用合成的setter。 我不能以任何方式看到一种方式来覆盖它:(但我不能相信,有没有办法解决这个问题,也许我不能看到树的木材。

我很感激解决这个问题的任何提示。


解:

它现在与@rob mayoff的解决scheme。 正如我在下面评论说,scrollViewDidScroll:call有问题。 我终于明白,问题是什么,即使我不明白为什么这样:/

就在我们设置超级代表的那一刻,

 - (id) initWithFrame:(CGRect)frame { ... _myDelegate = [[[MyPrivateDelegate alloc] init] autorelease]; [super setDelegate:_myDelegate]; <-- Callback is invoked here } 

有一个_myDelegate的callback。 debugging器中断

 - (BOOL) respondsToSelector:(SEL)aSelector { return [self.userDelegate respondsToSelector:aSelector]; } 

以“scrollViewDidScroll:”select器作为参数。

有趣的事情在这个时候self.userDelegate尚未设置,并指向零,所以返回值是NO! 这似乎导致scrollViewDidScroll:方法不会被解雇。 它看起来像预先检查,如果方法被实现,如果失败,这个方法不会被解雇,即使我们之后设置了我们的userDelegate属性。 我不知道这是为什么,因为大多数其他代表方法没有这个预先检查。

所以我的解决scheme是,调用PrivateDelegate setDelegate方法中的[super setDelegate …]方法,因为这是我确定我的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; } 

其余的代码保持不变。 我仍然不是很满意这个解决方法,因为它至less需要调用setDelegate方法一次,但它适用于我目前的需求,虽然感觉很不好意思:/

如果有人有想法如何改善,我会很感激。

感谢@rob为你的榜样!

使MySubclass成为自己的MySubclass有一个问题。 据推测,你不想为所有UIScrollViewDelegate方法运行自定义代码,但是你必须将消息转发给用户提供的委托,无论你是否有自己的实现。 所以你可以尝试实现所有的委托方法,其中大部分只是像这样转发:

 - (void)scrollViewDidZoom:(UIScrollView *)scrollView { [self.myOwnDelegate scrollViewDidZoom:scrollView]; } 

这里的问题是,有时iOS的新版本添加新的委托方法。 例如,iOS 5.0添加了scrollViewWillEndDragging:withVelocity:targetContentOffset: 所以你的scrollview子类将不会是未来的certificate。

处理这个问题的最好方法是创build一个单独的私有对象,它只是作为您的滚动视图的委托,并处理转发。 这个专用委托对象可以将收到的每个消息转发给用户提供的委托,因为它只接收委托消息。

这是你做的。 在你的头文件中,你只需要为你的scrollview子类声明接口。 你不需要公开任何新的方法或属性,所以它看起来像这样:

MyScrollView.h

 @interface MyScrollView : UIScrollView @end 

所有真正的工作都在.m文件中完成。 首先,我们定义私人委托类的接口。 它的工作是callbackMyScrollView中的一些委托方法,并将所有消息转发给用户的委托。 所以我们只想给它的方法是UIScrollViewDelegate一部分。 我们不希望它有额外的方法来pipe理对用户委托的引用,所以我们只保留该引用作为实例variables:

MyScrollView.m

 @interface MyScrollViewPrivateDelegate : NSObject <UIScrollViewDelegate> { @public id<UIScrollViewDelegate> _userDelegate; } @end 

接下来我们将实现MyScrollView 。 它需要创build一个MyScrollViewPrivateDelegate实例,它需要拥有它。 由于UIScrollView不拥有它的委托,我们需要额外的,强大的引用这个对象。

 @implementation MyScrollView { MyScrollViewPrivateDelegate *_myDelegate; } - (void)initDelegate { _myDelegate = [[MyScrollViewPrivateDelegate alloc] init]; [_myDelegate retain]; // remove if using ARC [super setDelegate:_myDelegate]; } - (id)initWithFrame:(CGRect)frame { if (!(self = [super initWithFrame:frame])) return nil; [self initDelegate]; return self; } - (id)initWithCoder:(NSCoder *)aDecoder { if (!(self = [super initWithCoder:aDecoder])) return nil; [self initDelegate]; return self; } - (void)dealloc { // Omit this if using ARC [_myDelegate release]; [super dealloc]; } 

我们需要重写setDelegate:delegate:存储并返回对用户委托的引用:

 - (void)setDelegate:(id<UIScrollViewDelegate>)delegate { _myDelegate->_userDelegate = delegate; // Scroll view delegate caches whether the delegate responds to some of the delegate // methods, so we need to force it to re-evaluate if the delegate responds to them super.delegate = nil; super.delegate = (id)_myDelegate; } - (id<UIScrollViewDelegate>)delegate { return _myDelegate->_userDelegate; } 

我们还需要定义我们的私人代理可能需要使用的任何额外的方法:

 - (void)myScrollViewDidEndDecelerating { // do whatever you want here } @end 

现在我们可以最终定义MyScrollViewPrivateDelegate的实现。 我们需要明确定义每个应该包含我们的私有自定义代码的方法。 该方法需要执行我们的自定义代码,并将消息转发给用户的委托,如果用户的委托响应消息:

 @implementation MyScrollViewPrivateDelegate - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [(MyScrollView *)scrollView myScrollViewDidEndDecelerating]; if ([_userDelegate respondsToSelector:_cmd]) { [_userDelegate scrollViewDidEndDecelerating:scrollView]; } } 

而且我们需要处理所有其他我们没有自定义代码的UIScrollViewDelegate方法,以及所有这些将在未来版本的iOS中添加的消息。 我们必须实施两种方法来实现这一点:

 - (BOOL)respondsToSelector:(SEL)selector { return [_userDelegate respondsToSelector:selector] || [super respondsToSelector:selector]; } - (void)forwardInvocation:(NSInvocation *)invocation { // This should only ever be called from `UIScrollView`, after it has verified // that `_userDelegate` responds to the selector by sending me // `respondsToSelector:`. So I don't need to check again here. [invocation invokeWithTarget:_userDelegate]; } @end 

感谢@robmayoff我封装成一个更通用的代理拦截器:有原始的MessageInterceptor类:

MessageInterceptor.h

 @interface MessageInterceptor : NSObject @property (nonatomic, assign) id receiver; @property (nonatomic, assign) id middleMan; @end 

MessageInterceptor.m

 @implementation MessageInterceptor - (id)forwardingTargetForSelector:(SEL)aSelector { if ([self.middleMan respondsToSelector:aSelector]) { return self.middleMan; } if ([self.receiver respondsToSelector:aSelector]) { return self.receiver; } return [super forwardingTargetForSelector:aSelector]; } - (BOOL)respondsToSelector:(SEL)aSelector { if ([self.middleMan respondsToSelector:aSelector]) { return YES; } if ([self.receiver respondsToSelector:aSelector]) { return YES; } return [super respondsToSelector:aSelector]; } @end 

它在你的generics委托类中使用:

GenericDelegate.h

 @interface GenericDelegate : NSObject @property (nonatomic, strong) MessageInterceptor * delegate_interceptor; - (id)initWithDelegate:(id)delegate; @end 

GenericDelegate.m

 @implementation GenericDelegate - (id)initWithDelegate:(id)delegate { self = [super init]; if (self) { self.delegate_interceptor = [[MessageInterceptor alloc] init]; [self.delegate_interceptor setMiddleMan:self]; [self.delegate_interceptor setReceiver:delegate]; } return self; } // delegate methods I wanna override: - (void)scrollViewDidScroll:(UIScrollView *)scrollView { // 1. your custom code goes here NSLog(@"Intercepting scrollViewDidScroll: %f %f", scrollView.contentOffset.x, scrollView.contentOffset.y); // 2. forward to the delegate as usual if ([self.delegate_interceptor.receiver respondsToSelector:@selector(scrollViewDidScroll:)]) { [self.delegate_interceptor.receiver scrollViewDidScroll:scrollView]; } } //other delegate functions you want to intercept ... 

所以我可以拦截任何我需要的任何代表UITableView,UICollectionView,UIScrollView …:

 @property (strong, nonatomic) GenericDelegate *genericDelegate; @property (nonatomic, strong) UICollectionView* collectionView; //intercepting delegate in order to add separator line functionality on this scrollView self.genericDelegate = [[GenericDelegate alloc]initWithDelegate:self]; self.collectionView.delegate = (id)self.genericDelegate.delegate_interceptor; - (void)scrollViewDidScroll:(UIScrollView *)scrollView { NSLog(@"Original scrollViewDidScroll: %f %f", scrollView.contentOffset.x, scrollView.contentOffset.y); } 

在这种情况下,UICollectionViewDelegate scrollViewDidScroll:函数将在我们的GenericDelegate(带有任何我们想要添加的代码)上执行,并在我们自己的类

这是我的5美分,感谢@robmayoff和@jhabbott以前的答案

另一种select是inheritance和使用抽象函数的力量。 例如,你创build

 @interface EnhancedTableViewController : UITableViewController <UITableViewDelegate> 

在那里你重载一些委托方法,并定义你的抽象函数

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // code before interception [self bfTableView:(UITableView *)tableView didSelectRowAtIndexPath:indexPath]; // code after interception } - (void)bfTableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {}; 

现在,您只需要inheritanceEnhancedTableViewController并使用抽象函数代替委托对象。 喜欢这个:

 @interface MyTableViewController : EnhancedTableViewController ... - (void)bfTableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { //overriden implementation with pre/post actions }; 

让我知道这里是否有什么问题。

不要inheritance它,而是封装它;)

创build一个新的UIView子类 – 你的头文件看起来像这样:

 @interface MySubclass : UIView @end 

只要有一个UIScrollView作为子视图 – 你的.m文件应该是这样的:

 @interface MySubclass () <UIScrollViewDelegate> @property (nonatomic, strong, readonly) UIScrollView *scrollView; @end @implementation MySubClass @synthesize scrollView = _scrollView; - (UIScrollView *)scrollView { if (nil == _scrollView) { _scrollView = [UIScrollView alloc] initWithFrame:self.bounds]; _scrollView.delegate = self; [self addSubView:_scrollView]; } return _scrollView; } ... your code here ... @end 

在你的子类中,只需要使用self.scrollView ,它会在你第一次请求的时候创build滚动视图。

这有利于隐藏滚动视图从任何人使用你的MySubClass – 如果你需要改变它的幕后工作(即从滚动视图更改为网页视图),这将是非常容易的:)

这也意味着没有人可以改变你想滚动视图的行为:)

PS我已经假设ARC – 更改strong retain并添加dealloc如果需要的话:)


编辑

如果你想让你的类像UIScrollView一样运行,那么你可以尝试添加这个方法(从这些文档中获取,并且未经testing!):

 - (void)forwardInvocation:(NSInvocation *)anInvocation { if ([self.scrollView respondsToSelector:anInvocation.selector]) [anInvocation invokeWithTarget:self.scrollView]; else [super forwardInvocation:anInvocation]; } 

对于那些想用UIControl类来做这种事情的人,看看我的STAControls项目 (特别是像这样的 Base类)。 我用Rob Mayoff的答案实现了项目的代理转发。