swiftoption-typeswift-keypath

Convert an optional Type to non-optional Type


The following code throws a compile error:

import UIKit

class Initable {
    required init() {}
}

class NestedObject:Initable {
    var nestedProp:String? = nil
}

class MyObj {
    var name:String? = nil
    var value:Int? = nil
    var otherVals:[String]? = nil
    var nestedObj:NestedObject? = nil
}

let obj = MyObj()
var nestedObj = obj[keyPath: \MyObj.nestedObj]
if(nestedObj == nil) {
    nestedObj = type(of: obj[keyPath: \MyObj.nestedObj]).init()
}
nestedObj![keyPath: \NestedObject.nestedProp] = "NEST ME BABY!!"
obj[keyPath: \MyObj.nestedObj] = nestedObj!

obj

This is due to the fact that type(of: obj[keyPath: \MyObj.nestedObj]) is NestedObject?, which of course cannot be initialized. I need a way to unwrap the optional typing and get the non-optional typing NestedObject.

Assumptions that have to be maintained.

This is a small part of a larger system, so the above assumptions must be considered. I cannot change those.


Solution

  • You can use your optional Wrapped type to initialize your nested object:

    let obj = MyObj()
    let nestedObj = type(of: obj[keyPath: \MyObj.nestedObj]).Wrapped()
    nestedObj[keyPath: \NestedObject.nestedProp] = "NEST ME BABY!!"
    
    print(nestedObj.nestedProp ?? "")  // "NEST ME BABY!!"
    

    If you want the resulting object to be optional as well:

    let obj = MyObj()
    var nestedObj = obj[keyPath: \MyObj.nestedObj]
    if nestedObj == nil {
        nestedObj = type(of: obj[keyPath: \MyObj.nestedObj]).Wrapped()
    }
    nestedObj?[keyPath: \NestedObject.nestedProp] = "NEST ME BABY!!"
    

    You need to add those helpers:

    protocol AnyOptional {
        associatedtype Wrapped
        var optional: Optional<Wrapped> { get }
    }
    
    extension Optional: AnyOptional {
        var optional: Optional<Wrapped> { self }
    }