具有由Self约束的协议扩展的泛型闭包中的错误推断类型

时间:2017-11-14 00:50:23

标签: swift generics inheritance protocols swift4

尝试在Swift中实现一个函数时遇到了一个问题,似乎类型推断破坏了某些函数参数。从其他语言做笔记,我想在Swift中实现一个函数,它接受两个参数:一个对象和一个块,它返回与前一个对象相同类型的另一个对象。我开始像这样实现它:

func prepare<T>(_ object: T?, with block: ((T?) -> T?)) -> T? {
    return block(object)
}

尝试在UITableViewController类中使用它之后,我遇到了命名问题,因为它似乎在与segue函数prepare(for:sender:)

进行斗争

我决定更改名称以解决此问题(我将保存以上内容以解决另一个问题):

func bootstrap<T>(_ object: T?, with block: ((T?) -> T?)) -> T? {
    return block(object)
}

此名称暂时可用。所以我有一个bootstrap(_:with:)函数,它接受一个对象和一个闭包,返回一个与object参数相同类型的对象。我可以使用某些对象轻松调用它,例如Int

bootstrap(1, with: { $0 + 1 })  // returns 2

然而,这当然不是函数的用途。所以我在它的实际应用程序中使用它,我有一个小UIView派生类,只有一个成员:

class ContactDetailHeaderView: UIView, Nibbable {
    var user: User?
}

这是使用Nibbable协议上的以下协议扩展功能从笔尖获取的:

extension Nibbable where Self: UIResponder {
    static func getRoot(withOwner owner: Any, options: [AnyHashable:Any]? = nil) -> Self? {
        return self.nib.instantiate(withOwner: owner, options: options).first as? Self
    }
}

有了这个,我们可以从nib(这个类的UINib对象)中检索一个对象,并将其转换为Self,在本例中为ContactDetailHeaderView。然后,我们可以使用我们先前声明的函数来设置对象。我们在UITableViewController - 派生类ContactDetailTableViewController内执行此操作,我将在此问题中缩进:

class ContactDetailTableViewController: UITableViewController {
    var user: User?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.backgroundView = bootstrap(ContactDetailHeaderView.getRoot(withOwner: self), with: { $0?.user = self.user })
    }
}

但是,我们在调用此函数时出错:

Value of type 'UIView' has no member 'user'

这是出乎意料的。我们的getRoot(withOwner:options:)函数应该返回Self ContactDetailHeaderView,对吧?

让我们通过用命名参数和明确定义的类型替换速记参数来扩展类型推断:

bootstrap(ContactDetailHeaderView.getRoot(withOwner: self),
    with: { (header: ContactDetailHeaderView?) -> ContactDetailHeaderView? in
        header?.user = self.user
    }
)

当然,我们的对象现在无法被解释为UIView?好吧,我们仍然得到同样的错误:

Value of type 'UIView' has no member 'user'

为什么会这样?即使我在Xcode中检查自动完成,此处的header参数也会将user成员显示为有效的可访问属性。在其他地方使用getRoot(withOwner:options:)函数会产生预期的结果:

ContactDetailHederView.getRoot(withOwner: self)?.user = self.user

那为什么它在bootstrap(_:with:)闭包内不起作用?这是一个错误吗?还有其他我想念的东西吗?

非常感谢任何帮助。

我会附上一个gist,其中包含足够的代码,可以在操场上重现问题。为了简单起见,我将用一些更易于管理的东西替换一些不必要的位(例如从user更改User类型,这是String的内部对象,可在标准库中找到)

请记住,这个问题关于该功能的作用或原因。有很多方法可以实现相同的功能,其中很多可能要好得多。无论我是否以当前形式使用此函数,我都想解决这个编译错误。

1 个答案:

答案 0 :(得分:2)

  

请记住,这个问题不是关于功能的作用或原因。有很多方法可以实现相同的功能,其中很多可能要好得多。无论我是否以当前形式使用此函数,我都想解决这个编译错误。

很好,因为这有太多的选择。但它很容易修复(它真的很难理解,因为它太复杂了。)

bootstrap应该返回T?。你传递了这个:

{ (header: ContactDetailHeaderView?) -> ContactDetailHeaderView? in
    header?.user = self.user
}

返回Void。这导致类型引擎耗尽所有类型的兔子洞(特别是因为任何T都可以成为T?)。解决方法是返回T

{ (header: ContactDetailHeaderView?) -> ContactDetailHeaderView? in
    header?.user = self.user
    return header
}

在你的要点中,这将是:

self.tableView.backgroundView = bootstrap(ContactDetailHeaderView.getRoot(withOwner: self), 
                                with: { $0?.user = self.user; return $0 })