强制将单个参数传递给多可选参数函数
Posted
技术标签:
【中文标题】强制将单个参数传递给多可选参数函数【英文标题】:Force passing a single parameter, to a multi-optional-parameter function 【发布时间】:2017-01-10 14:14:24 【问题描述】:考虑以下函数:
func myFunction(completion: (data: Data?, error: Error?) -> ())
我目前的要求是让completion:
只接受data
值或error
值,但不能同时接受两者。一个必须为零。
将它们都保留为可选然后打开包装并稍后检查它们的值很容易,但我认为如果编译器能够通知开发人员它们不能同时设置它们会更好。
从另一个角度来看,知道其中一个将始终设置为 someValue
会更加有用。
这样您就可以保证您将获得error
或data
,并且永远不必担心处理它们都是nil
的情况。
目前有没有办法做到这一点?
【问题讨论】:
我认为您正在提议扩展该语言及其类型系统。 AFAIK,您不能在 Swift 签名中关联参数。 【参考方案1】:我建议你使用这样的枚举
enum Result<T>
case Success(T)
case Error(String, Int)
查看Best way to handle errors from async closures in Swift 2?,原始答案写得更详细。
【讨论】:
【参考方案2】:作为结果枚举的替代方案,您应该研究 Promise 框架。有很多,但我喜欢PromiseKit。
func myFunction() -> Promise<Data>
这是它的使用方法。
obj.myFunction().then data -> Void in
// Use data, it's guaranteed to be there.
.catch error in
// Handle the error
【讨论】:
【参考方案3】:你想要一些它做不到的 Swift。特别是,据我所知,Swift 没有联合类型。如果你想要这样的东西,你可能想看看函数式或受函数启发的语言。
插曲:如果这两种类型都是结构或类(即不是协议),您可以使用这种模式:
protocol DataOrError
extension Data: DataOrError
extension ErrorClass: DataOrError
假设没有该协议的其他实现者,您现在有一些接近联合类型的东西。你也可以切换类型:
switch doe
case let e as Error: print("Error \(e)")
case let d as Data: print("Data \(d)")
default: assert(false, "thou shalt not implement my dummy!")
无论如何,我们可以使用具有关联值的枚举来伪造它,这可以(有限制地)用作穷人的联合类型。
像这样定义一个枚举:
enum DataOrError
case Data(Data)
case Error(Error)
现在你可以在任何你想使用的地方使用DataOrError
,好吧,拥有Data
或Error
和DataOrError?
中的一个,最多一个。
来电者网站,你会得到这样的东西:
extension String: Error // low-effort errors, don't judge me!
func myFunction(completion: (DataOrError) -> ())
completion(.Data(Data(bytes: [1,2,3,4,5])))
completion(.Error("this won't work!"))
和被调用者站点:
var myCompletion = (doe: DataOrError) in
switch doe
case .Data(let d): print("Data \(d)")
case .Error(let e): print("Error \(e)")
myFunction(completion: myCompletion)
// > Data 5 bytes
// > Error this won't work!
设计说明:您可能正在寻找另一个方向的概括,特别是如果您有许多不同的类型要与Error
进行或运算。在这种情况下,显式包装器可能是一个很好的解决方案,即使您牺牲了良好的语法。
struct TalkativeOptional<T>
private(set) var rawValue: T?
private(set) var error: Error?
init(value: T)
self.rawValue = value
init(error: Error)
self.error = error
请注意这两个属性中的一个究竟可以是非nil
。还有两种组合;在这里,您可以通过选择的初始化程序来控制您想要的。
被调用者站点示例:
func convert(_ number: String) -> TalkativeOptional<Int>
if let int = Int(number)
return TalkativeOptional(value: int)
else
return TalkativeOptional(error: "'\(number)' not a valid integer!")
调用者站点示例:
var a = convert("dsa2e2")
if let val = a.rawValue
print("a + 1 = \(val + 1)")
else // we know by design that error is set!
print("ERROR: \(a.error!)")
【讨论】:
为了在你的第一个switch
中绑定d
和e
,你需要说let
(例如case let d as Data: print("Data \(d)")
)。此外,您不需要带有enum
选项的default
案例,因为编译器可以看到switch
是详尽无遗的(枚举比联合类型的协议更可取的原因之一)。另请注意,它是 lowerCamelCase
枚举案例的 Swift 约定。最后(很抱歉用这一切打你),你的 TalkativeOptional
不能保证它的属性是非零的,因为它的属性是可变的,因此使强制展开具有潜在的危险。
都是真的,谢谢!还在学习自己。 1)关于lowerCamelCase
:这对我Java 缠身的头脑来说感觉很奇怪。 2)关于TalkativeOptional
,很好抓;我在考虑let
-declarations,但是如果我们将这样的值返回给某人,我们当然无法控制。
没问题,顺便说一句,欢迎来到 Swift 社区 :) 关于 lowerCamelCase
,这是使用 UpperCamelCase
命名类型的整体约定的结果,而其他所有命名都较低(因此枚举案例是't 类型,它们更低)(documented here)。关于您的第二点,您可以将 TalkativeOptional
let
的属性设为常量,因为它们在初始化时被赋予了值,并且不需要通过实现进行突变(您必须将其他属性显式设置为nil
虽然在 init 中)。
我喜欢所有这些示例,最后一个是我最喜欢的,因为它可以将所有示例很好地捆绑在一个地方,并且可以通过更多属性进行扩展。非常适合我的用例,谢谢。以上是关于强制将单个参数传递给多可选参数函数的主要内容,如果未能解决你的问题,请参考以下文章