2017-01-29 4 views
3

Я получаю набор целых чисел через ответ api json (который я конвертирую в словарь [String: Any]). Эти целые числа гарантированно находятся в диапазоне 10 ... 10 (включительно). В моей модели я хотел бы сохранить их как Int8.Безопасное литье из Int в Int8 в Swift

Вот как я конвертирую свой json-словарь в объект модели, но мне интересно, есть ли более идиоматический способ обеспечения того, чтобы ints в json действительно вписывался в Int8?

func move(with dict: JSONDictionary) -> Move? { 
    if let rowOffset = dict[JSONKeys.rowOffsetKey] as? Int, 
     let colOffset = dict[JSONKeys.colOffsetKey] as? Int { 

     if let rowInt8 = Int8(exactly: rowOffset), 
     let colInt8 = Int8(exactly: colOffset) { 

     return Move(rowOffset: rowInt8, colOffset: colInt8) 
     } 
     else { 
     print("Error: values out of range: (row: \(rowOffset), col: \(colOffset)") 
     } 
    } else { 
     print("Error: Missing key, either: \(JSONKeys.rowOffsetKey) or \(JSONKeys.colOffsetKey)") 
    } 

    return nil 
    } 

Обратите внимание, что, выполнив следующие действия всегда терпит неудачу, независимо от стоимости входящих Интс:

if let rowOffset = dict[JSONKeys.rowOffsetKey] as? Int8, 
    let colOffset = dict[JSONKeys.colOffsetKey] as? Int8 { 
... 

Это, как я преобразование входящего JSON в словарь. Json, о котором идет речь, глубоко вложен и содержит несколько разных типов.

typealias JSONDictionary = [String: Any] 
let jsonDict = try JSONSerialization.jsonObject(with: data) as? JSONDictionary 
+1

Не могли бы вы показать, как вы создаете 'JSONDictionary'? Литой 'как? Int8' должен работать нормально, если значения 'NSNumber', которые не исходят от Swift (не' Int8') до моста Obj-C, хотя для 'NSNumber' со значениями вне диапазона для' Int8', дополнительные биты будут усечены. – Hamish

+0

Обновлено, чтобы ответить на ваш вопрос. – RobertJoseph

+1

Да, странно, что 'как? Int' тогда не работает в вашем случае (хотя в любом случае вы заявили, что не хотите усекать поведение, поэтому вы все равно не захотите его использовать), но [этот пример демонстрирует] (https: //gist.github.com/hamishknight/5a673c23c5346c420588e9dd7d171228), что бросок должен работать. – Hamish

ответ

6

Вот некоторые варианты для конвертирования Int (который исходит от NSNumber) в Int8:

Просто преобразование с Int8 «s инициализатором init(_: Int)

Если Int значения гарантировал, чтобы вписаться в Int8, а затем преобразовать их с Int8(value) в порядке.

Если вы получаете Int, который не помещается в Int8, программа зависнет:

let i = 300 
let j = Int8(i) // crash! 

Инициализация с Int8 «s инициализаторе init(truncatingIfNeeded: BinaryInteger)

Для немного дополнительной безопасности, вы должны использовать инициализатор init(truncatingIfNeeded: BinaryInteger):

let i = 300 
let j = Int8(truncatingIfNeeded: i) // j = 44 

Это генерирует измененные значения, которые могут быть нежелательны, но это предотвратило бы сбой.

Явной проверка диапазона допустимых значений

В качестве другой альтернативы, вы можете просто проверить диапазон явно:

if (-10...10).contains(i) { 
    j = Int8(i) 
} else { 
    // do something with the error case 
} 

Преимуществом проверки является то, что вы можете указать допустимый диапазон вместо просто обнаруживая ошибку, когда диапазон превышает значения, которые будут соответствовать Int8.

Инициализация с Int8 «s инициализатор init(exactly: Int)

Ваш код в настоящее время использует этот метод безопасной инициализации Int8 значения. Это надежное, так как оно вернет nil, если значение не соответствует Int8.Таким образом, можно проверить с опциональным связывания, как вы делаете, или он может быть объединен с нолем коалесцирующего оператором?? предоставить значения по умолчанию, если у вас есть подходящие из них:

// Determine Int8 value, use 0 if value would overflow an Int8 
let j = Int8(exactly: i) ?? 0 

Непосредственно литейных NSNumber с as Int8 в Swift 3.0.1 и выше

Как @Hamish и @OOPer упоминается в комментариях, теперь можно бросить NSNumber непосредственно Int8.

let i: NSNumber = 300 
let j = i as Int8 // j = 44 

Это имеет тот же эффект, как и усечения с помощью init(truncatingIfNeeded: BinaryInteger).

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

+0

В настоящее время api, который я использую, возвращает только значения в диапазоне от -10 ... 10, но это может измениться в будущем. Не будет '(Int8.min ... Int8.max). Содержит (i)' лучше? Кроме того, я не уверен, почему 'init (truncatingBitPattern: Int)' будет лучше/безопаснее, чем 'Int8 (точно: Int)', так как я не хочу усекать поведение. – RobertJoseph

+2

Я обновил свой ответ. Он содержит информацию из комментариев. – vacawama

+1

Большое вам спасибо - это фантастический ресурс для других, кто, возможно, задавался вопросом, почему основной (интуитивный) 'Int8' листинг терпит неудачу. Лично я обновился до Swift 3.0.1. – RobertJoseph