在下面的例子中,让TextViewController
符合UIScrollViewDelegate
而不是UITextViewDelegate
,然后将文本视图转换为滚动视图(或者声明为UIScrollView
)是否合理?如果没有,你建议如何做?
我不想让代码看起来像TextViewController
可以符合UITextViewDelegate
,因为这会让人感到困惑。我假设如果一个非可选方法被添加到UITextViewDelegate
中,强制转换将失败。此外,不允许使用private/fileprivate扩展,但这会减少我的担忧。
class TextViewController: UIViewController {
let textView = UITextView()
override func viewDidLoad() {
textView.delegate = self // Error
(textView as? UIScrollView)?.delegate = self // OK
}
}
extension TextViewController: UIScrollViewDelegate
{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
doSomethingOnScroll()
}
}
个字符
在这个例子中,我没有创建自己的协议,以使代码更简洁。
2条答案
按热度按时间3phpmpom1#
(添加一个答案,因为这太长,无法评论。)
为了补充一些关于“为什么”的更明确的细节,你正在做的事情是一个坏主意:
你在这里遇到的情况是一个Objective-C-ism,通常在Swift中是不允许的;具体地说,这里处理的是子类型(
UITextView : UIScrollView
)上的一个属性,该属性比其超类型的属性(UITextViewDelegate : UIScrollViewDelegate
)更具体。这违反了Liskov substitution principle,但是Objective-C的类型系统是weak,足以允许这样做。您已经隐式地调用了这一点,但对读者来说是简短的:
UIScrollView
的delegate
属性允许 * 任何 *UIScrollViewDelegate
符合对象分配给它;毕竟,任何符合协议的东西都将满足UIScrollView
对如何处理事件的信息的需要。然而,UITextView
覆盖了这个属性,只接受符合更具体UITextViewDelegate
协议的对象,因为它有更多的问题需要向委托人询问如何处理各种场景。在正常的子类型规则下(以及Liskov替换原则成立时),如果你取一个对象a: A
,并将其强制转换为它的超类B
,那么它的所有属性应该完全保持原样。除了对于UITextView
不是这样。当你将UITextView
的示例强制转换为UIScrollView
时,它的delegate
属性突然变得 * 更一般 *,这意味着你可以将一个UIScrollViewDelegate
赋给delegate
属性,而不是 *UITextViewDelegate
(因为,再一次,UIScrollView
只关心UIScrollViewDelegate
能告诉它的事情)。如果UITextViewDelegate
有所需的方法,这将立即成为问题,因为在 * 运行时 *,文本视图将尝试调用委托上的一个方法,而它不知道如何回答。你提过
我假设如果向UITextViewDelegate添加了一个非可选方法,强制转换将失败
但需要强调的是这不是真的
UITextView
继承自UIScrollView
,所以textView as? UIScrollView
将无条件地成功-它只是在访问超类型属性时保持Objective-C的行为,就像它们是静态类型的属性一样。这意味着(textView as? UIScrollView)?.delegate
将 * 总是 * 允许您将UIScrollViewDelegate
对象分配给属性,即使在运行时UITextView
将调用不存在的方法;虽然这不可能合理地发生(因为现有的委托对象不能隐式地满足需求),但如果UITextViewDelegate
确实得到了非可选的方法需求,那么您将在运行时崩溃。在您非常特殊的情况下,这可能有效,但在一般情况下这样做是非常不安全的。
值得一提的是,如果你试图在纯Swift中重新创建这个场景,编译器不会允许你这样做,理由很充分:
字符串
Swift不允许用不同类型的属性覆盖属性,以防止这种情况,同样也防止方法参数和返回值的反转。
72qzrwbm2#
我不推荐这样做。它有点工作(虽然它抛出警告),但它比它取代的东西更令人困惑。TextViewController肯定仍然可以实现UITextViewDelegate方法,并且它们将被调用;它们只需要标记为
@objc
。随着代码的发展,这就要求出现一些细微的bug。作为其他人(或者你自己,因为你忘记了你这样做了),尝试添加UITextViewDelegate方法,它们可能会被调用,也可能不会被调用,这取决于它们的添加方式(在Swift或ObjC中,或者有或没有@objc
)。你可以假设这永远不会发生,但是“这使得未来的bug更容易被创建”是一个风险。不太清楚为什么要隐藏一致性,但是如果它很重要,有几种方法可以这样做。
最简单的方法是将
textView
本身设为UIScrollView:字符串
(If您将
textView
设为私有,不需要UITextView访问权限,这可能会更简单。)或者,您可以为UITextView创建一个内部私有委托,这样它就不会被公开。不清楚公开UITextViewDelegate比公开UIScrollViewDelegate更重要。但如果你想隐藏这些细节,那么我会把整个内容隐藏在一个内部类型中。
也就是说,你的问题是征求建议。建议是不要这样做。如果问题是“这会起作用吗”,答案是肯定的,除了它抛出了一个警告。