Swift:基于协议的类型构造

Swift: Protocol Based Type Construction

我正在尝试在 Swift 中创建一个可用于对象构建的协议。我 运行 遇到的问题是我需要存储类型信息,以便以后可以构造类型并 return 在回调中编辑。我似乎无法找到一种方法来存储它而不会使编译器崩溃或产生构建错误。这是基础知识(一个人为但可行的示例):

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

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

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

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

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>。在这种情况下,一切正常并编译,但我仍然需要在 func next() 中注释掉 let model = request.returnType(values: ["value1", "value2"])。这也会导致编译器崩溃。

这是一个用法示例:

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
}

有谁知道我如何将类型信息限制为特定协议存储到以后可以动态构建的类型并 returned 在回调中?

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

但希望我们以后可以这样表达:

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

其中 T 可以是任何东西,但必须符合 Model 例如。

您的 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)
    }
  }
}

如果您想要与 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函数做了一些更改

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

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

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

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

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)
    }
  }
}

请注意这里的一些特别之处:我现在可以成功 "queue"(或存储在数组中)Request<Model>,但我 必须 通过调用间接地这样做queueRequest(request: ModelConstructor)。在这种情况下,我正在超载,但这不是必需的。这里重要的是,如果我尝试在 queueRequest(request: Request<Model>) 函数中调用 callbacks.append(request),Swift 编译器就会崩溃。显然我们需要在这里稍微握住编译器的手,这样它才能理解我们到底想要什么。

我发现您无法将类型信息与类型构造分开。它需要都在同一个地方(在本例中是 Request 结构)。但是只要您保持构造与类型信息相结合,您就可以自由地 delay/store 构造,直到您拥有实际构造对象所需的信息。