Tech

Custom Subscripts in Swift | raywenderlich.com

Custom Subscripts in Swift | raywenderlich.com

Replace observe: Michael Katz up to date this tutorial for Swift four.2. Evan Dekhayser wrote the unique and Mikael Konutgan made a earlier replace.

Custom subscripts are a strong language function that improve the comfort issue and readability of your code.

Like operator overloading, customized subscripts allow you to use native Swift constructs. You should use one thing concise like checkerBoard[2][3] moderately than the extra verbose checkerBoard.objectAt(x: 2, y: three).

On this tutorial, you’ll discover customized subscripts by constructing the foundations for a primary checkers recreation in a playground. You’ll see how straightforward it’s to make use of subscripting to maneuver items across the board. Once you’re completed, you’ll be nicely in your option to constructing a brand new recreation to maintain your fingers occupied throughout all of your spare time.

Oh, and also you’ll know much more about subscripts too! :]

Getting Began

To start out, create a brand new playground. Go to File ▸ New ▸ Playground…, select the iOS ▸ Clean template and click on Subsequent. Identify the file Subscripts.playground and click on Create.

Exchange the default textual content with:


import Basis

struct Checkerboard
enum Sq.: String
case empty = “▪️”
case pink = “?”
case white = “⚪️”


typealias Coordinate = (x: Int, y: Int)

personal var squares: [[Square]] = [
[ .empty, .red, .empty, .red, .empty, .red, .empty, .red ],
[ .red, .empty, .red, .empty, .red, .empty, .red, .empty ],
[ .empty, .red, .empty, .red, .empty, .red, .empty, .red ],
[ .empty, .empty, .empty, .empty, .empty, .empty, .empty, .empty ],
[ .empty, .empty, .empty, .empty, .empty, .empty, .empty, .empty ],
[ .white, .empty, .white, .empty, .white, .empty, .white, .empty ],
[ .empty, .white, .empty, .white, .empty, .white, .empty, .white ],
[ .white, .empty, .white, .empty, .white, .empty, .white, .empty ]
]

Checkerboard incorporates three definitions:

  • Sq. represents the state of a person sq. on the board. .empty represents an empty sq. whereas .purple and .white symbolize the presence of a purple or white piece on that sq..
  • Coordinate is an alias for a tuple of two integers. You’ll use this sort to entry the squares on the board.
  • squares is the two-dimensional array that shops the state of the board.

Subsequent, add:


extension Checkerboard: CustomStringConvertible
var description: String
return squares.map row in row.map $zero.rawValue .joined(separator: “”)
.joined(separator: “n”) + “n”

That is an extension so as to add CustomStringConvertible conformance. With a customized description, you’ll be able to print a checkerboard to the console.

Open the console utilizing View ▸ Debug Space ▸ Present Debug Space, then enter the next strains on the backside of the playground:


var checkerboard = Checkerboard()
print(checkerboard)

This code initializes an occasion of Checkerboard. Then it prints the outline property to the console utilizing the CustomStringConvertible implementation.

After urgent the Execute Playground button, the output in your console ought to appear to be this:


▪️?▪️?▪️?▪️?
?▪️?▪️?▪️?▪️
▪️?▪️?▪️?▪️?
▪️▪️▪️▪️▪️▪️▪️▪️
▪️▪️▪️▪️▪️▪️▪️▪️
⚪️▪️⚪️▪️⚪️▪️⚪️▪️
▪️⚪️▪️⚪️▪️⚪️▪️⚪️
⚪️▪️⚪️▪️⚪️▪️⚪️▪️

Getting and Setting Items

Wanting on the console, it’s fairly straightforward so that you can know what piece occupies a given sq., however your program doesn’t have that energy but. It could possibly’t know which participant is at a specified coordinate as a result of the squares array is personal.

There’s an essential level to make right here: The squares array is the implementation of the the board. Nevertheless, the consumer of a Checkerboard shouldn’t know something concerning the implementation of that sort.

A kind ought to defend its customers from its inner implementation particulars; that’s why the squares array is personal.

With that in thoughts, you’re going so as to add two strategies to Checkerboard to seek out and alter a bit at a given coordinate.

Add the next strategies to Checkerboard, after the spot the place you assign the squares array:


func piece(at coordinate: Coordinate) -> Sq.
return squares[coordinate.y][coordinate.x]


mutating func setPiece(at coordinate: Coordinate, to newValue: Sq.)
squares[coordinate.y][coordinate.x] = newValue

Discover the way you entry squares – utilizing a Coordinate tuple – quite than accessing the array immediately. The precise storage mechanism is an array-of-arrays. That’s precisely the type of implementation element you must defend the consumer from!

Defining Custom Subscripts

You’ll have observed that these strategies look an terrible lot like a property getter and setter mixture. Perhaps it is best to implement them as a computed property as an alternative?

