强制将单个参数传递给多可选参数函数

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 会更加有用。

这样您就可以保证您将获得errordata,并且永远不必担心处理它们都是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,好吧,拥有DataErrorDataOrError? 中的一个,最多一个。

来电者网站,你会得到这样的东西:

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中绑定de,你需要说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 中)。 我喜欢所有这些示例,最后一个是我最喜欢的,因为它可以将所有示例很好地捆绑在一个地方,并且可以通过更多属性进行扩展。非常适合我的用例,谢谢。

以上是关于强制将单个参数传递给多可选参数函数的主要内容,如果未能解决你的问题,请参考以下文章

定义后无法将可选参数传递给express

如何将可选参数传递给typescript中的回调函数

如何将可选参数传递给 C++ 中的方法?

是否有必要在将可选参数传递给另一个可选参数之前检查它?

将可选路径参数传递给 ant

将可选参数传递给 ASP.NET API 的 GET 方法