Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unwrap an optional value from Any type?

Tags:

swift

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 

Update Progress...

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 
like image 844
mythz Avatar asked Jan 16 '15 17:01

mythz


People also ask

How does Swift handle optional value?

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

How do I force unwrap in Swift?

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

How do I remove optional text in Swift?

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)" .

Why use implicitly unwrapped optional?

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.


2 Answers

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.

like image 74
bubuxu Avatar answered Sep 26 '22 23:09

bubuxu


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.

Using Reflection

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 } 

Discussion

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:

  • I find returning NSNull() weird.
  • I think the alternative of returning 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)).
  • When calling 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.

Using an Extension on Optional

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

Discussion

This is bascially LopSae's solution updated to Swift 3. I also changed the precondition failure message and added unwrapUsingProtocol(_:).

Usage

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?

like image 38
thm Avatar answered Sep 25 '22 23:09

thm