Sadly, that gained’t work. These strategies require a Coordinate parameter, and computed properties can’t have parameters. Does that imply you’re caught with strategies?

Nicely, no – this particular case is strictly what subscripts are for! :]

Take a look at the way you outline a subscript:


subscript(parameterList) -> ReturnType
get
// return someValue of ReturnType


set (newValue)
// set someValue of ReturnType to newValue

Subscript definitions combine each perform and computed property definition syntax:

  • The primary half seems rather a lot like a perform definition, with a parameter listing and a return sort. As an alternative of the func key phrase and a perform identify, you employ the particular subscript key phrase.
  • The primary physique appears rather a lot like a computed property with a getter and a setter.

The mixture of perform and property syntax highlights the facility of subscripts. It supplies a shortcut for accessing the weather of an listed assortment. You’ll study extra about that quickly however, first, think about the next instance.

Exchange piece(at:) and setPiece(at:to:) with the next subscript:


subscript(coordinate: Coordinate) -> Sq.
get
return squares[coordinate.y][coordinate.x]

set
squares[coordinate.y][coordinate.x] = newValue

You implement the getter and setter of this subscript precisely the identical means as you implement the strategies they exchange:

  • Given a Coordinate, the getter returns the sq. on the column and row.
  • Given a Coordinate and worth, the setter accesses the sq. on the column and row and replaces its worth.

Give your new subscript a check drive by including the next code to the top of the playground:


let coordinate = (x: three, y: 2)
print(checkerboard[coordinate])
checkerboard[coordinate] = .white
print(checkerboard)

The playground will inform you the piece at (three, 2) is purple. After altering it to white, the output in the console can be:


▪️?▪️?▪️?▪️?
?▪️?▪️?▪️?▪️
▪️?▪️⚪️▪️?▪️?
▪️▪️▪️▪️▪️▪️▪️▪️
▪️▪️▪️▪️▪️▪️▪️▪️
⚪️▪️⚪️▪️⚪️▪️⚪️▪️
▪️⚪️▪️⚪️▪️⚪️▪️⚪️
⚪️▪️⚪️▪️⚪️▪️⚪️▪️

Now you can discover out which piece is at a given coordinate, and set it, through the use of checkerboard[coordinate] in each instances. A shortcut certainly!

Evaluating Subscripts, Properties and Features

Subscripts are like computed properties in many regards:

  • They include a getter and setter.
  • The setter is non-compulsory, which means a subscript could be both read-write or read-only.
  • A read-only subscript doesn’t want an specific get or set block; the complete physique is a getter.
  • Within the setter, there’s a default parameter newValue with a kind that equals the subscript’s return sort. You sometimes solely declare this parameter whenever you need to change its identify to one thing aside from newValue.
  • Customers anticipate subscripts to be quick, ideally O(1), so hold them brief and candy!

Subscripts are just computed properties in disguise.

The main distinction with computed properties is that subscripts don’t have a property identify, per se. Like operator overloading, subscripts allow you to override the language-level sq. brackets [] often used for accessing parts of a set.

Subscripts are additionally just like features in that they’ve a parameter record and return sort, however they differ on the next factors:

  • Subscript parameters don’t have argument labels by default. If you wish to use them, you’ll have to explicitly add them.
  • Subscripts can’t use inout or default parameters. Nevertheless, variadic (…) parameters are allowed.
  • Subscripts can’t throw errors. This implies a subscript getter should report errors by way of its return worth and a subscript setter can’t throw or return any errors in any respect.

Including a Second Subscript

There’s one different level the place subscripts are just like features: they are often overloaded. This implies a kind can have a number of subscripts, so long as they’ve totally different parameter lists or return varieties.

Add the next code after the prevailing subscript definition in Checkerboard:


subscript(x: Int, y: Int) -> Sq.
get
return self[(x: x, y: y)]

set
self[(x: x, y: y)] = newValue

This code provides a second subscript to Checkerboard that accepts two integers somewhat than a Coordinate tuple. Discover the way you implement the second subscript utilizing the primary by way of self[(x: x, y: y)].

Check out this new subscript by including the next strains to the top of the playground:


print(checkerboard[1, 2])
checkerboard[1, 2] = .white
print(checkerboard)

You’ll see the piece at (1, 2) change from pink to white.

Utilizing Dynamic Member Lookup

New in Swift four.2 is the language function of dynamic member lookup. This lets you outline runtime properties on a kind. This implies you should use the dot (.) notation to index into a worth or object, however you don’t need to outline a selected property forward of time.

That is most helpful when your object has an inner knowledge construction outlined at runtime, like an object from a database or distant server. Or to place it one other method, this brings key-value coding to Swift while not having an NSObject subclass.

