How to get the unwrapped type from an optional type in Swift? - swift

I'm trying to get an unwrapped type from an optional type in runtime.
The following code would print the type of a as Optional<String>.
class MySubClass: MyClass {
var a: String? = nil
}
var a = MySubClass()
let mirror = Mirror(reflecting: a)
for child in mirror.children {
print(child.value.dynamicType)
}
Now I want to unwrap the type and get String, what should I do to make this happen in runtime?

Assuming you have an optional
let someVar: String?
then print(type(of: someVar)) will print
Optional<String>
but if you add the following extension to Optional
protocol OptionalProtocol {
func wrappedType() -> Any.Type
}
extension Optional: OptionalProtocol {
func wrappedType() -> Any.Type {
return Wrapped.self
}
}
then print(someVar.wrappedType()) will print
String
No reflection whatsoever
Summary
As long as the optional is not referenced by Any or AnyObject the code will work fine.
For Any you will have to cast it to OptionalProtocol first. Running
let someVar: String?
let anyVar = someVar as Any
if let op = anyVar as? OptionalProtocol {
print(op.wrappedType())
}
will print
String
As for AnyObject, strangely enough (at least for me), it doesn't cast to OptionalProtocol.
The original StackOverflow answer can be found here

I played with your idea a little bit, but I think there isn't a real way to do that, since you can't get the type of the associated value of an enumeration, yet. Hence Optionals are basically Enumerations, we have a problem here.
My idea would be to test for all possible value types in your model objects could be or hold. Like:
let myModelObject:Any? = someWayToGetTheData()
if let aString = myModelObject as? String {
// do everything you need to store a string
}
else if let anInteger = myModelObject as? Int {
// do everything you need to store an integer
}
// and so on ...
Since your json and your model must have a predefined number of supported conversions that is a possible way, and as far as I understand your original problem, it's basically as useful as testing for the dynamic associated value type of an Optional Enumeration, which will lead into a chain of if-else statements as well.

You can either unwrap the optional explicitly with a bang (!) or with an if let.
For example:
var foo: String? = nil
if foo == nil {
print("foo is nil")
foo = "bar"
}
let fooBang = foo!
print("fooBang: \(fooBang)")
if let ifLetFoo = foo {
print("ifLetFoo: \(ifLetFoo)")
}
This will print:
foo is nil
fooBang: bar
ifLetFoo: bar
In your context, I think print(child.value.dynamicType!) might be what you're looking for.

If you cast the value to the non-optional String, it will print you the unwrapped type.
let mirror = Mirror(reflecting: a)
for child in mirror.children {
print(String(child.value).dynamicType) //String
}
Or you can play with the String and get the type from the Optional type.
class MySubClass: MyClass {
var a: Int? = nil
}
var a = MySubClass()
let mirror = Mirror(reflecting: a)
for child in mirror.children {
let typeArr = String(child.value.dynamicType).characters.split{$0 == "<"}.map(String.init)
let typeArr2 = typeArr[1].characters.split{$0 == ">"}.map(String.init)
print(typeArr2[0]) // print: Int
}
Sorry this is lame but you can do something like this.

Related

Swift enum custom raw type not expressible by different literals

