Last time we talked about Arrays in Swift, and the main way to acquire a value was to look it up via an index using subscripts. Swift made it rather easy to implement subscripts in your own classes. Sure you can use an Array or Dictionary type if you wanted, and in many cases you should, but if writing it in subscript syntax is clearer and more expressive, that is a good reason to use it.
Getting and Setting via Subscripts
Lets say we have this class below:
class DailyMeal { enum MealTime { case Breakfast case Lunch case Dinner } var meals: [MealTime : String] = [:] }
As it is, we would use this by querying the meals dictionary directly, like so:
let monday = DailyMeal() monday.meals[.Breakfast] = "Toast" if let someMeal = monday.meals[.Breakfast] { print(someMeal) }
Now, we already made this monday of the DailyMeal type, so we already know meals should be stored here, so wouldn’t it be easier to just ask monday about .Breakfast? We would add this right after our meals variable in the DailyMeal class:
subscript(requestedMeal: MealTime) -> String? { get { return meals[requestedMeal] } set(newMealName) { meals[requestedMeal] = newMealName } }
This looks a lot like a Swift computed property. You can read more about computed properties in my post Computed Properties in Swift. The major difference is the function prototype at the top. It uses the subscript keyword first, then it takes a parameter and describes a return type. The parameter is what goes in the subscript (between the square brackets). In an Array, that’s the Int that describes the index. In Dictionaries, that is whatever is used as the key.
Then, like computed properties, we write the getter and setter method. In both of these cases, we are still calling the meals dictionary, but we are covering up the details from the user.
We can now use the class like so:
var monday = DailyMeal() monday[.Breakfast] = "Toast" if let someMeal = monday[.Breakfast] { print(someMeal) }
In this example, it would then print “Toast” to the console.
Much better on the setting, but all of that optional binding still makes this messy, lets rewrite that subscript a little bit and remove that implementation detail:
subscript(requestedMeal: MealTime) -> String { get { if let thisMeal = meals[requestedMeal] { return thisMeal } else { return "Ramen" } } set(newMealName) { meals[requestedMeal] = newMealName } }
We’re still actually doing the optional binding, but we moved it inside the subscript to make things easier on the user. Now this subscript always returns a Swift String. If they have not set a meal name for that mealTime (the dictionary returns nil), then we just assume they’re having Ramen. Ramen is always a great default meal.
Now we can just call it like so:
let monday = DailyMeal() monday[.Lunch] = "Pizza" print(monday[.Lunch]) //Output: "Pizza" print(monday[.Dinner]) //Output: "Ramen"
Now that is some clean code. This hides your implementation details from the class using DailyMeal. If you need to change your API underneath, as long as it still ties the internal representation to that subscript, anything calling your API won’t need to change. For reference, the entire DailyMeal class now looks like:
class DailyMeal { enum MealTime { case Breakfast case Lunch case Dinner } var meals: [MealTime : String] = [:] subscript(requestedMeal: MealTime) -> String { get { if let thisMeal = meals[requestedMeal] { return thisMeal } else { return "Ramen" } } set(newMealName) { meals[requestedMeal] = newMealName } } }
Read-Only Subscripts
Read-Write subscripts are usually used as fronts for internal Arrays or Dictionaries. Read-only subscripts can be, but I have seen some instances of using them in other ways in Swift, like mathematical calculations. In Apple’s Swift iBook, you can see an example of using them for a custom times table, where you set the multiplier and call a subscript for an entry in that multiplier’s times-table column, and returns that value.
In an effort to show other examples to give you multiple perspectives on these features, I have made a simple factorial generator. You don’t even need to set an extra variable, just create the class and ask it for a factorial.
struct FactorialGenerator { subscript(n: Int) -> Int { var result = 1 if n > 0 { for value in 1...n { result *= value } } return result } }
It is a little longer, but that is mostly because factorials are a bit more complex than multiplication alone.
You may notice that I don’t have get or set written in this subscript. If you only want it to be read-only, then you can omit the get keyword. The Swift compiler will just treat what you have written as a getter. If another class attempts to set via this subscript, a compiler error will be thrown.
So now with this factorial generator, you can just call it like this:
let factorial = FactorialGenerator() print("Five factorial is equal to \(factorial[5]).") //Output: "Five factorial is equal to 120." print("Ten Factorial is equal to \(factorial[10]).") //Output: "Ten Factorial is equal to 3628800."
Now that is a concise way to request a factorial that isn’t actually an array underneath. This way is a bit computational heavy, so if you request factorials a lot, you might want to make this an array underneath that is created upon initialization, or maybe generated on another thread so it doesn’t block the UI. Either way though, using subscripts lets you hide those details from the user of the FactorialGenerator class. If you start doing it this way, and change your mind in later versions of your API, then the user won’t have to change their code, as long as FactorialGenerator still ties the internal representation of the values to these subscripts.
Another good example of using read-only subscripts was pointed out to me by Natasha The Robot. It is a nicer way of handling JSON in Swift named SwiftyJSON, written by Ruoyu Fu, available on GitHub. You should also check out Natasha’s website natashatherobot.com for other great articles about Swift.
Conclusion
Subscripting is a very powerful part of Swift. To paraphrase John McCall from WWDC Session 404 “Advanced Swift,” and to quote Stan Lee: “With great power, comes great responsibility.” Apparently that phrase goes all the way back to Voltaire, but I digress.
Since Swift subscripts are basically glorified computed properties, you can do anything a normal Swift function can do. You can make calls to other functions, you could draw on screen, you could even call to a hardware device if you wanted, but you probably shouldn’t (unless doing so REALLY IS required to get information in that subscript (like if that index was telling you the status of an IO pin on Raspberry Pi or something like that).
When you use subscripts, be careful that you treat them like a subscript. Ideally you should be calling into something where your subscript is essentially an index, and then getting or setting it like you were using an Array or Dictionary.
We have been entrusted with a powerful language feature in Swift. We must all strive to use it wisely and clearly.
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!