Jeśli mam tablicę w Swift i próbuję uzyskać dostęp do indeksu, który jest poza zakresem, pojawia się nieoczekiwany błąd w czasie wykonywania:
var str = ["Apple", "Banana", "Coconut"]
str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION
Jednak pomyślałbym z całym opcjonalnym łańcuchem i bezpieczeństwem, jakie zapewnia Swift, byłoby trywialne zrobić coś takiego:
let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
print(nonexistent)
...do other things with nonexistent...
}
Zamiast:
let theIndex = 3
if (theIndex < str.count) { // Bounds check
let nonexistent = str[theIndex] // Lookup
print(nonexistent)
...do other things with nonexistent...
}
Ale tak nie jest - muszę użyć instrukcji ol ' if
, aby sprawdzić i upewnić się, że indeks jest mniejszy niż str.count
.
Próbowałem dodać własną subscript()
implementację, ale nie jestem pewien, jak przekazać wywołanie do oryginalnej implementacji lub uzyskać dostęp do elementów (na podstawie indeksu) bez użycia notacji w indeksie dolnym:
extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
Odpowiedzi:
Odpowiedź Alexa zawiera dobrą radę i rozwiązanie tego pytania, jednak natknąłem się na lepszy sposób implementacji tej funkcjonalności:
Swift 3.2 i nowsze
Swift 3.0 i 3.1
Podziękowania dla Hamisha za opracowanie rozwiązania dla Swift 3 .
Swift 2
Przykład
źródło
safe:
nazwa parametru, aby zapewnić różnicę.return self.indices ~= index ? self[index] : nil;
Collection
typów, w którychIndices
są nie ciągłe. NaSet
przykład, jeśli mielibyśmy uzyskać dostęp do ustawionego elementu przez index (SetIndex<Element>
), możemy napotkać wyjątki w czasie wykonywania dla indeksów, które są>= startIndex
iw< endIndex
takim przypadku bezpieczny indeks dolny zawodzi (patrz np. Ten wymyślony przykład ).contains
Metoda iterację wszystkich wskaźników w ten sposób dzięki czemu jest to O (n). Lepszym sposobem jest użycie indeksu i liczenia do sprawdzenia granic.return index >= startIndex && index < endIndex ? self[index] : nil
Collection
typy mająstartIndex
,endIndex
które sąComparable
. Oczywiście to nie zadziała w przypadku niektórych dziwnych kolekcji, które na przykład nie mają wskaźników w środku, rozwiązanie zindices
bardziej ogólnym.Jeśli naprawdę chcesz tego zachowania, pachnie tak, jakbyś chciał Słownik zamiast Tablicy. Słowniki powracają
nil
podczas uzyskiwania dostępu do brakujących kluczy, co ma sens, ponieważ znacznie trudniej jest ustalić, czy klucz jest obecny w słowniku, ponieważ klucze te mogą być dowolne, gdzie w tablicy klucz musi zawierać się w przedziale:0
docount
. I to jest niezwykle powszechne, aby iterować w tym zakresie, gdzie możesz być absolutnie pewien, że ma realną wartość na każdej iteracji pętli.Myślę, że powodem, dla którego nie działa w ten sposób, jest wybór projektu dokonany przez programistów Swift. Weź przykład:
Jeśli już wiesz, że indeks istnieje, podobnie jak w większości przypadków, gdy używasz tablicy, ten kod jest świetny. Jednakże, jeżeli uzyskanie dostępu do indeks mógłby powrócić
nil
potem zmieniły typ zwracany zArray
„ssubscript
metody być opcjonalne. To zmienia kod na:Co oznacza, że będziesz musiał rozpakować opcjonalne za każdym razem, gdy przeprowadzasz iterację przez tablicę lub robisz cokolwiek innego ze znanym indeksem, tylko dlatego, że rzadko możesz uzyskać dostęp do indeksu poza zakresem. Projektanci Swift zdecydowali się na mniejsze rozpakowywanie opcji, kosztem wyjątku czasu wykonywania podczas uzyskiwania dostępu do indeksów poza granicami. Awaria jest lepsza niż błąd logiczny spowodowany przez
nil
nieoczekiwane gdzieś dane.I zgadzam się z nimi. Nie zmienisz więc domyślnej
Array
implementacji, ponieważ zniszczyłbyś cały kod, który oczekuje od opcjonalnych wartości z tablic.Zamiast tego możesz podklasę
Array
i zastąpić,subscript
aby zwrócić opcjonalną. Lub, bardziej praktycznie, można rozszerzyćArray
za pomocą metody innej niż indeks dolny, która to robi.Aktualizacja Swift 3
func get(index: Int) ->
T?
musi zostać zastąpiony przezfunc get(index: Int) ->
Element?
źródło
subscript()
na opcjonalny - była to podstawowa przeszkoda w zastąpieniu domyślnego zachowania. (Właściwie nie mogłem go uruchomić . ) Unikałem pisaniaget()
metody rozszerzenia, która jest oczywistym wyborem w innych scenariuszach (kategorie Obj-C, ktoś?), Aleget(
nie jest znacznie większa[
i sprawia, że jest jasne, że zachowanie może różnić się od tego, czego inni programiści mogą oczekiwać od operatora indeksów Swift. Dziękuję Ci!T
przemianowano naElement
. Tylko przyjazne przypomnienie :)nil
zamiast powodowania wyjątku z indeksu poza zakresem byłoby niejednoznaczne. Ponieważ np.Array<String?>
Może również zwrócić zero jako prawidłowy element kolekcji, nie będziesz w stanie rozróżnić tych dwóch przypadków. Jeśli masz swój własny typ kolekcji, o którym wiesz, że nigdy nie może zwrócićnil
wartości, czyli jest kontekstowy dla aplikacji, możesz rozszerzyć Swift o bezpieczne sprawdzanie granic, zgodnie z odpowiedzią w tym poście.Aby skorzystać z odpowiedzi Nikity Kukushkin, czasami trzeba bezpiecznie przypisać indeksy tablic, a także odczytać z nich, tj.
Oto aktualizacja odpowiedzi Nikity (Swift 3.2), która pozwala również bezpiecznie zapisywać do indeksów tablic zmiennych, dodając nazwę parametru safe:.
źródło
Obowiązuje w Swift 2
Chociaż na to pytanie udzielano już wiele razy, chciałbym przedstawić odpowiedź bardziej zgodną z kierunkami programowania Swift, która według słów Crusty'ego: „
protocol
Najpierw myśl ”• Co chcemy robić?
- Zdobądź element
Array
danego indeksu tylko wtedy, gdy jest bezpieczny, anil
poza tym• Na czym powinna polegać ta funkcjonalność?
-
Array
subscript
ing• Skąd ta funkcja?
- Ma swoją definicję
struct Array
wSwift
module.• Nic bardziej ogólnego / abstrakcyjnego?
- Przyjmuje,
protocol CollectionType
co również to zapewnia• Nic bardziej ogólnego / abstrakcyjnego?
- Przyjmuje
protocol Indexable
również ...• Tak, brzmi jak najlepiej, co możemy zrobić. Czy możemy go następnie rozszerzyć, aby uzyskać tę funkcję, której chcemy?
- Ale mamy bardzo ograniczone typy (nie
Int
) i właściwości (niecount
) do pracy teraz!• To wystarczy. Stdlib Swifta jest całkiem niezły;)
¹: nieprawda, ale daje pomysł
źródło
Collection
prawdopodobnie :)Oto kilka testów, które dla ciebie przeprowadziłem:
źródło
źródło
SafeIndex
jest klasą, a nie strukturą?Szybki 4
Rozszerzenie dla tych, którzy wolą bardziej tradycyjną składnię:
źródło
Zauważyłem, że bezpieczna tablica jest bardzo przydatna, ustawia, wstawia i usuwa. Wolę rejestrować i ignorować błędy, ponieważ wszystko inne wkrótce staje się trudne do zarządzania. Pełny kod poniżej
Testy
źródło
Używając powyższej wzmianki, zwróć zero, jeśli indeks w dowolnym momencie wykracza poza ramy.
wynik - zero
źródło
Zdaję sobie sprawę, że to stare pytanie. Używam Swift5.1 w tym momencie, OP było dla Swift 1 lub 2?
Potrzebowałem dziś czegoś takiego, ale nie chciałem dodawać rozszerzenia o pełnej skali tylko dla jednego miejsca i chciałem czegoś bardziej funkcjonalnego (bardziej bezpieczny dla wątków?). Nie musiałem też chronić się przed ujemnymi indeksami, tylko tymi, które mogą być poza końcem tablicy:
Co robisz z argumentami o sekwencjach z zerami, co robisz z właściwościami
first
ilast
właściwościami zwracającymi zero dla pustych kolekcji?Podobało mi się to, ponieważ mogłem po prostu złapać istniejące rzeczy i użyć go, aby uzyskać pożądany rezultat. Wiem również, że dropFirst (n) nie jest kopią całej kolekcji, tylko wycinek. I wtedy zachowuje się już istniejące zachowanie pierwszego.
źródło
Myślę, że to nie jest dobry pomysł. Wydaje się, że lepiej jest zbudować solidny kod, który nie spowoduje próby zastosowania indeksów spoza zakresu.
Proszę wziąć pod uwagę, że taki błąd nie powiedzie się po cichu (jak sugeruje powyższy kod) przez powrót,
nil
jest podatny na generowanie jeszcze bardziej złożonych, trudniejszych do rozwiązania błędów.Możesz zrobić to w podobny sposób, w jaki użyłeś i po prostu napisać indeksy na swój własny sposób. Jedyną wadą jest to, że istniejący kod nie będzie kompatybilny. Myślę, że znalezienie haka do zastąpienia ogólnego x [i] (również bez preprocesora tekstu jak w C) będzie trudne.
Najbliższe, o czym myślę, to
EDYCJA : To faktycznie działa. One-liner !!
źródło
if let
w tym przypadku nie powoduje, że program jest bardziej złożony, a błędy nie są łatwiejsze do usunięcia. Po prostu łączy tradycyjneif
sprawdzanie granic dwóch instrukcji i faktyczne wyszukiwanie w jedno-liniową, skróconą instrukcję. Istnieją przypadki (szczególnie pracujących w interfejsie), gdzie to jest normalne dla indeksem się poza boiskiem, jak pytaćNSTableView
dlaselectedRow
bez selekcji.countElements
lub PO zrobił zcount
, ale nie w sposób definiuje język pisania indeksów tablicy.nil
W moim przypadku użycia wstawiłem tablicę s:Sprawdź także rozszerzenie indeksu dolnego z
safe:
etykietą Erica Sadun / Mike Ash: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/źródło
Lista „Powszechnie odrzucane zmiany” dla Swift zawiera wzmiankę o zmianie dostępu do indeksu tablicy Array w celu zwrócenia opcjonalnego zamiast zawieszania się:
Tak więc podstawowy dostęp do indeksu dolnego nie zmieni się, aby zwrócić opcjonalny.
Jednak zespół / społeczność Swift wydaje się być otwarta na dodanie do tablic nowego, opcjonalnie zwracanego wzorca dostępu, za pomocą funkcji lub indeksu dolnego.
Zostało to zaproponowane i omówione na forum Swift Evolution tutaj:
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871
W szczególności Chris Lattner dał pomysł „+1”:
Może to być możliwe od razu po wyjęciu z pudełka w niektórych przyszłych wersjach Swift. Zachęcam każdego, kto chce, aby przyczynił się do tego wątku Szybkiej Ewolucji.
źródło
Aby propagować przyczyny niepowodzenia operacji, błędy są lepsze niż opcjonalne. Indeksy dolne nie mogą generować błędów, więc musi to być metoda.
źródło
Nie jestem pewien, dlaczego nikt nie wprowadził rozszerzenia, które ma również narzędzie do ustawiania automatycznego powiększania tablicy
Użycie jest łatwe i działa od Swift 5.1
Uwaga: koszta wydajności należy rozumieć (zarówno w czasie, jak i przestrzeni) przy szybkim rozwijaniu macierzy - ale w przypadku drobnych problemów czasami wystarczy, aby Swift przestał się przesuwać w stopie
źródło
Zrobiłem proste rozszerzenie dla tablicy
działa idealnie zgodnie z przeznaczeniem
Przykład
źródło
Szybkie użycie 5
skończyło się na, ale naprawdę chciałem zrobić ogólnie jak
ale ponieważ kolekcja jest kontekstowa, myślę, że jest właściwa.
źródło
Gdy potrzebujesz tylko uzyskać wartości z tablicy i nie przeszkadza ci niewielka utrata wydajności (tj. Jeśli twoja kolekcja nie jest ogromna), istnieje alternatywa oparta na słowniku, która nie wymaga (zbyt ogólna, jak dla mnie smak) rozszerzenie kolekcji:
źródło