Ошибка при обработке специальных символов с использованием utfOffset16 - PullRequest
1 голос
/ 08 апреля 2020

У меня есть функция, которая ищет и возвращает индекс в строке первого вхождения searchStr, однако я получаю фатальную ошибку всякий раз, когда строка содержит какие-либо специальные символы (такие как ç или é). Кажется, ошибка возникает при вызове utf16Offset, и я не могу понять, почему .. вот код, который я использую:

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
}

1 Ответ

1 голос
/ 08 апреля 2020

Эта часть мне кажется проблемной c

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

Вы берете начальный индекс на self и конвертируете его в смещение UTF-16 в aString. self и aString - это две несвязанные строки, хотя, возможно, это неопределенное поведение (возможно, в некоторых случаях вы видите его сбой).

Намерение этого оператора if состоит в том, чтобы убедитесь, что это дает действительный диапазон (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))

Вы можете сделать это, просто сравнив Index непосредственно как это

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))

Полный пример:

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)!)
...