Как развернуть необязательное значение из любого типа?
Учитывая массив [Any]
который имеет сочетание необязательных и необязательных значений, например:
let int:Int? = 1
let str:String? = "foo"
let values:[Any] = [int,2,str,"bar"]
Как мы можем извлечь значение Optional
в Any
введите (если есть), чтобы мы могли создать общую функцию печати, которая только распечатывает значения.
Например, эта функция printArray проходит и печатает каждый элемент:
func printArray(values:[Any]) {
for i in 0..<values.count {
println("value[\(i)] = \(values[i])")
}
}
printArray(values)
Который будет выводить:
value[0] = Optional(1)
value[1] = 2
value[2] = Optional("foo")
value[3] = bar
Как мы можем изменить его, чтобы он печатал только базовое значение, чтобы развернуть значение, если оно необязательное? например:
value[0] = 1
value[1] = 2
value[2] = foo
value[3] = bar
Обновление прогресса...
Может работать при изменении аргумента на [Any?]
Например:
let values:[Any?] = [int,2,str,"bar"]
func printArray(values:[Any?]) {
for i in 0..<values.count {
println("value[\(i)] = \(values[i]!)")
}
}
printArray(values)
Который напечатает желаемое:
value[0] = 1
value[1] = 2
value[2] = foo
value[3] = bar
Но все равно хотелось бы посмотреть, как мы можем развернуть Any
как это то, что MirrorType.value
возвращает, затрудняя извлечение значения Optional, например:
class Person {
var id:Int = 1
var name:String?
}
var person = Person()
person.name = "foo"
var mt:MirrorType = reflect(person)
for i in 0 ..< mt.count {
let (name, pt) = mt[i]
println("\(name) = \(pt.value)")
}
Распечатывает:
id = 1
name = Optional("foo")
Когда мне нужно:
id = 1
name = foo
10 ответов
Для Xcode 7 и Swift 2:
func unwrap(any:Any) -> Any {
let mi = Mirror(reflecting: any)
if mi.displayStyle != .Optional {
return any
}
if mi.children.count == 0 { return NSNull() }
let (_, some) = mi.children.first!
return some
}
let int:Int? = 1
let str:String? = "foo"
let null:Any? = nil
let values:[Any] = [unwrap(int),2,unwrap(str),"bar", unwrap(null)]
Это даст вам [1, 2, "foo", "bar", {NSObject}]
+ Изменить NSNull()
в nil
и возвращаемое значение unwrap func к Any?
всегда будет разворачивать любой тип.
Чтобы, возможно, спасти кого-то от объединения всего этого из ответов и комментариев, вот ответ, включающий оба "здравых" способа и кое-что, что я считаю улучшениями для Swift 3, поставляемой с Xcode 8.2.1.
Используя отражение
func unwrap<T>(_ any: T) -> Any
{
let mirror = Mirror(reflecting: any)
guard mirror.displayStyle == .optional, let first = mirror.children.first else {
return any
}
return first.value
}
обсуждение
Принятый ответ от bubuxu не компилируется с Swift 3. Как предполагает Walkline в своем комментарии, изменение .Optional
в .optional
исправляет это (см. SE-0005 и Руководство по разработке Swift API).
Причины, по которым я думал, что это решение может быть улучшено:
- Я нахожу возвращение
NSNull()
странно. - Я думаю, что альтернатива возвращения
nil
с типом возвратаAny?
также проблематично, потому что он превращает все (включая необязательные значения) в необязательные значения (например,unwrap(any: 42)
возвращаетсяOptional(42)
). - При звонке
unwrap(any:)
с чем угодно, кромеAny
значение (уже не кто-нибудь?) компилятор Swift 3 предупреждает о неявном принуждении кAny
,
Подобные мысли относятся к ответу Саджона.
Предлагаемое мной решение затрагивает все эти вопросы. Имейте в виду, однако, что unwrap(_:)
возвращается nil
как тип Any
так что использование оператора объединения nil больше не работает. Это означает, что это просто меняет то, что я считаю проблематичным по второму пункту. Но я обнаружил, что это как раз то, что нужно сделать для (для меня) более интересного варианта использования рефлексии.
Использование расширения по желанию
protocol OptionalProtocol {
func isSome() -> Bool
func unwrap() -> Any
}
extension Optional : OptionalProtocol {
func isSome() -> Bool {
switch self {
case .none: return false
case .some: return true
}
}
func unwrap() -> Any {
switch self {
case .none: preconditionFailure("trying to unwrap nil")
case .some(let unwrapped): return unwrapped
}
}
}
func unwrapUsingProtocol<T>(_ any: T) -> Any
{
guard let optional = any as? OptionalProtocol, optional.isSome() else {
return any
}
return optional.unwrap()
}
обсуждение
Это принципиально решение LopSae, обновленное до Swift 3. Я также изменил сообщение об ошибке предварительного условия и добавил unwrapUsingProtocol(_:)
,
использование
class Person {
var id:Int = 1
var name:String?
}
var person = Person()
person.name = "foo"
let mirror = Mirror(reflecting: person)
for child in mirror.children.filter({ $0.label != nil }) {
print("\(child.label!) = \(unwrap(child.value))")
}
Неважно, используете ли вы unwrap()
или же unwrapUsingProtocol()
, это напечатает
id = 1
name = foo
Если вы ищете способ аккуратно выровнять вывод, посмотрите, есть ли способ использовать вкладки для равномерного разметки строк описания в Swift?
Чтобы проверить, если Any
переменная является необязательной, протокол может использоваться как средство для необязательных необязательных.
Точно так же, как в настоящее время невозможно (начиная со Swift 2) проверять наличие необязательного необязательного параметра, также невозможно преобразовать в необязательный необязательный параметр:
let anyType: Any.Type = Optional<String>.self
let anyThing: Any = Optional.Some("string")
anyType is Optional.Type // Causes error
let maybeString = anything as? Optional // Also causes error
// Argument for generic parameter 'Wrapped' could not be inferred
Тем не менее, предложенный OptionalProtocol
также может использоваться для предоставления универсального интерфейса для доступа к необязательным значениям и даже для их развертывания:
protocol OptionalProtocol {
func isSome() -> Bool
func unwrap() -> Any
}
extension Optional : OptionalProtocol {
func isSome() -> Bool {
switch self {
case .None: return false
case .Some: return true
}
}
func unwrap() -> Any {
switch self {
// If a nil is unwrapped it will crash!
case .None: preconditionFailure("nill unwrap")
case .Some(let unwrapped): return unwrapped
}
}
}
// With this we can check if we have an optional
let maybeString: String? = "maybe"
let justString: String = "just"
maybeString is OptionalProtocol // true
justString is OptionalProtocol // false
С помощью предоставленных методов опциональные варианты можно проверить и получить к ним доступ совершенно естественным образом, не прибегая к невозможному приведению к Optional
:
let values:[Any] = [
Optional.Some(12),
2,
Optional<String>.None, // a "wrapped" nil for completeness
Optional.Some("maybe"),
"something"
]
for any in values {
if let optional = any as? OptionalProtocol {
if optional.isSome() {
print(optional.unwrap())
} else {
// nil should not be unwrapped!
print(optional)
}
continue
}
print(any)
}
Который напечатает:
12
2
nil
maybe
something
Небольшое изменение в @thm, чтобы полностью развернуть:
func unwrap<T>(_ any: T) -> Any {
let mirror = Mirror(reflecting: any)
guard mirror.displayStyle == .optional, let first = mirror.children.first else {
return any
}
return unwrap(first.value)
}
Я думаю, что это своего рода ошибка.
В общем, чтобы обнаружить и извлечь конкретный тип из Any
, литье с as
это единственный поддерживаемый метод. Но:
let int:Int? = 1
let any:Any = int
switch any {
case let val as Optional<Int>: // < [!] cannot downcast from 'Any' to a more optional type 'Optional<Int>'
print(val)
default:
break
}
Это означает, что нет поддерживаемого способа сделать это.
Во всяком случае, по-видимому, вы можете сделать это с reflect
:
func printArray(values:[Any]) {
for i in 0..<values.count {
var val = values[i]
var ref = reflect(val)
// while `val` is Optional and has `Some` value
while ref.disposition == .Optional && ref.count > 0 && ref[0].0 == "Some" {
// replace `val` with unwrapped value
val = ref[0].1.value;
ref = reflect(val)
}
println("value[\(i)] = \(val)")
}
}
let int:Int? = 1
let str:String? = "foo"
let values:[Any] = [int,2,str,"bar"]
printArray(values)
выходы:
value[0] = 1
value[1] = 2
value[2] = foo
value[3] = bar
ДОБАВЛЕНО: незначительная подправленная версия
func printArray(values:[Any]) {
for i in 0..<values.count {
var ref = reflect(values[i])
// while `val` is Optional and has `Some` value
while ref.disposition == .Optional && ref.count > 0 && ref[0].0 == "Some" {
// Drill down to the Mirror of unwrapped value
ref = ref[0].1
}
let val = ref.value
println("value[\(i)] = \(val)")
}
}
Факторинг в функцию:
func unwrapAny(val:Any) -> Any {
var ref = reflect(val)
while ref.disposition == .Optional && ref.count > 0 && ref[0].0 == "Some" {
ref = ref[0].1
}
return ref.value
}
func printArray(values:[Any]) {
for i in 0..<values.count {
println("value[\(i)] = \(unwrapAny(values[i]))")
}
}
Не полный ответ. Это сводится к этому:
let int:Int? = 1
let str:String? = "foo"
let values:[Any] = [int,2,str,"bar"]
func printArray(values:[Any]) {
for i in 0..<values.count {
let v = values[i]
if _stdlib_demangleName(_stdlib_getTypeName(v)) == "Swift.Optional" {
println("value[\(i)] = "it's optional: \(v)") // here I'm stuck
}else {
println("value[\(i)] = \(values[i])")
}
}
}
printArray(values)
Как насчет этого решения, я сделал общую версию предыдущего ответа.
fileprivate func unwrap<T>(value: Any)
-> (unwraped:T?, isOriginalType:Bool) {
let mirror = Mirror(reflecting: value)
let isOrgType = mirror.subjectType == Optional<T>.self
if mirror.displayStyle != .optional {
return (value as? T, isOrgType)
}
guard let firstChild = mirror.children.first else {
return (nil, isOrgType)
}
return (firstChild.value as? T, isOrgType)
}
let value: [Int]? = [0]
let value2: [Int]? = nil
let anyValue: Any = value
let anyValue2: Any = value2
let unwrappedResult:([Int]?, Bool)
= unwrap(value: anyValue) // ({[0]}, .1 true)
let unwrappedResult2:([Int]?, Bool)
= unwrap(value: anyValue2) // (nil, .1 true)
let unwrappedResult3:([UInt]?, Bool)
= unwrap(value: anyValue) // (nil, .1 false)
let unwrappedResult4:([NSNumber]?, Bool)
= unwrap(value: anyValue) ({[0]}, .1 false)
Ниже приведен код на игровой площадке.
На основе решения @bubuxu можно также:
func unwrap<T: Any>(any: T) -> T? {
let mirror = Mirror(reflecting: any)
guard mirror.displayStyle == .optional else { return any }
guard let child = mirror.children.first else { return nil }
return unwrap(any: child.value) as? T
}
Но вы должны проверить на ноль, используя ?? nil
когда используешь unwrap
как сделано в foo
func foo<T>(_ maybeValue: T?) {
if let value: T = unwrap(any: maybeValue) ?? nil {
print(value)
}
}
Все еще опрятный все же!
(Кто-нибудь получил решение для ?? nil
проверять?)
Согласно шаблонам примеров использования Enumeration в Swift 2.0 они могут выглядеть следующим образом:
let pattern :[Int?] = [nil, 332, 232,nil,55]
for case let number? in pattern {
print(number)
}
Выход: 332, 232, 55
Не усложняя, почему бы и нет
let int:Int? = 1
let str:String? = "foo"
let values:[Any?] = [int,2,str,"bar"]
for var i:Int = 0; i < values.count; i++
{
println("\(values[i]!)")
}
Это печатает:
1
2
Foo
бар