Se eu tiver uma matriz no Swift e tentar acessar um índice que está fora dos limites, há um erro de tempo de execução não surpreendente:
var str = ["Apple", "Banana", "Coconut"]
str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION
No entanto, eu teria pensado que, com todo o encadeamento e segurança opcionais que o Swift traz, seria trivial fazer algo como:
let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
print(nonexistent)
...do other things with nonexistent...
}
Ao invés de:
let theIndex = 3
if (theIndex < str.count) { // Bounds check
let nonexistent = str[theIndex] // Lookup
print(nonexistent)
...do other things with nonexistent...
}
Mas esse não é o caso - eu tenho que usar a if
instrução ol ' para verificar e garantir que o índice seja menor que str.count
.
Tentei adicionar minha própria subscript()
implementação, mas não tenho certeza de como passar a chamada para a implementação original ou acessar os itens (com base em índice) sem usar a notação subscrita:
extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
Respostas:
A resposta de Alex tem bons conselhos e solução para a pergunta, no entanto, eu encontrei uma maneira mais agradável de implementar essa funcionalidade:
Swift 3.2 e mais recente
Swift 3.0 e 3.1
Agradecemos a Hamish por ter encontrado a solução para o Swift 3 .
Swift 2
Exemplo
fonte
safe:
nome do parâmetro incluído para garantir a diferença.return self.indices ~= index ? self[index] : nil;
Collection
tipos onde oIndices
são não é contíguo. PorSet
exemplo, por exemplo, se acessarmos um elemento definido por index (SetIndex<Element>
), podemos executar exceções de tempo de execução para índices que são>= startIndex
e< endIndex
, nesse caso, o índice seguro falha (veja, por exemplo, este exemplo inventado ).contains
método irá percorrer todos os índices, tornando assim um O (n). Uma maneira melhor é usar o índice e contar para verificar os limites.return index >= startIndex && index < endIndex ? self[index] : nil
Collection
tipos possuemstartIndex
,endIndex
quais sãoComparable
. Obviamente, isso não funcionará para algumas coleções estranhas que, por exemplo, não têm índices intermediários, a soluçãoindices
é mais geral.Se você realmente deseja esse comportamento, cheira como se você quisesse um Dicionário em vez de uma Matriz. Os dicionários retornam
nil
ao acessar as chaves ausentes, o que faz sentido, porque é muito mais difícil saber se uma chave está presente em um dicionário, pois essas chaves podem ser qualquer coisa, onde em uma matriz a chave deve estar em um intervalo de:0
acount
. E é incrivelmente comum fazer iterações nesse intervalo, onde você pode ter certeza absoluta de ter um valor real em cada iteração de um loop.Acho que a razão de não funcionar dessa maneira é uma escolha de design feita pelos desenvolvedores do Swift. Veja o seu exemplo:
Se você já sabe que o índice existe, como na maioria dos casos em que você usa uma matriz, esse código é ótimo. No entanto, se o acesso a um índice poderia voltar
nil
então você tiver alterado o tipo de retorno deArray
'ssubscript
método para ser um opcional. Isso altera seu código para:O que significa que você precisaria desembrulhar um opcional toda vez que iterasse em uma matriz ou fizesse qualquer outra coisa com um índice conhecido, apenas porque raramente você pode acessar um índice fora dos limites. Os designers do Swift optaram por menos desembrulhar os opcionais, às custas de uma exceção de tempo de execução ao acessar índices fora dos limites. E uma falha é preferível a um erro de lógica causado por um que
nil
você não esperava em seus dados em algum lugar.E eu concordo com eles. Portanto, você não alterará a
Array
implementação padrão, porque você quebraria todo o código que espera valores não opcionais das matrizes.Em vez disso, você pode subclassificar
Array
e substituirsubscript
para retornar um opcional. Ou, mais praticamente, você pode estenderArray
com um método não subscrito que faz isso.Atualização Swift 3
func get(index: Int) ->
T?
precisa ser substituído porfunc get(index: Int) ->
Element?
fonte
subscript()
para um opcional - esse foi o principal obstáculo enfrentado na substituição do comportamento padrão. (Na verdade, eu não conseguia fazê-lo funcionar . ) Evitava escrever umget()
método de extensão, que é a escolha óbvia em outros cenários (categorias Obj-C, alguém?), Masget(
não é muito maior do que isso[
, e o torna claro que o comportamento pode diferir do que outros desenvolvedores podem esperar do operador de assinatura Swift. Obrigado!T
foi renomeada paraElement
. Apenas um lembrete amigável :)nil
vez de causar uma exceção de um índice fora dos limites seria ambíguo. Como, por exemplo,Array<String?>
também poderia retornar nulo como um membro válido da coleção, você não seria capaz de diferenciar esses dois casos. Se você tem seu próprio tipo de coleção que você sabe que nunca pode retornar umnil
valor, também conhecido como contextual ao aplicativo, você pode estender o Swift para verificação de limites seguros, conforme respondido nesta postagem.Para basear-se na resposta de Nikita Kukushkin, às vezes você precisa atribuir com segurança a índices de matriz e ler a partir deles, ou seja,
Então, aqui está uma atualização da resposta da Nikita (Swift 3.2) que também permite gravar com segurança em índices de matriz mutáveis, adicionando o nome do parâmetro safe:.
fonte
Válido no Swift 2
Mesmo que isso já tenha sido respondido várias vezes, eu gostaria de apresentar uma resposta mais alinhada para onde está indo a moda da programação Swift, que nas palavras de Crusty¹ é: "Pense
protocol
primeiro"• O que nós queremos fazer?
- Obter um elemento de um
Array
determinado índice somente quando for seguro;nil
caso contrário,• Em que essa funcionalidade deve basear sua implementação?
-
Array
subscript
ing• De onde vem esse recurso a partir de?
- Sua definição
struct Array
noSwift
módulo possui:• Nada mais genérico / abstrato?
- Adota o
protocol CollectionType
que garante isso também.• Nada mais genérico / abstrato?
- Adota
protocol Indexable
também ...• Sim, parece o melhor que podemos fazer. Podemos então estendê-lo para ter esse recurso que queremos?
- ) para trabalhar agora! Mas temos tipos muito limitados (não
Int
) e propriedades (nãocount
• Será o suficiente. O stdlib do Swift é feito muito bem;)
Not: não é verdade, mas dá a ideia
fonte
Collection
provavelmente :)Aqui estão alguns testes que eu executei para você:
fonte
fonte
SafeIndex
é uma classe e não uma estrutura?Swift 4
Uma extensão para quem prefere uma sintaxe mais tradicional:
fonte
Eu encontrei array seguro obter, definir, inserir, remover muito útil. Prefiro registrar e ignorar os erros, pois tudo o mais fica difícil de gerenciar. Código completo abaixo
Testes
fonte
Utilizando a extensão acima mencionada, retorne nil se a qualquer momento o índice ficar fora do limite.
resultado - nulo
fonte
Sei que essa é uma pergunta antiga. Estou usando o Swift5.1 neste momento, o OP foi para o Swift 1 ou 2?
Eu precisava de algo parecido hoje, mas não queria adicionar uma extensão em escala total apenas para um local e queria algo mais funcional (mais seguro para threads?). Eu também não precisava me proteger contra índices negativos, apenas aqueles que poderiam ter passado do final de uma matriz:
Para aqueles que discutem sobre Sequências com nada, o que você faz sobre as propriedades
first
elast
que retornam nada para coleções vazias?Eu gostei disso, porque eu podia simplesmente pegar as coisas existentes e usá-las para obter o resultado desejado. Sei também que dropFirst (n) não é uma cópia inteira da coleção, apenas uma fatia. E então o comportamento já existente do primeiro assume o controle para mim.
fonte
Eu acho que isso não é uma boa ideia. Parece preferível criar código sólido que não resulte na tentativa de aplicar índices fora dos limites.
Considere que esse erro falha silenciosamente (conforme sugerido pelo seu código acima) retornando
nil
é propenso a produzir erros ainda mais complexos e intratáveis.Você pode fazer a substituição da mesma maneira que usou e apenas escrever os subscritos à sua maneira. A única desvantagem é que o código existente não será compatível. Acho que encontrar um gancho para substituir o x [i] genérico (também sem um pré-processador de texto como em C) será um desafio.
O mais próximo que consigo pensar é
EDIT : Isso realmente funciona. One-liner !!
fonte
if let
nesse caso não torna o programa mais complexo nem os erros mais intratáveis. Ele simplesmente condensa a verificação tradicional deif
limites de duas instruções e a consulta real em uma instrução condensada de linha única. Há casos (particularmente que trabalham em uma UI) onde é normal para um índice para ser fora dos limites, como pedir umNSTableView
para oselectedRow
sem uma seleção.countElements
ou como o OP fez comcount
, mas não da maneira como a linguagem define a gravação de subscritos de matriz.Eu preenchi a matriz com
nil
s no meu caso de uso:Verifique também a extensão do subscrito com o
safe:
rótulo de Erica Sadun / Mike Ash: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/fonte
A lista "Alterações comumente rejeitadas" para Swift contém uma menção à alteração do acesso ao subscrito da matriz para retornar um opcional em vez de travar:
Portanto, o acesso básico ao subscrito não será alterado para retornar um opcional.
No entanto, a equipe / comunidade Swift parece aberta a adicionar um novo padrão de acesso com retorno opcional às matrizes, por meio de uma função ou de um subscrito.
Isso foi proposto e discutido no fórum do Swift Evolution aqui:
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871
Notavelmente, Chris Lattner deu à idéia um "+1":
Portanto, isso pode ser possível fora da caixa em alguma versão futura do Swift. Eu incentivaria qualquer um que quisesse contribuir com esse segmento do Swift Evolution.
fonte
Para propagar por que as operações falham, os erros são melhores que os opcionais. Os subscritos não podem gerar erros, por isso deve ser um método.
fonte
Não sei por que ninguém colocou uma extensão que também possui um setter para aumentar automaticamente a matriz
O uso é fácil e funciona a partir do Swift 5.1
Nota: Você deve entender o custo de desempenho (tanto em tempo quanto em espaço) ao aumentar rapidamente uma matriz - mas para pequenos problemas, às vezes, você só precisa que o Swift pare de se movimentar no pé
fonte
Eu fiz uma extensão simples para array
funciona perfeitamente como projetado
Exemplo
fonte
Uso do Swift 5
acabou com, mas realmente queria fazer geralmente como
mas como a coleção é contextual, acho que é adequada.
fonte
Quando você só precisa obter valores de uma matriz e não se importa com uma pequena penalidade de desempenho (por exemplo, se sua coleção não for grande), há uma alternativa baseada em dicionário que não envolve (muito genérica, para meu extensão) coleção:
fonte