Swift: przekazać tablicę przez odniesienie?


125

Chcę przekazać moje Swift Array account.chatsna chatsViewController.chatsodnośnik (tak, że kiedy dodać czat account.chats, chatsViewController.chatsnadal wskazuje account.chats). To znaczy, nie chcę, aby Swift rozdzielał dwie tablice, gdy account.chatszmienia się długość .


1
Skończyło się tylko co accountzmienną globalną i określaniu chatswłaściwości ChatsViewControllerjak: var chats: [Chat] { return account.chats }.
ma11hew28

Odpowiedzi:


72

Struktury w Swift są przekazywane według wartości, ale możesz użyć inoutmodyfikatora, aby zmodyfikować swoją tablicę (patrz odpowiedzi poniżej). Klasy są przekazywane przez referencje. Arrayaw DictionarySwift są implementowane jako struktury.


2
Tablica nie jest kopiowana / przekazywana według wartości w Swift - w Swift zachowuje się zupełnie inaczej niż zwykła struct. Zobacz stackoverflow.com/questions/24450284/…
Boon

14
@Boon Array jest nadal semantycznie kopiowana / przekazywana według wartości, ale jest zoptymalizowana pod kątem używania COW .
eonil

4
I nie polecam używania funkcji, NSArrayponieważ NSArraytablica Swift ma subtelne różnice semantyczne (takie jak typ referencyjny), co może prowadzić do większej liczby błędów.
eonil

1
To mnie poważnie zabijało. Waliłem się w głowę, dlaczego sprawy nie układają się tak, jak trzeba.
khunshan

2
Co jeśli używasz inoutze Structs?
Alston,

137

Dla operatora parametru funkcji używamy:

let (jest to domyślny operator, więc możemy pominąć let ), aby uczynić parametr stałym (oznacza to, że nie możemy modyfikować nawet lokalnej kopii);

var, aby uczynić go zmiennym (możemy go modyfikować lokalnie, ale nie wpłynie to na zmienną zewnętrzną, która została przekazana do funkcji); i

inout, aby był to parametr in-out. In-out oznacza w rzeczywistości przekazywanie zmiennej przez odniesienie, a nie przez wartość. Wymaga nie tylko akceptowania wartości przez odniesienie, ale także przekazywania jej przez referencję, więc przekazuj ją za pomocą & - foo(&myVar)zamiast po prostufoo(myVar)

Więc zrób to tak:

var arr = [1, 2, 3]

func addItem(inout localArr: [Int]) {
    localArr.append(4)
}

addItem(&arr)    
println(arr) // it will print [1, 2, 3, 4]

Dokładnie to nie jest tylko odniesienie, ale prawdziwy alias dla zmiennej zewnętrznej, więc możesz zrobić taką sztuczkę z dowolnym typem zmiennej, na przykład z liczbą całkowitą (możesz przypisać jej nową wartość), chociaż może to nie być dobrą praktyką i modyfikowanie takich pierwotnych typów danych może być mylące.


11
To naprawdę nie wyjaśnia, jak używać tablicy jako zmiennej instancji, do której odwołuje się, a nie kopiuje.
Matej Ukmar

2
Myślałem, że inout użył gettera i settera, aby skopiować tablicę do tymczasowej, a następnie zresetować ją po wyjściu z funkcji - tj. Kopiuje
dumbledad

2
Właściwie na wyjściu wykorzystuje kopiowanie na wejściu, kopiowanie na zewnątrz lub wywołanie według wartości. Jednak jako optymalizacja może być używana przez odniesienie. „W ramach optymalizacji, gdy argument jest wartością przechowywaną pod adresem fizycznym w pamięci, ta sama lokalizacja pamięci jest używana zarówno wewnątrz, jak i na zewnątrz ciała funkcji. Zoptymalizowane zachowanie jest znane jako wywołanie przez odniesienie; spełnia wszystkie wymagania model kopiowania do kopiowania z jednoczesnym usunięciem kosztów kopiowania ”.
Tod Cunningham,

11
W Swift 3 inoutpozycja uległa zmianie, czylifunc addItem(localArr: inout [Int])
elquimista

3
Ponadto varnie jest już dostępny dla atrybutu parametru funkcji.
elquimista

23

Zdefiniuj siebie a, BoxedArray<T>który implementuje Arrayinterfejs, ale deleguje wszystkie funkcje do przechowywanej właściwości. Takie jak

class BoxedArray<T> : MutableCollection, Reflectable, ... {
  var array : Array<T>

  // ...

  subscript (index: Int) -> T { 
    get { return array[index] }
    set(newValue) { array[index] = newValue }
  }
}

