简体   繁体   中英

swift affect value to inout generic variable

I want to simplify this piece of code with a T variable but could not succeed in compiling it. Hope could you give me the way.

here is the "duplicate" code I want to rewrite :

    func getIntegerValue (listValues: [Any], numValueToRead: Int, readValue: inout Int) -> Bool {
        if numValueToRead < 0 || numValueToRead >= listValues.count {
            return false
        }
        let value = listValues [numValueToRead]
        if type (of: value) == type(of: readValue) {
            readValue = value as! Int
            return true
        } else {
            return false
        }
    }

    func getStringValue (listValues: [Any], numValueToRead: Int, readValue: inout String) -> Bool {
        if numValueToRead < 0 || numValueToRead >= listValues.count {
            return false
        }
        let value = listValues [numValueToRead]
        if type (of: value) == type(of: readValue) {
            readValue = value as! String
            return true
        } else {
            return false
        }
    }

Here is the code I wrote but do not compile :

func getValue <T> (listValues: [Any], numValueToRead: Int, readValue: inout T) -> Bool {
    if numValueToRead < 0 || numValueToRead >= listValues.count {
        return false
    }
    let value = listValues [numValueToRead]
    if type (of: value) == type(of: readValue) {
        switch value {
        case let integerValue as Int:
            readValue = integerValue
        case let stringValue as String:
            readValue = stringValue
        default:
            return false
        }
        return true
    } else {
        return false
    }
}

for those affectations I got those compilation errors : readValue = integerValue -> 'Int' is not convertible to 'T' readValue = stringValue -> 'String' is not convertible to 'T'

Is there a way to synthetise my two functions with a unique one using generics ?

You theoretically could make it compile by adding forced casts, since you already know that value has the type T :

    case let integerValue as Int:
        readValue = integerValue as! T
    case let stringValue as String:
        readValue = stringValue as! T

But the far better solution is to use a conditional cast ( as? T ) and conditional binding ( if let ):

func getValue<T>(listValues: [Any], numValueToRead: Int, readValue: inout T) -> Bool {
    if numValueToRead < 0 || numValueToRead >= listValues.count {
        return false
    }
    let value = listValues[numValueToRead]
    if let tvalue = value as? T {
        readValue = tvalue
        return true
    } else {
        return false
    }
}

which then works for arbitrary types, not only Int and String .

A “swiftier” way would be return an optional value (with nil indicating "no value"). The code can then be simplified to

func getValue<T>(listValues: [Any], numValueToRead: Int) -> T? {
    guard listValues.indices.contains(numValueToRead) else {
        return nil
    }
    return listValues[numValueToRead] as? T
}

This should work:

func getValue <T> (listValues: [Any], numValueToRead: Int, readValue: inout T) -> Bool {
    if numValueToRead < 0 || numValueToRead >= listValues.count {
        return false
    }

    let value = listValues [numValueToRead]
    if type (of: value) == type(of: readValue) {
        if let genericValue = value as? T {
            readValue = genericValue
            return true
        }

        return false
    } else {
        return false
    }
}

At first sight, the function is wrongly named. You cannot call function getValue when it returns bool... I would call it transform or modify or something other than get value, because you are NOT getting value.

I think this method suits better your needs, not tested tought it should work.

func transformValue<T>(from listValues: [Any], numValueToRead: Int, readValue: inout T?) throws -> Bool {

    // Guard suits better this case...
    guard numValueToRead > 0 || numValueToRead < listValues.count else { return false }

    let value = listValues[numValueToRead]
    if type (of: value) == type(of: readValue) {
        guard let value =  value as? T else {
            throw NSError(
                domain: "Smth",
                code: 1,
                userInfo: ["Description": "Failed to cast to generic type T"]
            )
        }
        readValue = value as? T
        return true
    }
    return false // No need to call else...
}

Explenation: Returning optional generic type T is much safer. You try to cast it, you fail and you throw error that something went wrong. In my opinion saving force casts with throwing errors is much more safer approach, you know what went wrong and so.

As @MartinR pointed out, returning a nil value instead of an inout + Bool combination gives the same results, but with less, and more readable code. This is the path Swift also took when importing most of the NSError ** methods from Objective-C (ie dropped the last parameter, imported them as throwable functions).

These being said, another approach would be to add an extension over Array for extracting the value:

extension Array {
    subscript<T>(_ index: Int, as type: T.Type) -> T? {
        guard 0..<count ~= index else { return nil }
        return self[index] as? T
    }
}

let arr: [Any] = [1, "two", 3, "four"]
arr[1, as: String.self] // two
arr[2, as: String.self] // nil

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM