Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-02-19

Swift Gotcha: String.Index and the time of creation

String.Index can cause some nasty surprises, especially at runtime, just after you shipped your application...

Ok, that did not happen to me. But it is still frustrating when unit testing starts "EXC_BAD_..." even though it seems you did everything right.

Maybe I missed it in the documentation, but String.Index is a special kind of type. It is defined as a struct and that should have given me a hint. First I thought that String.Index stay's associated with the string it is derived from, but that was to optimistic.

When we assign a String.Index parameter, that is the time where the range for the parameter is fixed.

I.e. when a String.Index parameter is assigned, and the string it is derived from has 10 characters, then the "endIndex" associated with the parameter is fixed at this value. Even when later the string is appended to, the parameter cannot be incremented beyond this endIndex.

We can test this with the following:


As you can see, even though the original string now has a higher endIndex the associated parameter 'ind' cannot be updated beyond the original endIndex.

To me this is a major PITA, it means that for every update to a string, all associated index parameters must be updated as well.

One particularly instance where this is irksome is inserting a string in a string. Swift defines the insert function but that only inserts one character. When it is necessary to insert a string the usual solution is like this:

var str = "1234567890"

let s = "abcd"

var index = str.startIndex.successor().successor()

for c in s {
    str.insert(c, atIndex: index)
    index = index.successor()
}

This works, but only so long as the insertion index does not grow beyond the original str.endIndex. As soon as that happens the following occurs:


The only solution I see at this time is the following:

extension String {
    
    func fixIndex(index: String.Index) -> String.Index {
        let kludge = distance(startIndex, index)
        return advance(startIndex, kludge)
    }
    
}

var str = "1234567890"

let s = "abcd"

var index = str.endIndex.predecessor().predecessor()

for c in s {
    str.insert(c, atIndex: index)
    index = str.fixIndex(index)
    index = index.successor()
}

This works, but it's quite ugly imo.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment