Swift 3.0 String Index Changes

Swift 3.0 makes fairly sweeping changes to Collection types (SE-0065 - A New Model for Collections and Indices) and you may not be expecting them to impact Strings... But if you are used to working with indexes of any of the different views of a string (for example UTF8 or UnicodeScalarView) and traversing individual characters from string with the String index... You will be impacted. 

In Swift 2 you could iterate through all valid index values using something like

var currentIndex = myString.startIndex 
while currentIndex != myString.endIndex{
    currentIndex = currentIndex.successor()    
}

Or acquired the index for a particular character with

let thirdLetter = myString.startIndex.advanceBy(3)

This is no longer possible, the String itself is responsible for managing the Index (and new values of it). It does this through a set of index() functions

myString.index(after:) // returns an index advanced by one
myString.index(before:) // returns an index reduced by one
myString.index(currentIndex:,offsetBy:) // returns an index moved offsetBy characters

So our first Swift 2 example becomes

var currentIndex = myString.startIndex
while currentIndex != myString.endIndex {
    currentIndex = myString.index(after: currentIndex)
}

And our second

let thirdLetterIndex = myString.index(myString.startIndex, offsetBy: 2)    
let thirdLetter = myString.characters[thirdLetterIndex]

Obviously I could have put all of that into a single line (let thirdLetter = myString.characters[myString.index(myString.startIndex, offsetBy: 2)]) but it's worth taking a look at how the string index can then be used in the other views of the String. For different encodings the length of any given character can be different, so the index could be different. The responsibility of doing this, for any given encoding view is now delegated to the index itself. For example

myString.utf8[thirdLetterIndex.samePosition(in: myString.utf8)]

In migrating to Swift 3.0 I've found these to be some of the trickier cases to deal with, and there don't seem to be any fix-its (which I think is understandable).

You could make this a little easier with an extension that brings String indexes a little closer to their old behaviour.

extension String.Index{
    func successor(in string:String)->String.Index{
        return string.index(after: self)
    }
    func predecessor(in string:String)->String.Index{
        return string.index(before: self)
    }
    func advance(_ offset:Int, `for` string:String)->String.Index{
        return string.index(self, offsetBy: offset)
    }
}
//Our example using the extension
currentIndex = myString.startIndex
while currentIndex != myString.endIndex {
    currentIndex = currentIndex.successor(in: myString)
}
myString.characters[myString.startIndex.advance(3, for: myString)]

I've found that my migration was a lot faster with this extension, as really all you need to do is add the extra parameter.

Comment

Swift Studies

Nigel has been developing software for over 30 years. Starting with the ZX Spectrum and moving through the Commodore 64, Amiga, the Windows PC area and for the last 6 years the Mac.