I'm a software developer based in the UK. I am blogging regularly about software development & Apple

Ranges in Swift

What are ranges?

Ranges are structures in Swift that have a start index and an end index. There are as of Swift 2.2 two types

  • half open ranges like a..<b e.g. 2..<5 [2,3,4] and
  • closed ranges like a...b e.g. 2...5 [2,3,4,5]

Ranges can be used with collections in a for - next style manner like

for i in 1...n {
    // do something
}

I consider this as a kind of old style approach since fast enumeration is always the better choice. Even when you are in need of an index which is not available per se with fast enumeration, the preferable approach is then to rely on the enumerate() function i.e. instead of

let list= ["mouse","cat","dog"]
for index in 0..<list.endIndex {
    print ("\(index). \(list[i])")
}

use

let list= ["mouse","cat","dog"]
for (index,item) in list.enumerate() {
    print ("\(index).\(item)")
}

Most often however ranges are used with strings.  In Objective-C NSString and NSAttributedString rely on NSRange. While it is still possible to use those in Swift via toll free bridging of their Swift counterparts, it is definitely not the most straightforward approach. Hence let's relearn those things we got used to in the Objective-C world and do them properly in Swift.

Working with string ranges

Strings in Swift are encoded in Unicode. Unicode doesn't have a fixed length for its character encoding and because of that, there is no direct way of addressing a string's characters. When it comes to working with strings in Swift, you have to deal with index ranges. You normally start out with the start index and advance it as many times as you need to. Only then you can with the help of that index address a string's character i.e.

let s = "abcdefg"
let index = s.startIndex.advancedBy(2)
print (s[index]) // "c"

Working with Swift ranges can be somewhat cumbersome as well because there is no direct way to convert an integer range to an index range. Hence always rely on a string's start and end index. When it comes to working with ranges and strings remember

  • use advancedBy() on the range index to traverse strings because of the variable width nature of string characters
  • advancedBy(n:limit:) doesn't return an optional. So you never know if you just overstepped the limit or reached it. Result is always limit.
  • advanceBy() accepts negative n to go backwards
  • Swift string ranges are half open ranges i.e. the last character in a string ist at endIndex.advancedBy(-1)
  • Indexes can be converted to integer via the distance method i.e. s.startIndex.distance(s.startIndex.advancedBy(2)) is 2

Let's take a look at a few string operations to put our new found knowledge into practice.

Retrieving substrings of strings

Substrings can be retrieved via substringWithRange() which returns an optional Range e.g.

let s = "abcdefghijk"
let range = Range(s.startIndex.advancedBy(2) ..< s.endIndex.advancedBy(-1))
let rangeString = s.substringWithRange(range) // returns "cdefghij"

Replacing substrings in strings

Replacing or even deleting substrings can be achieved via stringByReplacingCharactersInRange()

let s = "abcdefghijk"
let range = Range(s.startIndex ..< s.startIndex.advancedBy(0))
let result = s.stringByReplacingCharactersInRange(range, withString: "test") //"testabcdefghijk"

or

let s = "abcdefghijk"
let range = Range(s.startIndex ..< s.startIndex.advancedBy(5))
let resultString = s.stringByReplacingCharactersInRange(range2, withString: "") //"fghijk"

But sometimes we just want to handle things the way we used to. There is nothing wrong with that and one way to convert a Swift Range to our old friend NSRange is:

let s = "1234567890"
let (startIndex,endIndex) = (s.startIndex,s.endIndex)
let nsRange = NSMakeRange(startIndex.distanceTo(startIndex), startIndex.distanceTo(endIndex)-startIndex.distanceTo(startIndex))
let subString = (s as NSString).substringWithRange(nsRange) // "1234567890"

Conclusion

It's not only that the old for-next loop is now deprecated in Swift, the use of ranges with collections is outdated as well. It's always better to rely on fast enumeration when traversing collections. In a similar way it's time to move away from bridging strings to the Objective-C world. We are still able to code the way we used to in Objective-C but for the sake of readability and swiftier code it's time to move forward and leave the old baggage behind.

Staying up-to-date

Working with Instruments - Allocations