Czy wartość jest szybkim przekazaniem lub wartością odniesienia

100

Jestem naprawdę nowy w Swift i właśnie przeczytałem, że klasy są przekazywane przez referencje, a tablice / ciągi itp. Są kopiowane.

Czy przekazywanie przez referencję jest takie samo jak w Objective-C lub Javie, w których faktycznie przekazuje się referencję „a”, czy też jest to właściwe przekazywanie przez referencję?

gran_profaci
źródło
„Czy przejście przez referencję jest takie samo jak w Objective-C lub Java” Ani Objective-C, ani Java nie mają przekazywania przez referencję.
newacct
2
Tak. Wiem to. Nie przechodzisz przez odniesienie. Przekazujesz odwołanie według wartości. Założyłem, że o tym wiedziałem, odpowiadając.
gran_profaci,
Java przekazuje wartość, a nie odwołanie.
6rchid

Odpowiedzi:

167

Rodzaje rzeczy w języku Swift

Zasada jest taka:

  • Instancje klasy są typy referencyjne (czyli Twój odwołanie do instancji klasy jest w rzeczywistości wskaźnik )

  • Funkcje są typami odwołań

  • Wszystko inne jest typem wartości ; „Wszystko inne” oznacza po prostu wystąpienia struktur i wystąpienia wyliczeń, ponieważ to wszystko, co znajduje się w języku Swift. Na przykład tablice i ciągi znaków są instancjami struktur. Państwo może przekazać odwołanie do jednej z tych rzeczy (jako argument funkcji) za pomocą inouti przy adresu, jak newacct podkreślił. Ale sam typ jest typem wartości.

Co oznaczają dla Ciebie typy referencyjne

Obiekt typu referencyjnego jest w praktyce wyjątkowy, ponieważ:

  • Zwykłe przypisanie lub przekazanie do funkcji może dać wiele odwołań do tego samego obiektu

  • Sam obiekt jest zmienny, nawet jeśli odniesienie do niego jest stałą ( letjawną lub domniemaną).

  • Mutacja obiektu wpływa na ten obiekt, jak widać po wszystkich odniesieniach do niego.

To może być niebezpieczne, więc miej oko. Z drugiej strony przekazywanie typu referencyjnego jest wyraźnie wydajne, ponieważ tylko wskaźnik jest kopiowany i przekazywany, co jest trywialne.

Jakie typy wartości oznaczają dla Ciebie

Oczywiście przekazywanie typu wartości jest „bezpieczniejsze” i letoznacza to, co jest napisane: nie można zmutować instancji struktury ani instancji wyliczenia za pomocą letodwołania. Z drugiej strony, bezpieczeństwo uzyskuje się wykonując osobną kopię wartości, prawda? Czy to nie sprawia, że ​​przekazywanie typu wartości jest potencjalnie kosztowne?

Cóż, tak i nie. Nie jest tak źle, jak mogłoby się wydawać. Jak powiedział Nate Cook, przekazanie typu wartości niekoniecznie oznacza kopiowanie, ponieważ let(jawne lub dorozumiane) gwarantuje niezmienność, więc nie ma potrzeby kopiowania czegokolwiek. I nawet przejście do varreferencji nie oznacza, że ​​rzeczy zostaną skopiowane, tylko, że mogą być w razie potrzeby (ponieważ istnieje mutacja). Dokumentacja wyraźnie radzi, aby nie skręcać majtek.

matowe
źródło
6
„Instancje klas są przekazywane przez odwołanie. Funkcje są przekazywane przez odwołanie” Nie. Jest to wartość przekazywana, gdy parametr nie jest inoutniezależny od typu. To, czy coś jest przekazane przez odwołanie, jest ortogonalne do typów.
newacct
5
@newacct Cóż, oczywiście masz rację w ścisłym tego słowa znaczeniu! Ściśle rzecz biorąc, należy powiedzieć, że wszystko jest przekazywane przez wartość, ale instancje wyliczenia i instancje struktur są typami wartości, a instancje klas i funkcje są typami referencyjnymi . Zobacz na przykład developer.apple.com/swift/blog/?id=10 - Zobacz także developer.apple.com/library/ios/documentation/Swift/Conceptual/… Jednak myślę, że to, co powiedziałem, jest zgodne z ogólnymi znaczenie słów.
mat.
6
Right, a typy wartości / typy odwołań nie powinny być mylone z przekazywaniem przez wartość / przekazywaniem przez odwołanie, ponieważ typy wartości mogą być przekazywane przez wartość lub przez odwołanie, a typy odwołań mogą być również przekazywane przez wartość lub przez odwołanie.
newacct
1
@newacct bardzo przydatna dyskusja; Przepisuję swoje podsumowanie, żeby nie wprowadzało w błąd.
mat.
43

Zawsze jest wartością przekazaną, gdy parametr nie jest inout.

Jest to zawsze przekazane przez odwołanie, jeśli parametr ma wartość inout. Jest to jednak nieco skomplikowane ze względu na fakt, że musisz jawnie użyć &operatora na argumencie podczas przekazywania inoutparametru, więc może nie pasować do tradycyjnej definicji przekazywania przez referencję, w której przekazuje się zmienną bezpośrednio.

