Swift:基于协议的类型构造

时间:2015-06-18 15:53:34

标签: swift protocols object-construction

我试图在Swift中创建一个可用于对象构建的协议。我遇到的问题是我需要存储类型信息,以便稍后可以构造类型并在回调中返回。我似乎找不到存储它的方法,而不会崩溃编译器或创建构建错误。这是基础知识(一个人为的,但有效的例子):

protocol Model {
  init(values: [String])
  func printValues()
}

struct Request<T:Model> {
  let returnType:T.Type
  let callback:T -> ()
}

我们有一个简单的协议,声明init(用于构造)和另一个func printValues()(用于测试)。我们还定义了一个可用于存储类型信息的结构和一个用于在构造时返回新类型的回调。

接下来我们创建一个构造函数:

class Constructor {
  var callbacks: [Request<Model>] = []

  func construct<T:Model>(type:T.Type, callback: T -> ()) {
    callback(type(values: ["value1", "value2"]))
  }

  func queueRequest<T:Model>(request: Request<T>) {
    callbacks.append(request)
  }

  func next() {
    if let request = callbacks.first {
      let model = request.returnType(values: ["value1", "value2"])
      request.callback(model)
    }
  }
}

有几点需要注意:这会导致编译器崩溃。由于某种原因,它无法解决这个问题。问题似乎是var callbacks: [Request<Model>] = []。如果我注释掉其他所有内容,编译器仍会崩溃。注释掉var回调并且编译器停止崩溃。

此外,func construct工作正常。但它并不存储类型信息,所以它对我来说并不那么有用。我把它放在那里进行演示。

如果我从Request结构中移除协议要求,我发现可以防止编译器崩溃:struct Request<T>。在这种情况下,一切正常并且可以编译,但我仍然需要在let model = request.returnType(values: ["value1", "value2"])中注释掉func next()。这也导致编译器崩溃。

这是一个用法示例:

func construct() {
  let constructor = Constructor()
  let request = Request(returnType: TypeA.self) { req in req.printValues() }

  //This works fine
  constructor.construct(TypeA.self) { a in
    a.printValues()
  }

  //This is what I want
  constructor.queueRequest(request)
  constructor.next() //The callback in the request object should be called and the values should print
}

有没有人知道如何将限制在特定协议中的类型信息存储到该类型中,以后可以动态构建并在回调中返回?

4 个答案:

答案 0 :(得分:1)

如果您想要next的完全相同的行为,我建议您这样做:

class Constructor {
  // store closures
  var callbacks: [[String] -> ()] = []

  func construct<T:Model>(type:T.Type, callback: T -> ()) {
    callback(type(values: ["value1", "value2"]))
  }

  func queueRequest<T:Model>(request: Request<T>) {
    // some code from the next function so you don't need to store the generic type itself
    // **EDIT** changed closure to type [String] -> () in order to call it with different values
    callbacks.append({ values in
      let model = request.returnType(values: values)
      request.callback(model)
    })
  }

  func next(values: [String]) {
    callbacks.first?(values)
  }
}

现在,您可以使用您的值调用next。希望这适合你。

编辑:对闭包类型和next函数进行了一些更改

答案 1 :(得分:0)

不幸的是,没有办法在数组中保存特定的泛型类型并动态调用它们的方法,因为Swift是一种静态类型语言(并且Array必须有明确的类型)。

但希望我们将来可以像这样表达这样的事情:

var callbacks: [Request<T: Model>] = []

其中T可以是任何内容,但必须符合Model

答案 2 :(得分:0)

您的queueRequest方法不应该知道传递的Request通用类型。由于callbacksRequest<Model>类型的数组,因此该方法只需知道排队的请求属于Request<Model>类型。通用类型的含义并不重要。

此代码在Playground中为我构建:

class Constructor {
  var callbacks: [Request<Model>] = []

  func construct<T:Model>(type:T.Type, callback: T -> ()) {
    callback(type(values: ["value1", "value2"]))
  }

  func queueRequest(request: Request<Model>) {
    callbacks.append(request)
  }

  func next() {
    if let request = callbacks.first {
      let model = request.returnType(values: ["value1", "value2"])
      request.callback(model)
    }
  }
}

答案 3 :(得分:0)

所以我找到了一个似乎完全符合我想要的答案。我还没有在实时代码中确认这个有效,但它确实编译没有任何错误。事实证明,我需要再添加一个重定向级别:

我明确地为对象构造创建了另一个协议:

protocol ModelConstructor {
  func constructWith(values:[String])
}

在我的Request结构中,我遵守这个协议:

struct Request<T:Model> : ModelConstructor {
  let returnType:T.Type
  let callback:T -> ()

  func constructWith(values:[String]) {
    let model = returnType(values: values)
    callback(model)
  }
}

请注意,实际构造已移至Request结构中。从技术上讲,Constructor不再构建,但是现在我只留下它的名字。我现在可以将Request结构存储为ModelConstructor并正确排队请求:

class Constructor {
  var callbacks: [ModelConstructor] = []

  func queueRequest(request: Request<Model>) {
    queueRequest(request)
  }

  func queueRequest(request: ModelConstructor) {
    callbacks.append(request)
  }

  func next() {
    if let request = callbacks.first {
      request.constructWith(["value1", "value2"])
      callbacks.removeAtIndex(0)
    }
  }
}

注意一些特别之处:我现在可以成功地排队&#34;排队&#34; (或存储在数组中)Request<Model>,但我必须通过调用queueRequest(request: ModelConstructor)间接地这样做。在这种情况下,我超载,但没有必要。这里重要的是,如果我尝试在callbacks.append(request)函数中调用queueRequest(request: Request<Model>),则Swift编译器崩溃。显然我们需要把编译器的手放在这里,以便它能够理解我们想要什么。

我发现您无法将Type信息与Type Construction分开。它需要全部在同一个地方(在这种情况下它是Request结构)。但只要你保持结构与类型信息相结合,你就可以自由地延迟/存储构造,直到你获得实际构建对象所需的信息。