Tuples in Swift, Advanced Usage and Best Practices

Private State

released Fri, 01 Mar 2019
Swift Version 5.0

Private State

In addition to the previous example, there are also use cases where using tuples beyond a temporary scope is useful. As long as the scope is private and the tuple's type isn't littered all over the implementation, using tuples to store internal state can be fine.

A simple and contrived example would be storing a static UITableView structure that displays various information from a user profile and contains the key path to the actual value as well as a flag noting whether the value can be edited when tapping on the cell.

let tableViewValues = [

     (title: \"Age\", value: \"user.age\", editable: true),

     (\"Name\", \"user.name.combinedName\", true),

     (\"Username\", \"user.name.username\", false),

     (\"ProfilePicture\", \"user.pictures.thumbnail\", false)]

The alternative would be to define a struct, but if the data is a purely private implementation detail, a tuple works just as well.

A better example is when you define an object and want to add the ability to add multiple change listeners to your object. Each listener consists of a name and the closure to be called upon any change:

typealias Action = (_ change: Any?) -> Void

func addListener(name: String, action: @escaping Action) { }

func removeListener(name: String) { }

How will you store these listeners in your object? The obvious solution would be to define a struct, but this is a very limited scope, and the struct will only be internal, and it will be used in only three cases. Here, using a tuple may even be the better solution, as the destructuring makes things simpler:

class ListenerStuff {



     typealias Action = (_ change: Any?) -> Void



     var listeners: [(String, Action)] = []



     func addListener(name: String, action: @escaping Action) {

         listeners.append((name, action))

     }



     func removeListener(name: String) {

         if let idx = listeners.index(where: { $0.0 == name }) {

             listeners.remove(at: idx)

         }

     }



     func execute(change: Int) {

         for (_, listener) in listeners {

             listener(change as Any?)

         }

     }

}



var stuff = ListenerStuff()

let ourAction: ListenerStuff.Action = { x in print(\"Change is \(x ?? \"NONE\").\") }

stuff.addListener(name: \"xx\", action: ourAction)

stuff.execute(change: 17)

As you can see in the execute function, the destructuring abilities make tuples especially useful in this case, as the contents are directly destructured into the local scope.