在 Swift 中,如何测试一个对象是不是实现了一个可选的协议方法,该方法在签名上有所不同,而无需实际调用该方法?

Posted

技术标签:

【中文标题】在 Swift 中,如何测试一个对象是不是实现了一个可选的协议方法,该方法在签名上有所不同,而无需实际调用该方法?【英文标题】:In Swift, how can you test if an object implements an optional protocol method which differs by signature without actually calling that method?在 Swift 中,如何测试一个对象是否实现了一个可选的协议方法,该方法在签名上有所不同,而无需实际调用该方法? 【发布时间】:2019-02-28 08:10:57 【问题描述】:

使用 Swift,是否可以在不实际调用该方法的情况下测试对象是否实现了可选协议方法?这适用于可选方法仅因签名不同的情况。

考虑这段代码...

@objc public protocol TestDelegate : AnyObject 
    @objc optional func testx()
    @objc optional func test(with string:String)
    @objc optional func test(with2 int:Int)


let delegate:TestDelegate? = nil

if let _ = delegate?.test(with:) 
    print("supports 'test(with:)'")


if let _ = delegate?.testx 
    print("supports 'testx'")

如果您将以上内容粘贴到 Playground 中,它会按预期工作。

但是,如果您将 testx 更改为 test,它将不再有效。

同样,如果您将 test(with2) 更改为 test(with) 也不会起作用。

有什么方法可以测试那些仅在签名上有所不同的方法吗?

【问题讨论】:

比较***.com/q/35658334/2976878——你可以明确地强制消除你想要的过载,例如delegate?.test as (() -> Void)? 有趣。我试过了(和几种变体)但无法让它工作。让我再试一次。 您能否提供更多背景信息来说明您要解决的问题?这似乎是一个 XY 问题。 本身没有什么可“解决”的。只是学习更多关于语言的知识。以上内容只是从我正在玩耍的游乐场中剪切/复制的。 @Hamish,我似乎无法让您的答案适用于上述第二种情况(参数名称相同,但类型不同。)这可能吗? 【参考方案1】:

Hey MarqueIV 检查您可以使用内置函数的可选功能

func 响应(对 aSelector: Selector!) -> Bool

返回一个布尔值,指示接收者是实现还是继承了可以响应指定消息的方法。

应用程序负责确定是否应将错误响应视为错误。

您无法通过使用 super 关键字向对象发送响应(to:) 来测试对象是否从其超类继承方法。

此方法仍将测试整个对象,而不仅仅是超类的实现。

因此,给super发送response(to:)相当于给self发送response(to:)。

相反,您必须直接在对象的超类上调用 NSObject 类方法 instancesRespond(to:),如以下代码片段所示。

清单 1

if( [MySuperclass instancesRespondToSelector:@selector(aMethod)] ) 
    // invoke the inherited method
    [super aMethod];

您不能简单地使用 [[self superclass] instancesRespondToSelector:@selector(aMethod)],因为如果它被子类调用,这可能会导致方法失败。

请注意,如果接收者能够将 aSelector 消息转发给另一个对象,那么即使此方法返回 false,它也可以间接响应该消息。 参数 选择器 标识消息的选择器。 退货 如果接收者实现或继承了可以响应 aSelector 的方法,则为 true,否则为 false。 SDK ios 2.0+、macOS 10.0+、tvOS 9.0+、watchOS 2.0+

【讨论】:

知识渊博! 如果我错了,请纠正我,但那是 Objective-C。我的问题专门针对 Swift 不可用的地方。 @MarqueIV 这个解释是用 Objective-C 写的,但是我用粗体写的方法是 swift,请检查,Xcode 会自动提示。 手动调用responds(to:) IMO 并不是检查类实例是否实现@optional 协议要求的惯用 Swift 方法。此类引用返回可选函数,以便模拟符合类可能未实现它们的事实。【参考方案2】:

同样如 How do I resolve "ambiguous use of" compile error with Swift #selector syntax? 所示,您可以显式地将函数引用强制转换为其预期类型,以解决此类歧义。

唯一的区别是,由于此类函数引用是通过可选链接完成的@optional 协议要求,因此您需要强制转换为函数的可选类型。从那里,您可以与nil 进行比较,以确定委托是否都非零,并且它实现了给定的要求。

例如:

import Foundation

@objc public protocol TestDelegate : AnyObject 
  @objc optional func test()

  // Need to ensure the requirements have different selectors.
  @objc(testWithString:) optional func test(with string: String)
  @objc(testWithInt:) optional func test(with int: Int)


class C : TestDelegate 
  func test() 
  func test(with someString: String) 
  func test(with someInt: Int) 


var delegate: TestDelegate? = C()

if delegate?.test as (() -> Void)? != nil 
  print("supports 'test'")


if delegate?.test(with:) as ((String) -> Void)? != nil 
  print("supports 'test w/ String'")


if delegate?.test(with:) as ((Int) -> Void)? != nil 
  print("supports 'test w/ Int'")


// supports 'test'
// supports 'test w/ String'
// supports 'test w/ Int'

请注意,我为 test(with:) 要求提供了唯一的选择器,以确保它们不会发生冲突(这不会影响消歧,只允许类 C 符合 TestDelegate)。

【讨论】:

接受! :) 这提出了一个新问题。你能区分func test(a: String)func test(b: String) 吗?从技术上讲,它们是两个不同的签名,但如果您只能测试类型,我不确定您是否可以在不诉诸其他审讯形式的情况下进行检查。

以上是关于在 Swift 中,如何测试一个对象是不是实现了一个可选的协议方法,该方法在签名上有所不同,而无需实际调用该方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 swift 3 中将单个对象作为参数发布到 http 请求中

Swift UI 测试,如何检查单元格是不是有图像视图

如何在 Swift 中制作没有 VisualFormat 的约束对象?

Swift - 点击按钮时如何测试标签是不是已更新

Swift 单元测试:HotSwap 一个 CLLocationManager 对象

检查 Swift 对象是不是是给定元类型的实例