newacct
źródło
3
Ta odpowiedź, w połączeniu z odpowiedzią Nate'a Cooka, była dla mnie jaśniejsza (pochodząca z C ++) i dotyczyła faktu, że nawet „typ referencyjny” nie zostanie zmodyfikowany poza zakresem funkcji, chyba że wyraźnie go określisz (używając inout)
Gobe
10
inoutw rzeczywistości nie jest przekazywana przez odniesienie, ale kopiowanie na zewnątrz Gwarantuje tylko, że po wywołaniu funkcji zmodyfikowana wartość zostanie przypisana do oryginalnego argumentu. Parametry
wejścia
chociaż prawdą jest, że wszystko jest przekazywane według wartości. Właściwości typów odwołań można modyfikować wewnątrz funkcji, tak jak kopiowanie odwołań do tego samego wystąpienia.
MrAn3
42

Wszystko w Swift jest domyślnie przekazywane przez „kopiowanie”, więc kiedy przekazujesz typ wartości, otrzymujesz kopię wartości, a kiedy przekazujesz typ referencyjny, otrzymujesz kopię referencji ze wszystkim, co to oznacza. (Oznacza to, że kopia odniesienia nadal wskazuje na ten sam przypadek, co oryginalne odniesienie).

Używam przerażających cudzysłowów wokół „kopii” powyżej, ponieważ Swift przeprowadza wiele optymalizacji; jeśli to możliwe, nie kopiuje się, dopóki nie nastąpi mutacja lub możliwość mutacji. Ponieważ parametry są domyślnie niezmienne, oznacza to, że w większości przypadków kopiowanie się nie dzieje.

Nate Cook
źródło
Dla mnie jest to najlepsza odpowiedź, ponieważ wyjaśniła, że ​​na przykład właściwości instancji można modyfikować wewnątrz funkcji, nawet parametr będący kopią (przekazywana przez wartość), ponieważ wskazuje na to samo odniesienie.
MrAn3
9

Oto mały przykład kodu do przekazywania przez odniesienie. Unikaj tego, chyba że masz do tego silny powód.

func ComputeSomeValues(_ value1: inout String, _ value2: inout Int){
    value1 = "my great computation 1";
    value2 = 123456;
}

Nazwij to tak

var val1: String = "";
var val2: Int = -1;
ComputeSomeValues(&val1, &val2);
Chris Amelinckx
źródło
Dlaczego powinieneś tego unikać?
Bez mózgów
1
@Brainless, ponieważ dodaje niepotrzebnej złożoności do kodu. Najlepiej jest wziąć parametry i zwrócić pojedynczy wynik. Konieczność tego zwykle sygnalizuje zły projekt. Innym sposobem jest to, że ukryte efekty uboczne w przekazanych zmiennych, do których istnieją odniesienia, nie są przezroczyste dla wywołującego.
Chris Amelinckx
Nie jest to traktowane jako odniesienie. inoutjest operatorem kopiowania na, kopiowania. Najpierw skopiuje obiekt, a następnie nadpisze oryginalny obiekt po powrocie funkcji. Chociaż może wydawać się to samo, istnieją subtelne różnice.
Hannes Hertach
7

Na blogu Apple Swift Developer znajduje się post zatytułowany Typy wartości i odniesienia, który zawiera jasną i szczegółową dyskusję na ten temat.

Cytować:

Typy w języku Swift należą do jednej z dwóch kategorii: po pierwsze, „typy wartości”, gdzie każda instancja przechowuje unikalną kopię swoich danych, zwykle definiowaną jako struktura, wyliczenie lub krotka. Drugi, „typy referencyjne”, gdzie instancje współużytkują pojedynczą kopię danych, a typ jest zwykle definiowany jako klasa.

W poście na blogu Swift nadal wyjaśniono różnice z przykładami i sugerowano, kiedy należy użyć jednego nad drugim.

biały biały
źródło
1
To nie odpowiada na pytanie. Pytanie dotyczy przekazywania przez wartość w porównaniu z przekazywaniem przez odniesienie, które jest całkowicie ortogonalne do typów wartości w porównaniu z typami referencyjnymi.
Jörg W Mittag
2

Klasy są przekazywane przez referencje, a inne są przekazywane domyślnie przez wartość. Możesz przekazać odwołanie za pomocą inoutsłowa kluczowego.

Bahram Zangene
źródło
To jest niepoprawne. inoutjest operatorem kopiowania na, kopiowania. Najpierw skopiuje obiekt, a następnie nadpisze oryginalny obiekt po powrocie funkcji. Chociaż może wydawać się to samo, istnieją subtelne różnice.
Hannes Hertach
2

Gdy używasz inout z operatorem infix, takim jak + =, wówczas symbol adresu & może zostać zignorowany. Domyślam się, że kompilator zakłada przekazanie przez odniesienie?

extension Dictionary {
    static func += (left: inout Dictionary, right: Dictionary) {
        for (key, value) in right {
            left[key] = value
        }
    }
}

origDictionary + = newDictionaryToAdd

I ładnie, ten słownik „add” pozwala tylko pisać do oryginalnego odniesienia, więc świetnie nadaje się do blokowania!

Jules Burt
źródło
2

Klasy i struktury

Jedną z najważniejszych różnic między strukturami i klasami jest to, że struktury są zawsze kopiowane, gdy są przekazywane w kodzie, ale klasy są przekazywane przez odwołanie.

Domknięcia

Jeśli przypiszesz zamknięcie do właściwości instancji klasy, a zamknięcie przechwytuje to wystąpienie, odwołując się do instancji lub jej elementów członkowskich, utworzysz silny cykl odwołań między zamknięciem a instancją. Swift używa list przechwytywania, aby przerwać te silne cykle referencyjne

ARC (automatyczne liczenie referencji)

Zliczanie odwołań dotyczy tylko wystąpień klas. Struktury i wyliczenia są typami wartości, a nie typami odwołań i nie są przechowywane ani przekazywane przez odwołanie.

mohsen
źródło