【问题标题】:Multiple delegates per one object?每个对象有多个代表?
【发布时间】:2012-02-18 11:11:20
【问题描述】:

我有一个 UIScrollView 需要子类化,并且在子类中我需要附加 UIScrollViewDelegate 以便我可以实现 viewForZoomingInScrollView 方法。

然后我有一个UIViewController,我需要在其中实例化我创建的这个UIScrollView 子类的一个对象,我还想让 UIViewController 成为这个对象的UIScrollViewDelegate,这样我就可以实现scrollViewDidZoom在这个UIViewController 类中。

如何让一个对象有两个委托? (我知道我可以很容易地只拥有一个委托并在那里实现这两种方法,但出于设计目的,我想按照我提到的方式来做)。

【问题讨论】:

  • 你为什么要这样做?为什么不能只将来自 customScrollView 的消息传递给具有 SV(带有第二个(自定义)委托)的 VC?
  • 也许这就是我需要做的,所以如果我在我的 SV 子类中实现 scrollViewDidZoom,我怎么能在触发时向我的 VC 发送消息? @totumus maximus
  • 您必须在您的自定义视图中创建一个自定义委托(协议),并让您的 VC 委托它。在调用您的滚动视图委托的那一刻,您还调用了自定义委托中的自定义委托方法之一。这样,滚动视图保持自己的责任,并且您的父视图可以对特定的滚动功能做出反应。我会把它写成答案给你。

标签: iphone objective-c ios cocoa-touch delegates


【解决方案1】:

这是您尝试做的另一个潜在问题...

假设您有两个 UIScrollView 实例和一个委托对象。在委托对象中,你重写了 UIScrollViewDelegate 协议的 scrollViewDidScroll(UIScrollView *): 方法。

在方法内部,您希望访问两个滚动视图的 contentOffset 属性的值,因为您可能有两个相邻的集合视图,并且您正试图获取位于中心的项目的索引路径收集视图以获取与这两个项目关联的属性的值(想想 UIDatePicker)。

在这种情况下,滚动视图之间有何不同? scrollView 属性只引用一个滚动视图;但是,即使它引用了两者,您如何获取它们各自的 contentOffset 属性的值?

现在,您可能会说,“我可以为两者创建一个 IBOutlet,并使用它们分配的引用而不是委托方法中的 scrollView 属性,例如 self.collectionViewFirst.contentOffset 和 self.collectionViewSecond.contentOffset,并忽略委托方法的 scrollView 属性。

问题在于:该属性没有被存储。它仅在调用委托方法时可用。为什么?因为只有一个委托对象,并且只有一个 contentOffset 属性。通过滚动另一个滚动视图,contentOffset 属性的值会发生变化,并且不会反映除最后一个滚动视图之外的任何其他滚动视图的内容偏移量。

即使我描述的案例(或类似案例)不适用于您的情况,做您想做的事情也是不好的做法。请记住:编写代码是关于共享代码。不正确的代码会向其他人发送消息,从而降低您的声誉。

