Reputation: 397
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
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 Index
es 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