Если у меня есть массив в Swift, и я пытаюсь получить доступ к индексу, выходящему за пределы, возникает неудивительная ошибка времени выполнения:
var str = ["Apple", "Banana", "Coconut"]
str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION
Тем не менее, я бы подумал, что со всеми дополнительными цепочками и безопасностью, которые приносит Swift, было бы тривиально сделать что-то вроде:
let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
print(nonexistent)
...do other things with nonexistent...
}
Вместо того:
let theIndex = 3
if (theIndex < str.count) { // Bounds check
let nonexistent = str[theIndex] // Lookup
print(nonexistent)
...do other things with nonexistent...
}
Но это не так - я должен использовать if
выражение ol ', чтобы проверить и убедиться, что индекс меньше str.count
.
Я попытался добавить свою собственную subscript()
реализацию, но я не уверен, как передать вызов исходной реализации или получить доступ к элементам (на основе индекса) без использования индексной записи:
extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
Ответы:
У ответа Алекса есть хороший совет и решение для вопроса, однако я случайно наткнулся на более хороший способ реализации этой функциональности:
Swift 3.2 и новее
Swift 3.0 и 3.1
Благодарим Хэмиша за то, что он предложил решение для Swift 3 .
Swift 2
пример
источник
safe:
параметра, чтобы гарантировать разницу.return self.indices ~= index ? self[index] : nil;
Collection
типов, гдеIndices
есть не смежный НапримерSet
, если мы обращаемся к элементу set с помощью index (SetIndex<Element>
), мы можем столкнуться с исключениями времени выполнения для индексов, которые,>= startIndex
и< endIndex
в этом случае безопасный индекс не работает (см., Например, этот надуманный пример ).contains
Метод итерации по всем показателям , таким образом , делая это O (N). Лучше использовать индекс и счетчик для проверки границ.return index >= startIndex && index < endIndex ? self[index] : nil
Collection
типы имеютstartIndex
,endIndex
которые естьComparable
. Конечно, это не будет работать для некоторых странных коллекций, которые, например, не имеют индексов посередине, решение сindices
более общим.Если вы действительно хотите такое поведение, оно пахнет так, как будто вы хотите словарь вместо массива. Словари возвращаются
nil
при доступе к пропущенным ключам, что имеет смысл, потому что гораздо сложнее узнать, присутствует ли ключ в словаре, поскольку эти ключи могут быть любыми, где в массиве ключ должен находиться в диапазоне от:0
доcount
. И невероятно часто выполнять итерации по этому диапазону, где вы можете быть абсолютно уверены , что имеете реальное значение на каждой итерации цикла.Я думаю, причина, по которой это не работает, заключается в выборе дизайна, сделанном разработчиками Swift. Возьмите свой пример:
Если вы уже знаете, что индекс существует, как и в большинстве случаев, когда вы используете массив, этот код отлично подходит. Однако, если доступ к подстрочному мог вернуться ,
nil
то вы изменили тип возвращаемого изArray
«Ssubscript
способа быть необязательным. Это изменит ваш код на:Это означает, что вам нужно будет разворачивать необязательное значение каждый раз, когда вы выполняете итерацию в массиве или делаете что-либо еще с известным индексом, просто потому, что редко вы можете получить доступ к индексу вне границ. Дизайнеры Swift выбрали меньшее развертывание опций за счет исключения времени выполнения при доступе за пределы индексов. А сбой предпочтительнее логической ошибки, вызванной тем, чего
nil
вы не ожидали где-то в своих данных.И я с ними согласен. Таким образом, вы не будете изменять
Array
реализацию по умолчанию, потому что вы сломаете весь код, который ожидает не необязательные значения из массивов.Вместо этого вы можете создать подкласс
Array
и переопределить,subscript
чтобы вернуть необязательный. Или, на практике, вы можете расширитьArray
с помощью не подстрочный метод, который делает это.Swift 3 Обновление
func get(index: Int) ->
T?
должен быть замененfunc get(index: Int) ->
Element?
источник
subscript()
на необязательный - это было основным препятствием на пути переопределения поведения по умолчанию. (Я вообще не мог заставить его работать . ) Я избегал написанияget()
метода расширения, который является очевидным выбором в других сценариях (категории Obj-C, кто-нибудь?), Ноget(
не намного больше[
, и делает его Понятно, что поведение может отличаться от того, что другие разработчики могут ожидать от оператора индекса Swift. Спасибо!T
был переименован вElement
. Просто дружеское напоминание :)nil
вместо вызова исключения из индекса за пределами границ будет неоднозначным. Так как egArray<String?>
может также вернуть nil в качестве действительного члена коллекции, вы не сможете провести различие между этими двумя случаями. Если у вас есть свой собственный тип коллекции, который, как вы знаете, никогда не сможет вернутьnil
значение, то есть это контекстно для приложения, тогда вы можете расширить Swift для безопасной проверки границ, как ответили в этом посте.Чтобы опираться на ответ Никиты Кукушкина, иногда нужно безопасно присваивать индексам массивов, а также читать из них, т.е.
Итак, вот обновление ответа Никиты (Swift 3.2), которое также позволяет безопасно записывать в индексы изменяемого массива, добавляя параметр safe: name.
источник
Действительно в Swift 2
Несмотря на то, что на это уже много раз отвечали, я хотел бы представить более последовательный ответ о том, куда движется мода программирования Swift, который, по словам Красти, таков: «
protocol
Сначала подумай »• Что мы хотим сделать?
- Получить элемент
Array
данного индекса только тогда, когда это безопасно, и вnil
противном случае• На чем должна основываться эта функциональность, на которой она реализуется?
-
Array
subscript
ing• Откуда он получает эту функцию?
- Его определение
struct Array
вSwift
модуле имеет это• Ничего более общего / абстрактного?
- Он принимает,
protocol CollectionType
что обеспечивает это также• Ничего более общего / абстрактного?
- Он также принимает
protocol Indexable
...• Да, звучит как лучшее, что мы можем сделать. Можем ли мы затем расширить его, чтобы получить эту функцию, которую мы хотим?
- Но у нас очень ограниченные типы (нет
Int
) и свойства (нетcount
) работать с сейчас!• этого будет достаточно. Stdlib Свифта сделан довольно хорошо;)
¹: не правда, но это дает идею
источник
Collection
:)Вот несколько тестов, которые я провел для вас:
источник
источник
SafeIndex
- это класс, а не структура?Swift 4
Расширение для тех, кто предпочитает более традиционный синтаксис:
источник
Я нашел безопасный массив получить, установить, вставить, удалить очень полезно. Я предпочитаю регистрировать и игнорировать ошибки, поскольку все остальное скоро становится сложным для управления. Полный код ниже
тесты
источник
Используя вышеуказанное расширение, верните nil, если индекс в любое время выходит за пределы.
результат - ноль
источник
Я понимаю, что это старый вопрос. Я использую Swift5.1 на данный момент, ОП был для Swift 1 или 2?
Мне нужно было что-то подобное сегодня, но я не хотел добавлять полномасштабное расширение только для одного места и хотел что-то более функциональное (более поточно-ориентированное?). Мне также не нужно было защищать от отрицательных индексов, только те, которые могут быть после конца массива:
Для тех, кто спорит о последовательностях с nil, что вы делаете со свойствами
first
and,last
которые возвращают nil для пустых коллекций?Мне понравилось это, потому что я мог просто взять существующий материал и использовать его, чтобы получить желаемый результат. Я также знаю, что dropFirst (n) - это не целая копия коллекции, а всего лишь фрагмент. И тогда уже существующее поведение сначала вступает во владение для меня.
источник
Я думаю, что это не очень хорошая идея. Кажется, предпочтительнее создать сплошной код, который не приводит к попыткам применить индексы вне пределов.
Пожалуйста, учтите, что такая ошибка молча (как показано в приведенном выше коде) при возврате
nil
может привести к появлению еще более сложных и неразрешимых ошибок.Вы можете сделать переопределение таким же образом, как вы использовали, и просто написать подписку по-своему. Единственным недостатком является то, что существующий код не будет совместимым. Я думаю, что найти крючок для переопределения универсального x [i] (также без текстового препроцессора, как в C) будет непросто.
Самое близкое, о чем я могу думать, это
РЕДАКТИРОВАТЬ : Это на самом деле работает. Один лайнер!!
источник
if let
в этом случае не делает программу более сложной, и ошибки не становятся более неразрешимыми. Он просто объединяет традиционнуюif
проверку границ двух операторов и фактический поиск в сжатый оператор в одну строку. Есть случаи (особенно работающие в пользовательском интерфейсе) , где он является нормальным показателем , чтобы быть вне границ, как спрашиватьNSTableView
дляselectedRow
без выбора.countElements
или как OPcount
, но не так, как язык определяет запись индексов массива.Я добавил массив с
nil
s в моем случае использования:Также проверьте расширение
safe:
нижнего индекса с меткой Эрики Садун / Майка Эша: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/источник
«Обычно отклоненные изменения» для списка Swift содержат упоминание об изменении доступа к индексам массива для возврата необязательного, а не сбойного:
Таким образом, базовый индексный доступ не изменится и вернет необязательный.
Однако команда / сообщество Swift, похоже, открыта для добавления нового необязательного шаблона доступа к массивам через функцию или индекс.
Это было предложено и обсуждено на форуме Swift Evolution здесь:
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871
В частности, Крис Латтнер дал идею «+1»:
Так что это может быть возможно из коробки в какой-то будущей версии Swift. Я бы посоветовал всем, кто хочет, чтобы они внесли свой вклад в эту тему Swift Evolution
источник
Чтобы объяснить, почему операции терпят неудачу, ошибки лучше, чем необязательные. Подписчики не могут выдавать ошибки, поэтому это должен быть метод.
источник
Не уверен, почему никто не выставил расширение, которое также имеет установщик для автоматического увеличения массива
Использование простое и работает с Swift 5.1
Примечание: вы должны понимать стоимость производительности (как во времени, так и в пространстве) при выращивании массива в swift - но для небольших проблем иногда вам просто нужно заставить Swift остановить сам Swifting в ноге
источник
Я сделал простое расширение для массива
он отлично работает как задумано
пример
источник
Swift 5 Использование
в конечном итоге, но очень хотел сделать, как правило, как
но поскольку коллекция контекстуальна, я думаю, что это правильно.
источник
Когда вам нужно только получить значения из массива, и вы не возражаете против небольшого снижения производительности (т. Е. Если ваша коллекция невелика), есть альтернатива на основе словаря, которая не включает (слишком общий, для моего вкус) расширение коллекции:
источник