Я хочу упростить этот кусок кода с помощью переменной T, но мне не удалось его скомпилировать. Надеюсь, вы могли бы дать мне путь.

Вот «дубликат» кода, который я хочу переписать:

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

Вот код, который я написал, но не компилирую:

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

Для этих воздействий я получил эти ошибки компиляции: readValue = integerValue -> 'Int' не конвертируется в 'T' readValue = stringValue -> 'String' не конвертируется в 'T'

Есть ли способ синтезировать две мои функции с уникальной, используя дженерики?

1
PyrosBrother 3 Мар 2018 в 16:14

4 ответа

Лучший ответ

Теоретически вы можете заставить его скомпилировать, добавив принудительное приведение, так как вы уже знаете, что value имеет тип T:

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

Но гораздо лучшим решением является использование условного приведения (as? T) и условное связывание (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
    }
}

Который затем работает для произвольных типов, а не только Int и String.

«Более быстрый» способ - возвращать необязательное значение (с nil указывая «нет значения»). Код может быть упрощен до

func getValue<T>(listValues: [Any], numValueToRead: Int) -> T? {
    guard listValues.indices.contains(numValueToRead) else {
        return nil
    }
    return listValues[numValueToRead] as? T
}
2
Martin R 3 Мар 2018 в 13:31

На первый взгляд, функция названа неправильно. Вы не можете вызвать функцию getValue, когда она возвращает bool ... Я бы назвал это преобразованием или изменением, или чем-то другим, кроме get value, потому что вы НЕ получаете значение.

Я думаю, что этот метод лучше подходит вашим потребностям, а не проверял, думал, что он должен работать.

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

Расширение: возврат необязательного универсального типа T намного безопаснее. Вы пытаетесь разыграть его, терпите неудачу и выкидываете ошибку, что что-то пошло не так По моему мнению, экономия силы с ошибками броска - намного более безопасный подход, вы знаете, что пошло не так и так.

0
Dominik Bucher 3 Мар 2018 в 14:21

Это должно работать:

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
    }
}
0
nikano 3 Мар 2018 в 13:26

Как указывал @MartinR, возврат значения nil вместо комбинации inout + Bool дает те же результаты, но с меньшим и более читаемым кодом. Это путь, по которому Свифт также пошел, импортируя большинство методов NSError ** из Objective-C (то есть отбросил последний параметр, импортировал их как функции с возможностью выброса).

При этом другой подход заключается в добавлении расширения к Array для извлечения значения:

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
0
Cristik 4 Мар 2018 в 13:27