This function requires two elements: A @dynamicMemberLookup annotation and a particular type of subscript.

A Third Subscript

First, you’ll lay the inspiration for dynamic lookup by introducing yet one more subscript overload. This one makes use of a string to outline the coordinate.

Add the next code under the earlier subscript definitions:


personal func convert(string: String) -> Coordinate
let expression = attempt! NSRegularExpression(sample: “[xy](d+)”)
let matches = expression
.matches(in: string,
choices: [],
vary: NSRange(string.startIndex…, in: string))
let xy = matches.map String(string[Range($0.range(at: 1), in: string)!])
let x = Int(xy[0])!
let y = Int(xy[1])!
return (x: x, y: y)


subscript(enter: String) -> Sq.
get
let coordinate = convert(string: enter)
return self[coordinate]

set
let coordinate = convert(string: enter)
self[coordinate] = newValue

This code provides a number of issues:

  1. First, convert(string:) takes a string in the type of x#y# (the place ‘#’ is a quantity) and returns a Coordinate with an x-value and a y-value. You’d usually throw an error if the common expression sample didn’t match however since subscripts can’t throw an error, there’s not a lot it will probably do besides crash anyway, so the attempt is pressured in this specific state of affairs.
  2. Then a newly-introduced subscript takes a string, converts it to a Coordinate, and reuses the primary subscript outlined earlier.

Do this one out by including the next strains to the playground:


print(checkerboard[“x2y5”])
checkerboard[“x2y5”] = .purple
print(checkerboard)

This time, one of many whites in the sixth row will flip purple.


▪️?▪️?▪️?▪️?
?▪️?▪️?▪️?▪️
▪️⚪️▪️⚪️▪️?▪️?
▪️▪️▪️▪️▪️▪️▪️▪️
▪️▪️▪️▪️▪️▪️▪️▪️
⚪️▪️?▪️⚪️▪️⚪️▪️
▪️⚪️▪️⚪️▪️⚪️▪️⚪️
⚪️▪️⚪️▪️⚪️▪️⚪️▪️

Implementing Dynamic Member Lookup

Up to now this isn’t dynamic member lookup, it’s only a string index in a particular format. Subsequent you’ll sprinkle on the syntactic sugar.

First, add the next line on the prime of the playground, instantly earlier than the struct key phrase.


@dynamicMemberLookup

Then add the next beneath the opposite subscript definitions:


subscript(dynamicMember enter: String) -> Sq.
get
let coordinate = convert(string: enter)
return self[coordinate]

set
let coordinate = convert(string: enter)
self[coordinate] = newValue

This is identical because the final subscript, nevertheless it has a particular argument label: dynamicMember. This subscript signature plus the annotation on the sort lets you entry a Checkerboard utilizing the dot-syntax.

Wow, now the string index doesn’t have to be inside sq. brackets ([]). You possibly can entry the string immediately on the occasion!

See it in motion by including these remaining strains to the underside of the playground:


print(checkerboard.x6y7)
checkerboard.x6y7 = .pink
print(checkerboard)

Run the playground once more, and the final white piece will flip to pink.


▪️?▪️?▪️?▪️?
?▪️?▪️?▪️?▪️
▪️⚪️▪️⚪️▪️?▪️?
▪️▪️▪️▪️▪️▪️▪️▪️
▪️▪️▪️▪️▪️▪️▪️▪️
⚪️▪️?▪️⚪️▪️⚪️▪️
▪️⚪️▪️⚪️▪️⚪️▪️⚪️
⚪️▪️⚪️▪️⚪️▪️?▪️

A Phrase of Warning

Dynamic lookup is a strong function that may make code so much cleaner, particularly server or scripting code. You not have to outline an object’s construction at compile time to get dot-notation entry.

But there are some harmful drawbacks.

For instance, the @dynamicMemberLookup annotation principally tells the compiler to not examine the validity of property names. You’ll nonetheless get sort checking in addition to completion for explicitly-defined properties, however now you possibly can put something after the interval and the compiler gained’t complain. You’ll solely discover out at runtime should you make a typo.

When you add this line to the playground, you gained’t get an error till you run it.


checkerboard.queen

The place to Go From Right here?

You’ll be able to obtain the ultimate playground utilizing the Obtain Supplies button on the prime or backside of this tutorial.

Now that you simply’ve added subscripts to your toolkit, search for alternatives to make use of them in your personal code. When used correctly, they make your code extra readable and intuitive.

That stated, you don’t all the time need to revert to subscripts. Should you’re writing an API, your customers are used to utilizing subscripts to entry parts of an listed assortment. Utilizing them for different issues will really feel unnatural and compelled.

For additional info on subscripts, take a look at this chapter of The Swift Programming Language documentation by Apple.

When you’ve got any questions or feedback, please depart them under!