I was reading through the wonderful blog post by Jon Sundell where he is trying to demonstrate how one can use custom raw values with Swift Enums.
I had a play around with his code and made up a bit of a contrived example to see how robust the Enums with custom raw types are.
I wondered if I can make a type, instance of which can be used as a raw value whilst being expressible by both String and Integer literals. When I implemented ExpressiblebyIntegerLiteral the String part did not work the way I expected and I'm not quite sure why.
Here is the code (the question follows the code):
import Foundation
struct Path: Equatable {
var string: String
var int: Int
}
enum Target: Path {
case content
case resources
case images = "resources/images"
}
extension Path: ExpressibleByIntegerLiteral, ExpressibleByStringLiteral {
init(stringLiteral: String) {
string = stringLiteral
int = 0
}
init(integerLiteral: Int) {
string = ""
int = integerLiteral
}
}
if let stringTarget = Target(rawValue: "content") {
print("stringTarget: \(stringTarget)")
}
if let intTarget = Target(rawValue: 1) {
print("intTarget: \(intTarget)")
}
What I expected the above code to produce is both stringTarget and intTarget being initialized to appropriate Enum cases, however, the stringTarget one turns out to be nil.
If you remove the conformance to ExpressibleByIntegerLiteral protocol (and the appropriate block of code which initializes intTarget), the stringTarget automagically gets initialized as I expected: into Target.string case.
Could someone please explain to me what is going on here?
Solving your Question
What I expected the above code to produce is both stringTarget and intTarget being initialized to appropriate Enum cases, however, the stringTarget one turns out to be nil.
They aren't nil. They are this: ""
This happens because both the .content and .resources cases are not explicitly defined by a String. And because of this, they both take the ExpressibleByIntegerLiteral route, and are hence defined as this ""
init(integerLiteral: Int) {
string = "" // see
int = integerLiteral
}
Solved for Int
Use this fancy method in place of IntValue(rawValue: 1):
func IntValue(_ this: Int) -> String? {
return Target(rawValue: 0) != nil ? String(describing: Target(rawValue: 0)!) : nil
}
Solved for String
First, conform your enum to CaseIterable, like so:
enum Target: Path, CaseIterable {
Next, use this fancy method in place of Target(rawValue: "content"):
func StringValue(_ this: String) -> String? {
return Target.allCases.contains { this == String(describing: $0) } ? this : nil
}
Truly solved for String
Now, I've removed a crucial bug where case foo = "bar" can be found both as 'foo' or 'bar'. If you don't want this, use this code instead:
func StringValue(_ this: String) -> String? {
var found: String? = nil
_ = Target.allCases.filter {
if let a = Target(rawValue: Path.init(string: this, int: 0)) {
found = String(describing: a)
return this == String(describing: a)
}
found = String(describing: $0)
return this == String(describing: $0)
}
return found
}
Custom Raw Values for Enums
Here's a quick tutorial:
I am wondering if enum can conform it's rawValue to the ClosedRange struct, just like it can conform to String.
enum Foo: ClosedRange<Int> {
case bar = 1...4
}
Obviously this is not an option, since 1...4 is not a literal
This seems to work:
enum Foo: ClosedRange<Int> {
case foo = "1...3"
case bar = "1...4"
func overlaps(_ with: Foo) -> Bool { return self.rawValue.overlaps(with.rawValue) }
}
extension ClosedRange: ExpressibleByStringLiteral {
public typealias StringLiteralType = String
public init(stringLiteral value: String) {
let v = value.split(separator: ".")
switch Bound.self {
case is Int.Type: self = (Int(v[0])! as! Bound)...(Int(v[1])! as! Bound)
default: fatalError()
}
}
}
It allows you to do this:
print(Foo.foo.overlaps(Foo.bar))
You can add more types like Double or String using this technique
Side Note: My attempt allows for non-unique rawValues (SR-13212) which is a shame. But I'm not thinking that is fixable:
enum Foo: ClosedRange<Int> {
case foo = "1...3"
case bar = "1...4"
case bar = "1...04" // Duplicate, but Swift allows it.
}

Unwrap/coalesce a multi-level optional

I'm trying to write a function to unwrap optionals with an arbitrary number of levels of nesting. Here's the test I'm using:
let a: Int??? = 1
let b: Int??? = nil
print(a.unwrap(0), b.unwrap(0)) // should print 1, 0
I can get the correct output with a basic generic function:
extension Optional {
func unwrap<T> (_ defaultValue: T) -> T {
return (self as? T) ?? defaultValue
}
}
print(a.unwrap(0), b.unwrap(0)) // 1, 0
But this doesn't prevent the function from being called with a different type than the optional. For instance, I could call a.unwrap("foo") and it would print "foo" instead of "1" since of course you can't cast Int??? to String.
I tried it using Wrapped instead, which semi-properly restricts the default value but doesn't give the correct output:
extension Optional {
func unwrap (_ defaultValue: Wrapped) -> Wrapped {
return (self as? Wrapped) ?? defaultValue
}
}
print(a.unwrap(0), b.unwrap(0)) // Optional(Optional(1)), nil
It only unwraps one level of the optional, instead of all three, and since nil is a valid value for Int?? it doesn't return the default.
Is there any way to safely do what I want here?
This code does what you ask for. The drawback is that you need to implement the Unwrappable protocol in every type you want to put inside optionals. Maybe Sourcery can help with that.
protocol Unwrappable {
associatedtype T
func unwrap(_ default: T) -> T
}
extension Optional {}
extension Optional: Unwrappable where Wrapped: Unwrappable {
typealias T = Wrapped.T
func unwrap(_ defaultValue: T) -> T {
if let value = self {
return value.unwrap(defaultValue)
}
return defaultValue
}
}
extension Int: Unwrappable {
typealias T = Int
func unwrap(_ default: Int) -> Int {
return self
}
}
let nestedOptionalValue: Int??? = 6
let nestedOptionalNil: Int??? = nil
let optionalValue: Int? = 6
let optionalNil: Int? = nil
print(nestedOptionalValue.unwrap(0)) // prints 6
print(nestedOptionalNil.unwrap(0)) // prints 0
print(optionalValue.unwrap(0)) // prints 6
print(optionalNil.unwrap(0)) // prints 0
The trick is that the Unwrappable protocol marks types that can be eventually unwrapped (as in after 0, 1 or more unwraps) to a certain type.
The difficulty in this problem comes from the fact that in an extension to Optional, you can get the Wrapped type, but if Wrapped is optional again, you can't access Wrapped.Wrapped (in other words, Swift doesn't support Higher Kinded Types).
Another approach, would be to try to add an extension to Optional where Wrapped == Optional. But again you'd need Higher Kinded Types support to access the Wrapped.Wrapped type. If we try to extend Optional where Wrapped == Optional<T>, we also fail, because we cant extend Optional generically over T.

