Ответ 1
Обновлен для Swift 3
Сдвиговые диапазоны более сложны, чем NSRange
, и они не стали легче в Swift 3. Если вы хотите попытаться понять причину некоторой сложности, прочитайте this и this. Я просто покажу вам, как их создавать и когда вы можете их использовать.
Закрытые диапазоны: a...b
1. ClosedRange
Элементы всех диапазонов в Swift сопоставимы (т.е. соответствуют протоколу Comparable). Это позволяет вам получить доступ к элементам в диапазоне от коллекции. Вот пример:
let myRange: ClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
Однако a ClosedRange
не является счетным (т.е. не соответствует протоколу Sequence). Это означает, что вы не можете перебирать элементы с помощью цикла for
. Для этого вам понадобится CountableClosedRange
.
2. CountableClosedRange
Это похоже на последнее, за исключением того, что теперь диапазон также можно повторить.
let myRange: CountableClosedRange = 1...3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c", "d"]
for index in myRange {
print(myArray[index])
}
Полуоткрытые диапазоны: a..<b
Этот оператор диапазона содержит элемент a
, но не элемент b
. Как и выше, существуют два разных типа полуоткрытых диапазонов: Range
и CountableRange
.
1. Range
Как и в случае с ClosedRange
, вы можете получить доступ к элементам коллекции с помощью Range
. Пример:
let myRange: Range = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
Опять же, вы не можете перебирать Range
, потому что это только сопоставимо, а не просто.
2. CountableRange
A CountableRange
допускает итерацию.
let myRange: CountableRange = 1..<3
let myArray = ["a", "b", "c", "d", "e"]
myArray[myRange] // ["b", "c"]
for index in myRange {
print(myArray[index])
}
NSRange
Вы можете (обязательно) использовать NSRange
порой в Swift (например, при создании атрибутных строк), поэтому полезно знать, как сделать один.
let myNSRange = NSRange(location: 3, length: 2)
Обратите внимание, что это местоположение и длина, а не индекс индекса и конца. Пример здесь похож по значению на диапазон Swift 3..<5
. Однако, поскольку типы различны, они не взаимозаменяемы.
Диапазоны со строками
Операторы диапазона ...
и ..<
являются сокращенным способом создания диапазонов. Например:
let myRange = 1..<3
Длинный способ создания одного и того же диапазона будет
let myRange = CountableRange<Int>(uncheckedBounds: (lower: 1, upper: 3)) // 1..<3
Вы можете видеть, что тип индекса здесь Int
. Это не работает для String
, хотя, поскольку строки состоят из символов, а не все символы одного размера. (Прочтите этот для получения дополнительной информации.) Эможи вроде 😀, например, занимает больше места, чем буква "b".
Проблема с NSRange
Попробуйте поэкспериментировать с NSRange
и NSString
с emoji, и вы поймете, что я имею в виду. Головная боль.
let myNSRange = NSRange(location: 1, length: 3)
let myNSString: NSString = "abcde"
myNSString.substring(with: myNSRange) // "bcd"
let myNSString2: NSString = "a😀cde"
myNSString2.substring(with: myNSRange) // "😀c" Where is the "d"!?
У смайликов есть два кодовых блока UTF-16, поэтому он дает неожиданный результат, не включая "d".
Быстрое решение
Из-за этого с Swift Strings вы используете Range<String.Index>
, а не Range<Int>
. Индекс String рассчитывается на основе определенной строки, чтобы он знал, существуют ли какие-либо кластеры emoji или расширенные графемы.
Пример
var myString = "abcde"
let start = myString.index(myString.startIndex, offsetBy: 1)
let end = myString.index(myString.startIndex, offsetBy: 4)
let myRange = start..<end
myString.substring(with: myRange) // "bcd"
myString = "a😀cde"
let start2 = myString.index(myString.startIndex, offsetBy: 1)
let end2 = myString.index(myString.startIndex, offsetBy: 4)
let myRange2 = start2..<end2
myString.substring(with: myRange2) // "😀cd"
См. также:
Примечания
- Вы не можете использовать диапазон, который вы создали, с одной строкой в другой строке.
- Как вы можете видеть, диапазоны строк - это боль в Swift, но они действительно позволяют лучше справляться с emoji и другими скалярами Unicode.