Użyj BoxedArraydowolnego miejsca, w którym chcesz użyć Array. Przypisanie BoxedArraywoli będzie przez odniesienie, jest to klasa, a tym samym zmiany w przechowywanej właściwości, poprzez Arrayinterfejs, będą widoczne dla wszystkich referencji.


Trochę przerażające rozwiązanie :) - niezupełnie eleganckie - ale wydaje się, że zadziała.
Matej Ukmar

Cóż, z pewnością jest to lepsze niż cofanie się do opcji „Użyj NSArray”, aby uzyskać „przejście przez semantykę odniesienia”!
GoZoner

13
Po prostu mam wrażenie, że definiowanie Array jako struktury zamiast klasy jest błędem projektowym języka.
Matej Ukmar

Zgadzam się. Istnieje również obrzydliwość, w której Stringjest podtypem AnyALE, jeśli import Foundationnastępnie Stringstajesz się podtypem AnyObject.
GoZoner

19

W przypadku wersji Swift 3-4 (XCode 8-9) użyj

var arr = [1, 2, 3]

func addItem(_ localArr: inout [Int]) {
    localArr.append(4)
}

addItem(&arr)
print(arr)

3

Coś jak

var a : Int[] = []
func test(inout b : Int[]) {
    b += [1,2,3,4,5]
}
test(&a)
println(a)

???


3
Myślę, że chodzi o sposób, w jaki właściwości dwóch różnych obiektów wskazują na tę samą tablicę. W takim przypadku odpowiedź Kaana jest poprawna: należy albo zawinąć tablicę w klasę, albo użyć NSArray.
Wes Campaigne

1
prawda, inout działa tylko przez cały okres istnienia funkcji (brak zachowania zamykającego)
Christian Dietrich

Drobna nitka: to func test(b: inout [Int])... może to stara składnia; Swift dostałem dopiero w 2016 roku, a ta odpowiedź pochodzi z 2014 roku, więc może kiedyś było inaczej?
Ray Toal

2

Inną opcją jest poproszenie konsumenta macierzy o to w razie potrzeby. Na przykład coś w rodzaju:

class Account {
    var chats : [String]!
    var chatsViewController : ChatsViewController!

    func InitViewController() {
        chatsViewController.getChats = { return self.chats }
    }

}

class ChatsViewController {
    var getChats: (() -> ([String]))!

    func doSomethingWithChats() {
        let chats = getChats()
        // use it as needed
    }
}

Następnie możesz dowolnie modyfikować tablicę w klasie Account. Zauważ, że to nie pomaga, jeśli chcesz również zmodyfikować tablicę z klasy kontrolera widoku.


0

Używanie inoutjest jednym z rozwiązań, ale nie wydaje mi się zbyt szybkie, ponieważ tablice są typami wartości. Stylistycznie osobiście wolę zwrócić zmutowaną kopię:

func doSomething(to arr: [Int]) -> [Int] {
    var arr = arr
    arr.append(3) // or likely some more complex operation
    return arr
}

var ids = [1, 2]
ids = doSomething(to: ids)
print(ids) // [1,2,3]

1
Takie rzeczy mają negatywny wpływ na wydajność. Zużywa trochę mniej baterii telefonu, aby zmodyfikować oryginalną tablicę :)
David Rector

Z całym szacunkiem się nie zgadzam. W tym przypadku czytelność w witrynie wywołania generalnie przewyższa wpływ wydajności. Zacznij od niezmiennego kodu, a następnie zoptymalizuj, zmieniając go później. Są przypadki z ogromnymi tablicami, w których masz rację, ale w większości aplikacji jest to 0,01% przypadek krawędzi.
ToddH

1
Nie wiem, z czym się nie zgadzasz. Istnieje spadek wydajności w przypadku kopiowania macierzy, a operacje o mniejszej wydajności zużywają więcej procesora, a tym samym więcej baterii. Myślę, że założyłeś, że mówię, że to lepsze rozwiązanie, a ja nie. Dbałem o to, aby ludzie mieli wszystkie dostępne informacje, aby dokonać świadomego wyboru.
David Rector

1
Tak, masz rację, nie ma wątpliwości, że jest bardziej wydajne. Myślałem, że sugerujesz, że inoutjest to uniwersalnie lepsze, ponieważ oszczędza baterię. Powiedziałbym, że czytelność, niezmienność i bezpieczeństwo wątków tego rozwiązania są ogólnie lepsze i że inout powinien być używany tylko jako optymalizacja w tych rzadkich przypadkach, gdy uzasadnia to przypadek użycia.
ToddH

0

użyj a NSMutableArraylub aNSArray , które są klasami

w ten sposób nie musisz implementować żadnego opakowania i możesz użyć budowania w mostkowaniu

open class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.