2016-12-17 7 views
1

Swift 3.Swift Generic «Числовой» Протокол, который Подтверждает числовые значения

В конце концов мои функции должны получить UInt8 типы данных, но я никогда не уверен, что если аргументы я буду получать от абонентов будут Int, Int64, UInt, Float и т. Д. Я знаю, что они будут цифровых типов, я просто не знаю, какой вкус.

я мог бы сделать:

func foo(value: Int) { } 
func foo(value: Float) {} 
func foo(value: UInt) {} 

Но это безумие. Так я думал, что я мог бы сделать что-то вроде создать протокол

protocol ValidEncodable { 
} 

А затем передать типы, которые соответствуют:

func foo(value: ValidEncodable) { } 

И тогда в этой функции я могу получить мои ценности в правильный формат

func foo(value: ValidEncoable) -> UInt8 { 
    let correctedValue = min(max(floor(value), 0), 100) 
    return UInt8(correctedValue) 
} 

Я действительно изо всех сил, чтобы выяснить

1) Как создать этот Вали dEncodable протокола, который содержит все числовые типов

2) И как сделать такие вещи, как пол (значение), когда значение, которое я получаю в Int без перебора всех возможных числовых типов (т.е. (пол (x) доступен только для типов с плавающей точкой)

В конечном счете мне нужны значения UInt8 в диапазоне 0-100. Вся причина этого безумия заключается в том, что я разбираю XML-файлы в своих собственных внутренних структурах данных, и я хочу испечь некоторые проверки для моих значений.

ответ

0

Это можно сделать без протокола и с помощью проверок компилятора, что значительно уменьшает количество ошибок.

Моя рекомендация состоит в использовании частичной функции, то есть функции, которая вместо принятия int принимает уже проверенное значение. Проверьте this article для более подробного описания того, почему частичные функции отличные.

Вы можете построить на структуру Int0to100, которая имеет либо failable или метательного инициализатору (в зависимости от вкуса):

struct Int0to100 { 
    let value: UInt8 

    init?(_ anInt: Int) { 
     guard anInt >= 0 && anInt <= 100 else { return nil } 
     value = UInt8(anInt) 
    } 

    init?(_ aFloat: Float) { 
     let flooredValue = floor(aFloat) 
     guard flooredValue >= 0 && flooredValue <= 100 else { return nil } 
     value = UInt8(flooredValue) 
    } 

    // ... another initializers can be added the same way 
} 

и изменить foo, чтобы называться с этим аргументом вместо:

func foo(value: Int0to100) { 
    // do your stuff here, you know for sure, at compile time, 
    // that the function can be called with a valid value only 
} 

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

Еще один важный аспект заключается в том, что вы явно объявляете домен функции foo, что улучшает общий дизайн кода.

И не последнее, принудительное исполнение, установленное во время компиляции, значительно снижает вероятность возникновения проблем времени исполнения.

+0

Отличная статья! Спасибо! – MH175

+0

Это отлично работает! Еще раз спасибо. – MH175

0

Если вы знаете входящие значения будут лежать в 0 .. < 256, то вы можете просто построить UInt8 и передать его функции.

func foo(value: UInt8) -> UInt8 { return value } 
let number = arbitraryNumber() 
print(foo(UInt8(number))) 

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

+0

Спасибо! Это одна из проблем. Я не знаю, какую ценность я получу. Иногда я получаю -256, иногда я получаю 10,5. Я должен всегда проверять переполнение. Возможно, я мог бы переписать как – MH175

+0

Вот почему я сказал, что вы можете добавить проверку границ между второй и третьей строками. Вы можете что-то сделать (используйте значение по умолчанию, выйдите раньше), если 'number' не лежит в' 0 ... 100'. – BallpointBen