Given an array of [Any]
that has a mix of optional and non optional values, e.g:
let int:Int? = 1 let str:String? = "foo" let values:[Any] = [int,2,str,"bar"]
How can we extract the value of the Optional
in the Any
type (if there is one) so we can create a generic print function that only prints out the values.
E.g. this printArray function goes through and prints each element:
func printArray(values:[Any]) { for i in 0..<values.count { println("value[\(i)] = \(values[i])") } } printArray(values)
Which will output:
value[0] = Optional(1) value[1] = 2 value[2] = Optional("foo") value[3] = bar
How can we change it so it only prints the underlying value so that it unwraps the value if it's Optional? e.g:
value[0] = 1 value[1] = 2 value[2] = foo value[3] = bar
It can work when changing the argument to [Any?]
, e.g:
let values:[Any?] = [int,2,str,"bar"] func printArray(values:[Any?]) { for i in 0..<values.count { println("value[\(i)] = \(values[i]!)") } } printArray(values)
Which will print the desired:
value[0] = 1 value[1] = 2 value[2] = foo value[3] = bar
But would still like to see how we can unwrap an Optional from Any
as this is what MirrorType.value
returns making it difficult to extract the Optional value, e.g:
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)") }
Prints out:
id = 1 name = Optional("foo")
When I need:
id = 1 name = foo
To use an optional, you "unwrap" it An optional String cannot be used in place of an actual String . To use the wrapped value inside an optional, you have to unwrap it. The simplest way to unwrap an optional is to add a ! after the optional name. This is called "force unwrapping".
Even though Swift isn't sure the conversion will work, you can see the code is safe so you can force unwrap the result by writing ! after Int(str) , like this: let num = Int(str)! Swift will immediately unwrap the optional and make num a regular Int rather than an Int? .
If my assumption is incorrect, then one quick hack you can quickly do to get rid of the "Optional" in the text is to change your strings to force unwrap item , like so: theYears. text = "\(item!. experience)" .
Implicitly unwrapped optionals are useful when an optional's value is confirmed to exist immediately after the optional is first defined and can definitely be assumed to exist at every point thereafter.
For Xcode 7 and 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)]
This will give you [1, 2, "foo", "bar", {NSObject}]
Change NSNull()
to nil
and the return value of unwrap func to Any?
will always unwrap any type.
To maybe save somebody from cobbling it all together from the answers and comments, here is an answer including both "sane" ways and some what I consider to be improvements for Swift 3 coming with 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 }
The accepted answer from bubuxu fails to compile with Swift 3. As walkline suggests in his comment, changing .Optional
to .optional
fixes this (see SE-0005 and Swift API Design Guidelines).
Reasons I thought this solution can be improved:
NSNull()
weird.nil
with return type Any?
is also problematic because it turns everything (including non-optional values) into optional values (e.g. unwrap(any: 42)
returns Optional(42)
).unwrap(any:)
with anything but an Any
value (any more any anybody?) the Swift 3 compiler warns about implicitly coercing to Any
.Similiar thoughts apply to Sajjon's answer.
The solution I suggest addresses all those points. Be aware however that unwrap(_:)
returns nil
as type Any
so using the nil coalescing operator does not work anymore. This means that this just shifts around what I think is problematic about the second point. But I found this to be just the right thing to do for the (to me) more interesting use case regarding reflection.
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() }
This is bascially LopSae's solution updated to Swift 3. I also changed the precondition failure message and added 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))") }
No matter if you're using unwrap()
or unwrapUsingProtocol()
, this will print
id = 1 name = foo
If you're looking for a way to neatly align the output, see Is there a way to use tabs to evenly space out description strings in Swift?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With