Search code examples
iosswiftnullforced-unwrapping

Getting value in a nested wrapped variable in Swift


Currently, I need this code to get my data to display on a UITableViewCell label:

guard let leaseUnits = Data.leaseUnits else { return cell; }
guard let leaseUnit = leaseUnits[indexPath.row] else { return cell; }
guard let unitId = leaseUnit.unitId else { return cell; }    
guard let units = Data.units else { return cell; }
guard let unit = units[unitId] else { return cell; }
(cell.viewWithTag(1)! as! UILabel).text = unit.unitNumber;

Or I can make it like this with the risk of null run time error:

let unitNumber = Data.units![Data.leaseUnits![indexPath.row]!.unitId]!.unitNumber;
(cell.viewWithTag(1)! as! UILabel).text = unitNumber; 

Is there any way I can make it to be like this:

let unitNumber = Data.units?[Data.leaseUnits?[indexPath.row]?.unitId]?.unitNumber;
if (unitNumber != nil) { (cell.viewWithTag(1)! as! UILabel).text = unitNumber!; }

I just want to get a value that if any of the chain data retrieval is nil, then just return a nil for the whole operation.

EDIT:

Okay, so from the comment of dfri: I can simplify it at least like this:

if let unitId = Data.leaseUnits?[indexPath.row]?.unitId {
    if let unitNumber = Data.units?[unitId]?.unitNumber {
        (cell.viewWithTag(1)! as! UILabel).text = unitNumber;
    }
}

I can't get more simple than that.

This is good enough I guess. Now I understand more of the limitation of the optional chaining.

EDIT 2:

So finally it can becomes like this, eliminating one nested block:

if let unitId = Data.leaseUnits?[indexPath.row]?.unitId, 
    let unitNumber = Data.units?[unitId]?.unitNumber {
    (cell.viewWithTag(1)! as! UILabel).text = unitNumber;
}

IMO, dfri should makes his comment as answer so I can accept it. :)


Solution

  • You can make use of optional chaining, as described in the Language Guide - Optional Chaining

    struct Bar {
        let baz: Int?
        init(baz: Int) { self.baz = baz }
    }
    
    struct Foo {
        let bar: Bar?
        init(bar: Bar) { self.bar = bar }
    }
    
    let bar: Bar? = Bar(baz: 42)
    let foo: Foo? = Foo(bar: bar!)
    
    if let innerBaz = foo?.bar?.baz { 
        print(innerBaz) // 42
    }
    

    Also, note that you needn't nest the two optional binding clauses (with two nested if statements), but can place them as two (or more) comma-separated optional bindings in the same if statement, where the first binded property (if successful) is available in the one following.

    /* ... as example above */
    
    let dict = [42: "foobar!"]
    
    if let innerBaz = foo?.bar?.baz, let dictValue = dict[innerBaz] { 
        print(dictValue) // foobar!
    }