【讨论】:

    【解决方案2】:

    有时将多个委托附加到滚动视图是有意义的。在这种情况下,您可以构建一个简单的委托拆分器:

    // Public interface
    @interface CCDelegateSplitter : NSObject
    
    - (void) addDelegate: (id) delegate;
    - (void) addDelegates: (NSArray*) delegates;
    
    @end
    
    // Private interface
    @interface CCDelegateSplitter ()
    @property(strong) NSMutableSet *delegates;
    @end
    
    @implementation CCDelegateSplitter
    
    - (id) init
    {
        self = [super init];
        _delegates = [NSMutableSet set];
        return self;
    }
    
    - (void) addDelegate: (id) delegate
    {
        [_delegates addObject:delegate];
    }
    
    - (void) addDelegates: (NSArray*) delegates
    {
        [_delegates addObjectsFromArray:delegates];
    }
    
    - (void) forwardInvocation: (NSInvocation*) invocation
    {
        for (id delegate in _delegates) {
            [invocation invokeWithTarget:delegate];
        }
    }
    
    - (NSMethodSignature*) methodSignatureForSelector: (SEL) selector
    {
        NSMethodSignature *our = [super methodSignatureForSelector:selector];
        NSMethodSignature *delegated = [(NSObject *)[_delegates anyObject] methodSignatureForSelector:selector];
        return our ? our : delegated;
    }
    
    - (BOOL) respondsToSelector: (SEL) selector
    {
        return [[_delegates anyObject] respondsToSelector:selector];
    }
    
    @end
    

    然后只需将此拆分器的一个实例设置为滚动视图的委托,并将任意数量的委托附加到拆分器。他们都将收到代表团事件。有一些注意事项,例如所有的委托都被假定为相同的类型,否则你会遇到天真的 respondsToSelector 实现的麻烦。这不是什么大问题,很容易将实现更改为仅将委托事件发送给支持它们的人。

    【讨论】:

    • 应该声明为@property (assign) CCDelegateSplitter* delegates 吗?
    • 而不是 [NSMutableSet 集];使用 [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];因为它将持有对委托对象的弱引用,并将避免它们的保留周期
    • 不要使用这个解决方案,由于引用不能解除委托。使用 Nav 建议的内容、NSNotification 或其他内容。只是不是这个......
    • 或者选择[NSPointerArray weakObjectsPointerArray];,它可以保存弱指针并且不会让你陷入保持强引用的陷阱
    【解决方案3】:

    您不想要一个有 2 个委托的对象。您希望让您的 customScrollView 保持其自己的 UIScrollViewDelegate 函数的责任。

    要让您的 parentVC 也响应 UIScrollView 的委托方法,您必须在您的 customScrollView 中创建一个自定义委托。

    在调用 UIScrollViewDelegate 函数的那一刻,您还将从自定义委托中调用您的委托函数之一。这样,您的 parentVC 将在您希望的时候做出响应。

    看起来有点像这样。

    CustomScrollView.h

    @protocol CustomDelegate <NSObject>
    
    //custom delegate methods
    -(void)myCustomDelegateMethod;
    
    @end
    
    @interface CustomScrollView : UIScrollView <UIScrollViewDelegate>
    {
        id<CustomDelegate> delegate
        //the rest of the stuff
    

    CustomScrollView.m

    -(void) viewForZoomingInScrollView
    {
        [self.delegate myCustomDelegateMethod];
        //rest of viewForZoomingInScrollView code
    

    ParentVC.h

    @interface CustomScrollView : UIViewController <CustomDelegate>
    {
        //stuff
    

    ParentVC.m

    -(void)makeCustomScrollView
    {
         CustomScrollView *csv = [[CustomScrollView alloc] init];
         csv.delegate = self;
         //other stuff
    
    }
    
    -(void)myCustomDelegateMethod
    {
       //respond to viewForZoomingInScrollView
    }
    

    我希望这可以完全解决您的问题。 祝你好运。

    【讨论】:

    • @TotumusMaximus 一个ScrollView已经有一个名为“delegate”的属性,这里你尝试声明一个同名的属性,这是不好的
    【解决方案4】:

    简短的回答:你不知道。代表通常是弱的一对一关系:

    @property (nonatomic, weak /*or assign*/) id<MyViewDelegate> delegate;
    

    有时您会看到“监听器”设计模式,即委托的一对多形式:

    - (void) addListener:(id<MyViewListener>)listener;
    - (void) removeListener:(id<MyViewListener>)listener;
    

    在您的情况下,UIScrollView 中似乎没有一个很好的公共覆盖点,它允许子类指定 viewForZoomingInScrollView。如果可能的话,我会避免让 UIScrollView 成为自己的代表。您可以将 UIViewController 设为 UIScrollViewDelegate 并让它提供 viewForZooming。或者您可以创建一个使用 UIScrollView 的中间视图子类,提供 viewForZooming,并向上转发其他委托方法。

    【讨论】:

      【解决方案5】:

      我认为你不能让两个 UIScrollViewDelegate 代表直接连接到同一个对象。

      您可以做的是让两个代表链式连接。即,您将一个代表连接到另一个代表,然后在前者无法直接处理消息时让后者将消息转发给后者。

      在任何情况下,我认为我缺少一些完整的解决方案建议,即为什么您确实需要第二个代表并且不能总是通过一个代表来完成。换句话说,我认为可能有替代设计可以避免需要两个代表。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-05-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-04-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多