【问题标题】:Complex Swift closure with generic argument带有泛型参数的复杂 Swift 闭包
【发布时间】:2017-09-08 04:41:19
【问题描述】:

我有一个场景,我需要调用一个带有可选闭包参数的方法,其中闭包接收一个通用参数。这是我的简化代码:

class FooModel
{
}

class FooSubClass1 : FooModel
{
}

class FooSubClass2 : FooModel
{
}

class Client
{
    func httpGet<T:FooModel>(closure:((T) -> Void)? = nil)
    {
        // Doing some network request stuff here and call onHTTPRequestComplete() when done!
        onHTTPRequestComplete(data: result, closure: closure)
    }

    func onHTTPRequestComplete<T:FooModel>(data:[String:Any], closure:((T) -> Void)? = nil)
    {
        if let c = closure
        {
            switch(T)
            {
                case is FooSubClass1:
                    var foo = FooSubClass1()
                    // Process data into new FooSubClass1 object here!
                    c(foo)
                case is FooSubClass2:
                    var foo = FooSubClass2()
                    // Process data into new FooSubClass2 object here!
                    c(foo)
            }
        }
    }
}


class App
{
    func someFunc()
    {
        client.httpGet()
        {
            response in
            print("\(response)")
        }
    }
}

我在这里尝试做的事情: 我有一个带有超类和几个子类的数据模型。在Client 中,我执行http 请求来检索数据,并希望用检索到的数据填充模型对象。 App 中的调用方法应该能够提供一个在异步网络请求完成后调用的闭包,并在其中返回正确的数据模型对象。

我的代码有几个问题:

switch(T): 错误:预期成员名称或类型名称后的构造函数调用

c(foo): 错误:'(FooSubClass1) -> Void' 不能转换为 '(T) -> Void'

不确定我的闭包定义是否对我正在尝试做的事情有意义。

更新代码:

protocol FooModel
{
    init()
}

class FooModelBase : FooModel
{
}

class FooSubClass1 : FooModelBase
{
}

class FooSubClass2 : FooModelBase
{
}

class Client
{
    func httpGet<T:FooModel>(closure:((T) -> Void)? = nil)
    {
        // Doing some network request stuff here and call onHTTPRequestComplete() when done!
        onHTTPRequestComplete(data: result, closure: closure)
    }

    func onHTTPRequestComplete<T:FooModel>(data:[String:Any], closure:((T) -> Void)? = nil)
    {
        if let c = closure
        {
            switch T.self
            {
                case is FooSubClass1.Type:
                    var foo = FooSubClass1()
                    // Assign retrieved values to model!
                    closure(foo)
                case is FooSubClass2.Type:
                    var foo = FooSubClass2()
                    // Assign retrieved values to model!
                    closure(foo)
                default:
                    break
            }
        }
    }
}


class App
{
    func someFunc()
    {
        client.httpGet()
        {
            response in
            print("\(response)") // Should output FooSubClass1 instance with assigned values!
        }
    }
}

几乎可以工作,但出现以下编译错误:

'closure(foo): Error: '(@lvalue FooSubClass1) -> Void' is not convertible to '(T) -> Void'

【问题讨论】:

  • 这仍然行不通,因为你的闭包需要一个通用的 T。你需要类似 ((FooModel) -&gt; Void)? 的东西(此时你可以摆脱泛型)但是在闭包中你会必须适当地将它转换为它应该是的任何东西。 (有很多方法可以解决这个问题,比如传入另一个参数以便知道它是什么类型等)
  • @BadmintonCat 如果您发现自己编写了一个 switch 语句来处理泛型参数的类型,那么您就违背了泛型的全部目的,即抽象。使用T 的方法必须能够采用any T,而不仅仅是您在方法中定义的一些狭窄列表。在我看来,重新考虑整个事情。
  • 像这样的最小的东西:pastebin.com/0WDWh1Cb
  • 另外,你的闭包永远不能是nil,否则编译器将无法弄清楚T的具体类型是什么。如果你省略闭包并说onHTTPRequest(data: [:]),那么T 是什么?您的代码将无法编译。

标签: swift generics swift3 closures


【解决方案1】:

对于switch(T) 错误,您当然会收到此错误。因为您正在尝试切换类型,但您应该切换一个值。对于您的第二个错误,这是非常有意义的,因为那时会出现什么关闭,因为 T 是通用的。

有关闭包中泛型的更多信息,请查看此 stackoverflow 线程

generics as parameters to a closure in swift

总而言之,你在那里尝试做的事情是不可能的,乍一看,我认为这不是正确的方向(尽管我可能在没有看到整个上下文的情况下错了)。你可以做的一件事也许是让你的闭包是((Any) -&gt; Void)?,但不幸的是,类型是Any,你必须在闭包内做一个开关。

【讨论】:

    【解决方案2】:

    闭包中T 的值将传递给闭包本身。你不能打开它。但我认为你想要的是打开类型。你可以这样做:

    let type = String(reflecting: T.self)
    switch (type) {
    case "Module.FooSubClass1":
        var foo = FooSubClass1()
    }
    

    或者你可以说:

    if FooSubClass1.self == T.self {
      var foo = FooSubClass1();
    } else if FooSubClass2.self == T.self { 
      var foo = FooSubClass2();
    }
    

    但是,this code smells。您似乎想使用泛型,但随后在泛型方法中有一组硬编码的类,这在一定程度上违背了泛型的全部目的。为什么不直接将FooModel 定义为

    protocol FooModel {
        init()
    }
    

    然后,不用您的 switch 语句,只需创建一个并将其传递给您的闭包。

    func onHTTPRequestComplete<T: FooModel>(data: [String: Any], closure: ((T) -> Void)? = nil) {
        guard let closure = closure else { return }
        closure(T())
    }
    

    或者为什么不在onHTTPRequestComplete 之外创建T 并通过它?

    onHTTPRequestComplete(data: data: model: FooSubClass1()) { model in
        // Do something awesome with `model` right here.
    }
    

    但是,我可能会这样做:

    func httpGet(callback: (([String: Any]) -> Void)? = nil) {
        // Do some network stuff and get the data
        callback?(data)
    }
    

    然后在callback 中,创建任何你想要的类型:

    httpGet { data in
       let foo = FooSubClass1(data: data)
       // foo on you
    }
    

    【讨论】:

    • 感谢您的详尽回答!我想我可以使用您的第一个和第四个代码示例的变体。但现在的问题是它从哪里得到T 的具体类型?我想如果我在调用闭包中定义具体的模型类型,它会从那里找出来,但情况似乎并非如此。 onHTTPRequestComplete 中的 T 类型将始终评估为 FooModel
    • 没关系!这行得通!我忘了调整中间调用的一些方法的方法签名。
    • 好吧,几乎成功了。现在我得到另一个错误:closure(foo): Error: (@lvalue FooSubClass1) -&gt; Void is not convertible to (T) -&gt; Void。我用上面的新代码更新了。
    • @BadmintonCat closure 的目的是什么?是否打算在 T 初始化之后使用?还是打算用来初始化T
    猜你喜欢
    • 1970-01-01
    • 2022-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-06
    相关资源
    最近更新 更多