user2647092
user2647092

Reputation: 397

Error handling special characters using utfOffset16

I have a function that searches and returns the index within the string of the first occurrence of searchStr, however I keep getting a fatal error whenever the string contains any special charactsers (such as ç or é). The error seems to be occuring at the utf16Offset call and I can't seem to figure out why.. here is the code I'm using:

func index(of aString: String, startingFrom position: Int? = 0) -> String.Index? {
    guard let position = position else {
        return nil
    }

    if self.startIndex.utf16Offset(in: aString) + position > self.endIndex.utf16Offset(in: aString) {
        return nil
    } // produces fatal error when special character encountered

    let start: String.Index = self.index(self.startIndex, offsetBy: position)
    let range: Range<Index> = Range<Index>.init(uncheckedBounds: (lower: start, upper: self.endIndex))
    return self.range(of: aString, options: .literal, range: range, locale: nil)?.lowerBound
}

Upvotes: 1

Views: 234

Answers (1)

hennes
hennes

Reputation: 9342

This part seems problematic to me

if self.startIndex.utf16Offset(in: aString) + position > self.endIndex.utf16Offset(in: aString) {
    return nil
}

You're taking the start index on self and convert it to its UTF-16 offset in aString. self and aString are two unrelated strings though so this is probably undefined behavior (which might be why you see it crashing in some cases).

The intent of this if statement seems to be to ensure that this produces a valid range (lower <= upper)

let start: String.Index = self.index(self.startIndex, offsetBy: position)
let range: Range<Index> = Range<Index>.init(uncheckedBounds: (lower: start, upper: self.endIndex))

You can actually do that by just comparing the Indexes directly like this

let start: String.Index = self.index(self.startIndex, offsetBy: position)

guard start < self.endIndex else {
    return nil
}

// Range is guaranteed to have valid boundaries now
let range: Range<Index> = Range<Index>.init(uncheckedBounds: (lower: start, upper: self.endIndex))

Full example:

extension String {
    func index(of aString: String, startingFrom position: Int? = 0) -> String.Index? {
        guard let position = position else {
            return nil
        }

        let start: String.Index = self.index(self.startIndex, offsetBy: position)

        guard start < self.endIndex else {
            return nil
        }

        let range: Range<Index> = Range<Index>.init(uncheckedBounds: (lower: start, upper: self.endIndex))
        return self.range(of: aString, options: .literal, range: range, locale: nil)?.lowerBound
    }
}

// Doesn't crash anymore
"aaç".distance(from: foobar.startIndex, to: foobar.index(of: "ç", startingFrom: 0)!)

Upvotes: 1

Related Questions