2

I'm practising Swift by building trees with array.

I have a class Node. Each node has an ID and a parent ID. The parent ID could be nil, if the node is a top-level node. So I made parent ID Optional.

Then I manually (for testing purpose) created an array of nodes, which contains multiple trees. So there are multiple nodes with parent ID = nil.

Then I created a function which find nodes by their parent ID.

Compiler didn't complain. However when run in iPhone simulator (I tried to display the nodes in a tableview), a message displays at the if clause in the function:

fatal error: unexpectedly found nil while unwrapping an Optional value

Please help. Thanks.

Here is the code. (for my own habit I write ";" at the end of each line.)

class Node {
  var id:Int;
  var parent:Int!;
  init(id:Int, parent:Int!) {
    self.id = id;
    self.parent = parent;
  }
}

var allNodes = [
  Node(id:1, parent: nil),
  Node(id:2, parent: nil),
  Node(id:3, parent: 1),
  Node(id:4, parent: 1),
  Node(id:5, parent: 2),
  Node(id:6, parent: 2),
  Node(id:7, parent: 2)
];

func findNodes(parent:Int!) -> [Node] {
  var arr:[Node] = [];
  for node in allNodes {
    if node.parent == parent! {
      // if I use "if node.parent == parent" without the "!" it gives me the same error
      arr.append(node);
    }
  }
  return arr;
}

let nodes = self.findNodes(nil);
// called in tableview functions.
// same message if I use findNodes(1).
golddc
  • 468
  • 3
  • 12

1 Answers1

4

Your find function is declared as

func findNodes(parent:Int!) -> [Node]

where the parameter is declared as implicitly unwrapped optional, so it will be unwrapped on each access. If you want to pass nil as parameter, then a "normal" optional makes more sense:

func findNodes(parent:Int?) -> [Node] {
    var arr:[Node] = [];
    for node in allNodes {
        if node.parent == parent {
            arr.append(node);
        }
    }
    return arr;
}

Note that you can simplify the code to (Swift 1.2):

func findNodes(parent:Int?) -> [Node] {
    return filter(allNodes) { $0.parent == parent };
}

It also would make more sense to declare the parent property in the Node class as optional if nil is an "expected" value:

class Node {
    var id : Int;
    var parent : Int?;
    init(id:Int, parent : Int?) {
        self.id = id;
        self.parent = parent;
    }
}

Or perhaps a (optional) pointer to the parent node:

var parent : Node?

but that is now unrelated to your question.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thank you Martin R, this works for me! I'm new with Swift. I initially made it an implicitly unwrapped optional because I thought this would make it easier each time I access it. Let me ask another stupid question: does this mean Optional could be nil and implicitly unwrapped Optional shouldn't be nil? – golddc Sep 08 '15 at 15:48
  • And yes I wanted to use your last advice "var parent:Node?", but then found out I don't know how to store it in database if some day I need to, so I gave up and used the Int ID instead... – golddc Sep 08 '15 at 15:49
  • 1
    @golddc: Have a look at http://stackoverflow.com/questions/24006975/why-create-implicitly-unwrapped-optionals for the possible use-cases of implicitly unwrapped optionals. – Martin R Sep 08 '15 at 15:52
  • 1
    @golddc: Instead of `node.parent` you would store `nod.parent?.id`, just my 2ct. – Martin R Sep 08 '15 at 15:54