A single expression for guard case let with type casting of the associated type?

I have the following enum:
enum JSONData {
case dict([String:Any])
case array([Any])
}
I would like to do a pattern matching assignment with type casting using guard case let. I not only want to make sure that someData is .array, but that its associated type is [Int] and not just [Any]. Is this possible with a single expression? Something like the following:
let someData: JSONData = someJSONData()
guard case let .array(anArray as [Int]) = someData
else { return }
But the above does not compile; the error is downcast pattern value of type '[Int]' cannot be used. I know this is possible with the following but I'd rather do it in a single expression if possible.
guard case let .array(_anArray) = someData, let anArray = _anArray as? [Int]
else { return }
Edit on April 27, 2018: An update to this situation: you may now use the following syntax as long as the cast type is not a Collection:
enum JSONData {
case dict([String:Any])
case array(Any) // Any, not [Any]
}
func f() {
let intArray: JSONData = .array(1)
guard case .array(let a as Int) = intArray else {
return
}
print("a is \(a)")
}
f() // "a is 1"
If you attempt to use a collection type such as [Int], you receive the error error: collection downcast in cast pattern is not implemented; use an explicit downcast to '[Int]' instead.
What you're trying to do is not possible due to a limitation in Swift's pattern matching implementation. It's actually called out here:
// FIXME: We don't currently allow subpatterns for "isa" patterns that
// require interesting conditional downcasts.
This answer attempts to explain why, though falls short. However, they do point to an interesting piece of information that makes the things work if you don't use a generic as the associated value.
The following code achieves the one-liner, but loses some other functionality:
enum JSONData {
case dict(Any)
case array(Any)
}
let someData: JSONData = JSONData.array([1])
func test() {
guard case .array(let anArray as [Int]) = someData else {
return
}
print(anArray)
}
Alternatively, the same one liner can be achieved through a utility function in the enum definition that casts the underlying value to Any. This route preserves the nice relationship between the cases and the types of their associated values.
enum JSONData {
case dict([String : Any])
case array(Array<Any>)
func value() -> Any {
switch self {
case .dict(let x):
return x
case .array(let x):
return x
}
}
}
// This coercion only works if the case is .array with a type of Int
guard let array = someData.value() as? [Int] else {
return false
}

