Search code examples
swiftoptional-binding

swift: safely access dictionary of SCNNode arrays


I'm relatively new to Swift and have yet to master the safety aspects of optionals.

I have a dictionary of type [String: [SCNNode]]. A given molecule will have multiple components as elements in an [SCNNode]. For that molecule I retrieve this array of components and assign each element to a local SCNNode to be displayed, manipulated and animated.

    let components = moleculeDictionary["aceticAcid"]     // the array of components
        // [baseMolecule, hydrogenCharge, oxygenCharge, ionizingH, ionizedBond, bonds]

    atomsNode_1 = components![0]            // baseMolecule
    baseNode.addChildNode(atomsNode_1)

    atomsNode_5 = components![3]            // ionizingH
    atomsNode_1.addChildNode(atomsNode_5)

   // etc.

In an attempt to optionally bind this, the compiler seems happy with this.

    if let node = components?[0]  {          // baseMolecule
        baseNode.addChildNode(node)
    }

I'm unclear on the ?. My reading on this suggests we're unwrapping in such a way that we don't care if there's a nil. But does that make this optional binding any better that the forced unwrapping above? Or is this "optional chaining"? Should I be looking instead to just do a check when I assign components? Should I even be concerened about safety here? The only "upsteam" test I've done is for the presence of the dictionary archive before assigning it to moleculeDictionary.

I will have hundreds of these assignments so I'd like to get this right. Suggestions on the best way to handle this are welcome!

Thanks, Byrne


Solution

  • In my opinion, you should be worried about safety in Swift. Given your code above I would rewrite it in the first pass as:

    guard let components = moleculeDictionary["aceticAcid"] where components.count > 3 else { // handle the case where there is no value for this key, or not enough nodes }
    
    // Now I know that I have the correct number of components I don't need to  force unwrap:
    
    atomsNode_1 = components[0]            // baseMolecule
    baseNode.addChildNode(atomsNode_1)
    
    atomsNode_5 = components[3]            // ionizingH
    atomsNode_1.addChildNode(atomsNode_5)
    

    That's the first first pass. The second pass would be to make sure that I have values for all the components would be to write a struct to contain the SCNNode values so that I either had a value or a nil for each node, you could then build up your node structure accordingly.

    Edited to add

    Here's an indication of the second pass - which really needs more domain knowledge than I have, but it's a start.

    It seems you build up the molecules from [[SCNNode]] (an array of arrays of SCNNodes where the position of each subarray is significant. It's worth putting this into it's own structure such that:

    struct Molecule {
        let baseMolecule: [SCNNode]?
        let hydrogenCharge: [SCNNode]?
        let oxygenCharge: [SCNNode]?
        let ionizingH: [SCNNode]?
        let ionizedBond: [SCNNode]?
        let bonds: [SCNNode]?
    
        // Other methods can be written to build up atom nodes from within the struct which handle the cases where the component array is nil
    }