有没有办法一起使用模板、输入输出参数和可选参数?

Is there a way to use a Template, and In-Out Parameter and Optional together?

前几天我 运行 做了一些有趣的事。基本上我最初写了一个辅助函数来防止在自动类型转换 JSON 属性 时出错。它看起来像这样:

func readData<T>(inout output:T, _ input:AnyObject?, _ throwError:Bool = true) throws
{
    if (input == nil) {
        if (throwError) {
            throw ConvertError.MissingParameter
        }
    }
    else {
        if let inputObject:T = input as? T {
            output = inputObject
        }
        else if (throwError) {
            throw ConvertError.WrongType
        }
    }
}

var myProperty:String
try readData(&myProperty, myJson["data"], true)

这会检查 属性 是否存在,并且它是正确的类型。如果一切顺利,myProperty 中的值会发生变化。

过了一会儿,我需要做一些改变。我制作了一个名为 properties 的 class,其中包含一个属性列表。这种类型的 class 有 2 个变量:originalPropertiesmodifiedProperties 这些 class 中的每个属性都是现在可选变量,专门用于跟踪用户更改了哪些属性。基本上它看起来像这样:

class Properties
{
    var x:Int?
    var y:Int?
}

现在当我运行这个:

try readData(&originalProperties.x, myJson["x"], false)

它不再起作用了。我查看了 another question,它解释了发生了什么。基本上 x 的值仍然是 nil (因为它是可选的),我将 nil 值传递到我的 readData 函数,所以模板类型设置不正确,这就是为什么它在 输入上失败的原因? T码.

幸运的是,我不用再拥有这样的创意功能了。我只能用这个:

originalProperties.x= obj["x"] as? Int

但是如果我需要的话,我会失去抛出错误的功能。

有没有人知道如何确保我的模板类型在其值为 nil 的情况下正确传递?我什至在另一个线程中读到我可能必须使用某种默认值闭包,但看看是否有办法解决这个问题会很有趣。

这里的主要问题是泛型 T 永远 自己 不知道它是否是 Optional 类型,这使得类型转换成功到 T 对于 T 实际上是 Optional<SomeType> 类型的情况来说很棘手。我们自己可以断言 T 是可选类型(检查 Mirror(reflecting: ...).displayStyle == .Optional 等),但这仍然不能立即解决到 T 的转换问题。相反,我们可以使用另一种方法,如下所示。

我们可以通过创建两个 readData(...) 函数来解决这个问题,一个采用可选的泛型 inout 参数,类型 U?,另一个采用隐式 [=66] =] 泛型 inout 参数 U(仅当 U? 函数无法使用时调用,因此隐式仅调用 non-optionals)。反过来,这两个函数是最小的,基本上只调用您的 "core" dataReader(..) 函数,我们在其中进行了调整,使 inout 泛型参数现在显式可选,即 T?.

enum ConvertError: ErrorType {
    case MissingParameter
    case WrongType
}

/* optional inout parameter */
func readData<U>(inout output: U?, _ input: AnyObject?, _ throwError: Bool = true) throws {
    try readDataCore(&output, input, throwError)
}

/* non-optional inout parameter */
func readData<U>(inout output: U, _ input: AnyObject?, _ throwError: Bool = true) throws {
    var outputOpt : U? = output
    try readDataCore(&outputOpt, input, throwError)
    output = outputOpt!
    /* you could use a guard-throw here for the unwrapping of 'outputOpt', but
       note that 'outputOpt' is initialized with a non-nil value, and that it can
       never become 'nil' in readDataHelper; so "safe" forced unwrapping here.    */
}

/* "core" function */
func readDataCore<T>(inout output: T?, _ input: AnyObject?, _ throwError: Bool = true) throws
{
    if (input == nil) {
        if (throwError) {
            throw ConvertError.MissingParameter
        }
    }
    else {
        if let inputObject: T = input as? T {
            output = inputObject
        }
        else if (throwError) {
            throw ConvertError.WrongType
        }
    }
}

当我们尝试这个时,我们发现我们现在得到了我们正在寻找的行为,即使作为 inout 参数发送的参数是 nilOptional


示例 1:使用值为 nil

的可选 inout 参数
class Properties
{
    var x:Int?
    var y:Int?
}

var myJson : [String:Int] = ["data":10]
var originalProperties = Properties()

do {
    try readData(&originalProperties.x, myJson["data"], true)
    print("originalProperties.x = \(originalProperties.x ?? 0)")
} catch ConvertError.MissingParameter {
    print("Missing parameter")
} catch ConvertError.WrongType {
    print("Wrong type")
} catch {
    print("Unknown error")
}
/* Prints: 'originalProperties.x = 10', ok! */

// try some non-existing key 'foo'
do {
    try readData(&originalProperties.x, myJson["foo"], true)
    print("originalProperties.x = \(originalProperties.x ?? 0)")
} catch ConvertError.MissingParameter {
    print("Missing parameter")
} catch ConvertError.WrongType {
    print("Wrong type")
} catch {
    print("Unknown error")
}
/* Prints: 'Missing parameter', ok! */

示例 2:使用可选的 inout 参数,但带有 non-optional 值

class Properties
{
    var x:Int? = 1
    var y:Int? = 1
}

var myJson : [String:Int] = ["data":10]
var originalProperties = Properties()

// ...

/* Same results as for Example 1: 
   'originalProperties.x = 10' and 'Missing parameter' */

示例 3:使用 non-optional inout 参数

class Properties
{
    var x:Int = 1
    var y:Int = 1
}

var myJson : [String:Int] = ["data":10]
var originalProperties = Properties()

// ...

/* Same results as for Example 1: 
   'originalProperties.x = 10' and 'Missing parameter' */