Optional array vs. empty array in Swift

19,835

Solution 1

The ability to choose between an empty array or an optional gives us the ability to apply the one that better describe the data from a semantic point of view.

I would choose:

  • An empty array if the list can be empty, but it's a transient status and in the end it should have at least one element. Being non optional makes clear that the array should not be empty
  • An optional if it's possible for the list to be empty for the entire life cycle of the container entity. Being an optional makes clear that the array can be empty

Let me make some examples:

  • Purchase order with master and details (one detail per product): a purchase order can have 0 details, but that's a transient status, because it wouldn't make sense having a purchase order with 0 products
  • Person with children: a person can have no children for his entire life. It is not a transient status (although not permanent as well), but using an optional it's clear that it's legit for a person to have no children.

Note that my opinion is only about making the code more clear and self-explainatory - I don't think there is any significant difference in terms of performance, memory usage, etc. for choosing one option or the other.

Solution 2

I'm going to make the opposite case from Yordi - an empty array just as clearly says "this Person has no children", and will save you a ton of hassle. children.isEmpty is an easy check for the existence of kids, and you won't ever have to unwrap or worry about an unexpected nil.

Also, as a note, declaring something as optional doesn't mean it takes zero space - it's the .None case of an Optional<Array<Person>>.

Solution 3

Interestingly enough, we have recently had few discussions regarding this very same question at work.

Some suggest that there are subtle semantic differences. E.g. nil means a person has no children whatsoever, but then what does 0 mean? Does it mean "has children, the whole 0 of them"? Like I said, pure semantics "has 0 children" and "has no children" makes no difference when working with this model in code. In that case why not choosing more straightforwards and less guard-let-?-y approach?

Some suggest that keeping a nil there may be an indication that, for example, when fetching model from backend something went wrong and we got error instead of children. But I think model should not try to have this type of semantics and nil should not be used as indication of some error in the past.

I personally think that the model should be as dumb as possible and the dumbest option in this case is empty array.

Having an optional will make you drag that ? until the end of days and use guard let, if let or ?? over and over again.

You will have to have extra unwrapping logic for NSCoding implementation, you will have to do person.children?.count ?? 0 instead of straightforward person.children.count when you display that model in any view controller.

The final goal of all that manipulation is to display something on UI. Would you really say

"This person has no children" and "This person has 0 children" for nil and empty array correspondingly? I hope you would not :)

Last Straw

Finally, and this is really the strongest argument I have

There's tons of examples like this in Cocoa framework: UIViewController::childViewControllers and more.

Even from pure Swift world: Dictionary::keys though this may be a bit far fetched.

Why is it OK for person to have nil children, but not for SKNode? For me the analogy is perfect. Hey, even the SKNode's method name is children :)

My view: there must be an obvious reason for keeping those arrays as optionals, like a really good one, otherwise empty array offers same semantics with less unwrapping.

The Last Last Straw

Finally, some references to very good articles, each of those

In Natasha's post, you will find a link to NSHipster's blog post and in Swiftification paragraph you can read this:

For example, instead of marking NSArray return values as nullable, many APIs have been modified to return an empty array—semantically these have the same value (i.e., nothing), but a non-optional array is far simpler to work with

Solution 4

Sometimes there's a difference between something not existing and being empty.

Let's say we have an app where a user can modify a list of phone numbers and we save said modifications as modifiedPhoneNumberList. If no modification has ever occurred the array should be nil. If the user has modified the parsed numbers by deleting them all the array should be empty.

Empty means we're going to delete all the existing phone numbers, nil means we keep all the existing phone numbers. The difference matters here.

When we can't differentiate between a property being empty or not existing or it doesn't matter empty is the way to go. If a Person were to lose their only child we should simply have to remove that child and have an empty array rather than have to check if the count is 1 then set the entire array to nil.

Solution 5

I always use empty arrays.

In my humble opinion, the most important purpose of optionals in Swift is to safely wrap some value that may be nil. An array already act as this type of wrapper - you can ask the array if it has anything inside & access its value(s) safely with for loops, mapping, etc. Do we need to put a wrapper within a wrapper? I don't think so.

Share:
19,835
Aaron Rasmussen
Author by

Aaron Rasmussen

Updated on June 03, 2022

Comments

  • Aaron Rasmussen
    Aaron Rasmussen almost 2 years

    I have a simple Person class in Swift that looks about like this:

    class Person {
        var name = "John Doe"
        var age = 18
        var children = [Person]?
    
        \\ init function goes here, but does not initialize children array
    }
    

    Instead of declaring children to be an optional array, I could simply declare it and initialize it as an empty array like this:

    var children = [Person]()
    

    I am trying to decide which approach is better. Declaring the array as an optional array means that it will not take up any memory at all, whereas an empty array has at least some memory allocated for it, correct? So using the optional array means that there will be at least some memory saving. I guess my first question is: Is there really any actual memory saving involved here, or are my assumptions about this incorrect?

    On the other hand, if it is optional then each time I try to use it I will have to check to see if it is nil or not before adding or removing objects from it. So there will be be some loss of efficiency there (but not much, I imagine).

    I kind of like the optional approach. Not every Person will have children, so why not let children be nil until the Person decides to settle down and raise a family?

    At any rate, I would like to know if there are any other specific advantages or disadvantages to one approach or the other. It is a design question that will come up over and over again.

  • chrisl08
    chrisl08 almost 7 years
    "nil" to me is the null in the database ( same as java's and c#'s nulls). It means that we don't know if the person has children or not. A value of 0 means that we know that the person has no children.
  • rFlex
    rFlex over 6 years
    Completely agree. Making it optional also forces to make two check if we really want to know if there is more than 1 child (unwrap then check if it’s not empty), which slightly complicates the code without real benefits. Empty array is clear in my opinion.
  • Bretsko
    Bretsko over 6 years
    I wonder about performance issues, if I have a heavy class, will an array of this type be a big overhead compared to a .None? Or is there any optimization in Swift not to allocate space for initially empty arrays?
  • David Rector
    David Rector about 6 years
    The count of items in the array is the number of children. Zero items is zero children. Using nil for the array implies a count that is neither zero nor non-zero, which is almost nonsense. The only case where nil might make sense is if you want to pass on to the caller that the number cannot be calculated (is unknown), which is the only other state possible. Return the number of children if the number is known and return it by having that many items in the array.
  • David Rector
    David Rector about 6 years
    "An optional if it's possible for the list to be empty for the entire life cycle of the container entity" That's something I would never think so using nil for that is not intuitive and would require a comment. In fact, any use of nil would require some code comments making me think it's better to just not use it and return an empty array, which is totally clear and needs no code comments to understand.
  • Skwiggs
    Skwiggs almost 6 years
    This. Having optionals lets you also understand (to a certain extent) the history of a variable. Like Deco said, if you have an optional children array which at some point is empty, it means the Person has had kids, but somehow lost them at some point. In UI terms, you could then differentiate 'Person never had children' from 'Person does not have any children anymore'
  • i4niac
    i4niac over 5 years
    @chrisl08, that's a good example where nil makes sense in problem domain. I'd still argue that if there's no need to model the "we don't know" or similar state, then there's no need to use nil. E.g. in banking apps, we (the bank) definitely know that client has accounts, so nil and empty array would have same semantics. Same with SKNode, the SpriteKit framework knows for sure whether a node has children or not, so they model it as empty array.
  • chrisl08
    chrisl08 over 5 years
    totally agree and that is why they give us the option to mark a column as nullable in rdbms systems and why it was necessary to have nullable variables in programming languages like swift/java/c# etc