带有泛型参数的复杂Swift闭包

时间:2017-09-08 04:41:19

标签: swift generics swift3 closures

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

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) - &gt; Void'不能转换为'(T) - &gt;无效“

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

更新代码:

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'

2 个答案:

答案 0 :(得分:1)

对于switch(T)错误,您当然会收到此错误。因为您正在尝试切换类型,但您应该切换一个值。对于你的第二个错误它是完全合理的,因为那时闭包会期待什么因为T是通用的。

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

generics as parameters to a closure in swift

总而言之,你在那里尝试做的事情是不可能的,乍一看我相信这不是正确的方向(虽然我没有看到整个背景)。你可以做的一件事可能是你的闭包是((Any) -> Void)?,但不幸的是,类型将是Any,你必须在闭包内进行切换。

答案 1 :(得分:1)

闭包中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())
}

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

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
}