Swift: Cast array of objects to array of sub type

Say I have an array of Animals and I'd like to cast it to an array of Cats. Here, Animal is a protocol that Cat adopts. I'd like something like let cats: [Cat] = animals as! [Cat] but this seg faults in compilation (btw I'm on both Linux Swift 3 and Mac Swift 2.2). My workaround is to just create a function that downcasts each item individually and adds it to a new array (see small example below). It produces the desired result, but isn't as clean as I'd like.
My questions are:
is this totally dumb and I'm just missing an easier way to do this?
how can I pass a type as the target parameter in the function below, rather than passing an instance? (e.g. I'd like to pass Cat.self rather than Cat(id:0) but doing so causes an error saying cannot convert Cat.Type to expected argument type Cat)
Here's what I have so far:
protocol Animal: CustomStringConvertible
{
var species: String {get set}
var id: Int {get set}
}
extension Animal
{
var description: String
{
return "\(self.species):\(self.id)"
}
}
class Cat: Animal
{
var species = "felis catus"
var id: Int
init(id: Int)
{
self.id = id
}
}
func convertArray<T, U>(_ array: [T], _ target: U) -> [U]
{
var newArray = [U]()
for element in array
{
guard let newElement = element as? U else
{
print("downcast failed!")
return []
}
newArray.append(newElement)
}
return newArray
}
let animals: [Animal] = [Cat(id:1),Cat(id:2),Cat(id:3)]
print(animals)
print(animals.dynamicType)
// ERROR: cannot convert value of type '[Animal]' to specified type '[Cat]'
// let cats: [Cat] = animals
// ERROR: seg fault
// let cats: [Cat] = animals as! [Cat]
let cats: [Cat] = convertArray(animals, Cat(id:0))
print(cats)
print(cats.dynamicType)
Am I missing an easier way to do this?
You can use map to make the conversion:
let cats: [Cat] = animals.map { $0 as! Cat }
how can I pass a type as the target parameter in the function below, rather than passing an instance?
First, you need to remove the instance parameter:
func convertArray<T, U>(array: [T]) -> [U] {
var newArray = [U]()
for element in array {
guard let newElement = element as? U else {
print("downcast failed!")
return []
}
newArray.append(newElement)
}
return newArray
}
Since you cannot specify type parameters explicitly, you need to provide the compiler with some info to deduce the type of U. In this case, all you need to do is to say that you are assigning the result to an array of Cat:
let cats: [Cat] = convertArray(animals)
As of Swift 4.1 using compactMap would be the preferred way, assuming you don't want the method to completely fail (and actually crash) when you have any other Animal (for example a Dog) in your array.
let animals: [Animal] = [Cat(id:1),Dog(id:2),Cat(id:3)]
let cats: [Cat] = animals.compactMap { $0 as? Cat }
Because compactMap will purge any nil values, you will end up with an array like so:
[Cat(1), Cat(3)]
As a bonus, you will also get some performance improvement as compared to using a for loop with append (since the memory space is not preallocated; with map it automatically is).

Swift Cast Generics Type

I'm trying to cast a generic type to its super class.
class Foo : NSObject {
}
class Test<T> {
let value: T
init(_ value: T) {
self.value = value
}
}
let c = Test(Foo())
let v = c as Test<AnyObject>
But on the line
let v = c as Test<AnyObject>
I get 'Foo' is not identical to 'AnyObject'
While I can do that with built-in Arrays
let array1 = [Foo(), Foo()]
let array2 = array1 as [AnyObject]
Arrays are special-cased in Swift to be able to have this behaviour. You won't be able to replicate it with your own classes, unfortunately.
To get similar behaviour, you could give your type another init method:
class Test<T> {
let value: T
init(_ value: T) {
self.value = value
}
init<U>(other: Test<U>) {
// in Swift 1.2 you will need as! here
self.value = other.value as T
}
}
let c = Test(Foo())
let v = Test<AnyObject>(other: c) // this will work ok
Note though that this is unsafe (i.e. will assert at runtime) in cases where you want to convert to an incompatible type (and the alternative, a failable initializer, will also have complications)