【问题标题】:Cascades of delegates and hijacking delegate callbacks in Objective-CObjective-C 中的委托级联和劫持委托回调
【发布时间】:2014-11-16 21:40:21
【问题描述】:

假设我编写了一个 UITextField 子类并希望控制用户写入其中的文本。我会将输入字段的委托设置为我自己并实现-textField:shouldChangeCharactersInRange:replacementString:

但是,我仍然希望允许代码的任何部分使用我作为文本字段来实现通常的委托方法。一种方法是存储第二个委托引用并像这样映射它们:

- (id)init {
    self = [super init];
    super.delegate = self;
    return self;
}

- (void)setDelegate:(id)delegate {
    self.nextDelegate = delegate;
}

- (id)delegate {
    return self.nextDelegate;
}

然后我会继续实现 all UITextFieldDelegate 方法,并按照我的意愿将它们转发给下一个委托。显然,我可能想在将它们传递给下一个委托之前修改一些参数,例如-textField:shouldChangeCharactersInRange:replacementString:
我正在考虑的另一个问题是当用户将nextDelegate 设置为文本字段本身时(无论出于何种原因),从而导致无限循环。

有没有更优雅的方式来劫持委托回调,就像我发布的示例代码一样?

【问题讨论】:

  • 您所描述的基本上是合理的。可能有其他方法可以解决给定的问题,但是您的级联委托是一种应该有效的通用方法。魔鬼当然在细节中——不止有几种方法可以搞砸它。
  • @HotLicks 实际上,它根本不健全,实际上它不起作用。 UITextField 中的代码将使用self.delegate 向委托发送消息,而不是super.delegate,这意味着委托方法实际上将转到self.nextDelegate
  • @JeremyP 它正在工作。您介意详细说明您的理论为什么它不应该起作用吗?
  • @ChristianSchnorr 唯一可行的方法是,UITextField 对象在内部使用实例变量向委托发送消息。如果要使用该属性(实际上应该如此),它将调用 your 委托方法来获取委托,而不是超级委托方法。
  • @ChristianSchnorr Nikolai 的回答更详细地解释了这个问题

标签: ios objective-c delegates delegation


【解决方案1】:

您的方法的问题是被覆盖的delegate 访问器:不能保证Apple 的代码总是直接使用delegate ivar 并且 使用getter 来访问委托。在这种情况下,它只会调用nextDelegate,绕过你偷偷溜进的self委托。

您可能已经检查过您的方法在当前实现中是否有效,但这也可能在未来的 UIKit 版本中发生变化。

有没有更优雅的方式来劫持委托回调,就像我发布的示例代码一样?

不,我不知道有任何优雅的解决方案。您不能覆盖委托访问器,而是设置辅助委托(您必须手动向其传递所有委托消息)。

为了解决过滤文本输入的实际问题,可能值得研究一下

- (void)replaceRange:(UITextRange *)range withText:(NSString *)text;

此方法由UITextField 实现(因为它采用UITextInput)并且可以被覆盖以过滤text 参数。

【讨论】:

  • 这听起来很有趣。但是,覆盖似乎没有帮助。即使我不调用 super 或使用修改后的文本调用 super,用户输入也会保留。我做错了吗?
  • @ChristianSchnorr 我不确定建议的覆盖是否可以满足您的需求。也许您在UITextInput 中找到了一些其他有用的方法来覆盖。另一方面,我想提出另一种避免子类化的方法。我正在为此添加一个新答案。
  • 我查看了 UITextInput 但您建议的方法是唯一对我有帮助的方法,但显然没有调用。至于在这种情况下的子类化,我确实认为这是最好的方法,因为我希望文本字段本身知道它可以接受哪些文本,并且潜在的代表不需要担心这一点。不过期待您的回答。
【解决方案2】:

我认为您对此的考虑是正确的,并且您概述的方法可以正常工作(我已经做到了)。

不存在循环问题,因为您不应该在子类的公共接口中公开 nextDelegate,因此调用者将没有机会设置循环。 (您也可以在 delegate != self 的设置器中进行测试。

不过,如果您能完全避免这种情况,那就更好了。例如,如果您只想在文本更改时调整文本字段文本,则可以获取控制事件:

[self addTarget:self action:@selector(didChange:) forControlEvents:UIControlEventEditingChanged];

那么,

- (void)textFieldDidChange:(id)sender {
    self.text = [self alteredText];  
}

- (NSString *)alteredText {
    // do whatever transform to user input you wish, like change user input 'a' to 'x'
    return [self.text stringByReplacingOccurrencesOfString:@"a" withString:@"x"];
}

这也可以,但会产生奇怪的副作用,即委托不会在shouldChangeCharactersInRange: 中看到更改后的Text。这可以通过将 alteredText 公开并让类客户调用它而不是标准 getter 来解决。

【讨论】:

  • 您的方法还会在每次文本“可能”更改时将插入符号重置为完全不透明。如果我删除空格并且用户尝试将空格添加到字符串的末尾,就会出现这种情况。即使文本实际上没有改变,菜单也不会闪烁。
  • 我所说的级联代表的意思也不是循环问题,而是以下层次结构:CreditCardField
  • 另一件要考虑的事情是 UI 风格。有人可能会争辩说,文本不反映用户输入是错误的(它可能看起来很糟糕)。更温和的方法可能是允许无约束输入,然后在获得 didEndEditing 时进行更改。
  • 很明显,如果用户不知道发生了什么,它可能看起来很糟糕,但他会想知道为什么他完成编辑后一半的输入消失了,不是吗?此外,在我使用它的大多数情况下,用户会知道他只被允许输入一组字符,例如信用卡信息。
  • 当然由你决定。无论如何,正如我所说,您概述的方法效果很好。添加公共接口的委托部分的 NSSet 也可以(尽管我从未尝试过这种方法)。
【解决方案3】:

所有子类化问题都可以通过使用不同的拦截委托消息的方法来避免:“委托代理”。

这个想法是使用一个中间对象(派生自NSProxy),它要么响应委托消息,要么将其传递给下一个委托。这基本上是您通过子类化 UITextField 所做的,但我们将使用一个自定义对象,而不是使用文本字段对象,该对象仅处理一些委托消息的拦截。

这些定制的委托代理形成了一组可重用的构建块,它们简单地相互插入,以定制任何使用委托的对象的行为。

这是一个代表链的示例 (code on github):

UITextField -> TextFilterDelegate -> SomeViewController

UITextField 将委托消息传递给 TextFilterDelegate,后者响应 textField:shouldChangeCharactersInRange:replacementString: 并将其他委托消息传递给它自己的委托(视图控制器)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多