Est Swift Pass By Value ou Pass By Reference

100

Je suis vraiment nouveau sur Swift et je viens de lire que les classes sont passées par référence et que les tableaux / chaînes, etc. sont copiés.

Le passage par référence est-il de la même manière qu'en Objective-C ou Java où vous passez réellement "une" référence ou est-ce un passage par référence approprié?

gran_profaci
la source
"Le passage par référence est-il le même qu'en Objective-C ou Java" Ni Objective-C ni Java n'ont le passage par référence.
newacct
2
Oui. Je le sais. Vous ne passez pas par référence. Vous passez la référence par valeur. J'ai supposé que cela était connu en répondant.
gran_profaci
Java passe par valeur, pas par référence.
6rchid

Réponses:

167

Types de choses à Swift

La règle est:

  • Les instances de classe sont des types de référence (c'est-à - dire que votre référence à une instance de classe est en fait un pointeur )

  • Les fonctions sont des types de référence

  • Tout le reste est un type valeur ; "tout le reste" signifie simplement des instances de structs et des instances d'énumérations, car c'est tout ce qu'il y a dans Swift. Les tableaux et les chaînes sont des instances de struct, par exemple. Vous pouvez passer une référence à l'une de ces choses (en tant qu'argument de fonction) en utilisant inoutet en prenant l'adresse, comme newacct l'a souligné. Mais le type est lui-même un type valeur.

Ce que les types de référence signifient pour vous

Un objet de type référence est spécial dans la pratique car:

  • La simple affectation ou le passage à une fonction peut produire plusieurs références au même objet

  • L'objet lui-même est modifiable même si sa référence est une constante ( letexplicite ou implicite).

  • Une mutation de l'objet affecte cet objet tel que vu par toutes les références à celui-ci.

Ceux-ci peuvent être des dangers, alors gardez un œil sur. D'un autre côté, passer un type de référence est clairement efficace car seul un pointeur est copié et passé, ce qui est trivial.

Ce que les types de valeur signifient pour vous

Clairement, passer un type valeur est "plus sûr" et letsignifie ce qu'il dit: vous ne pouvez pas muter une instance de struct ou une instance enum via une letréférence. D'un autre côté, cette sécurité est obtenue en faisant une copie séparée de la valeur, n'est-ce pas? Cela ne rend-il pas le passage d'un type valeur potentiellement coûteux?

Eh bien, oui et non. Ce n'est pas aussi grave que vous pourriez le penser. Comme Nate Cook l'a dit, passer un type valeur n'implique pas nécessairement une copie, car let(explicite ou implicite) garantit l'immuabilité, il n'est donc pas nécessaire de copier quoi que ce soit. Et même passer dans une varréférence ne signifie pas que les choses seront copiées, seulement qu'elles peuvent l' être si nécessaire (car il y a une mutation). Les docs vous conseillent spécifiquement de ne pas mettre votre culotte dans une torsion.

mat
la source
6
"Les instances de classe sont passées par référence. Les fonctions sont passées par référence" Non. Il est pass-by-value lorsque le paramètre n'est pas inoutquel que soit son type. Le fait que quelque chose soit pass-by-reference est orthogonal aux types.
newacct
4
@newacct Eh bien, bien sûr, vous avez raison au sens strict! Strictement, on devrait dire que tout est passe-par-valeur mais que les instances enum et les instances de struct sont des types valeur et que les instances de classe et les fonctions sont des types référence . Voir, par exemple, developer.apple.com/swift/blog/?id=10 - Voir également developer.apple.com/library/ios/documentation/Swift/Conceptual/... Cependant, je pense que ce que j'ai dit est en accord avec le général sens des mots signifie.
mat
6
Les types de droite et de valeur / types de référence ne doivent pas être confondus avec le passage par valeur / passage par référence, car les types valeur peuvent être passés par valeur ou par référence, et les types référence peuvent également être passés par valeur ou par référence.
newacct
1
@newacct discussion très utile; Je réécris mon résumé de peur qu'il ne soit trompeur.
mat
43

Il est toujours pass-by-value lorsque le paramètre ne l'est pas inout.

Il est toujours pass-by-reference si le paramètre est inout. Cependant, cela est quelque peu compliqué par le fait que vous devez utiliser explicitement l' &opérateur sur l'argument lors du passage à un inoutparamètre, il peut donc ne pas correspondre à la définition traditionnelle du passage par référence, où vous passez la variable directement.

newacct
la source
3
Cette réponse, combinée à celle de Nate Cook, était plus claire pour moi (venant de C ++) sur le fait que même un "type de référence" ne sera pas modifié en dehors de la portée de la fonction à moins que vous ne le spécifiiez explicitement (en utilisant inout)
Gobe
10
inoutn'est en fait pas un passage par référence mais une copie par copie. Cela garantit seulement qu'après l'appel de la fonction, la valeur modifiée sera affectée à l'argument d'origine. Paramètres d'
entrée
même s'il est vrai que tout est passé par valeur. Les propriétés des types de référence peuvent être modifiées à l'intérieur de la fonction comme les références de copie à la même instance.
MrAn3
42

Tout dans Swift est passé par "copie" par défaut, donc lorsque vous passez un type valeur, vous obtenez une copie de la valeur, et lorsque vous passez un type référence, vous obtenez une copie de la référence, avec tout ce que cela implique. (Autrement dit, la copie de la référence pointe toujours vers la même instance que la référence d'origine.)

J'utilise des citations effrayantes autour de la "copie" ci-dessus parce que Swift fait beaucoup d'optimisation; dans la mesure du possible, il ne se copie pas tant qu'il n'y a pas de mutation ou de possibilité de mutation. Étant donné que les paramètres sont immuables par défaut, cela signifie que la plupart du temps, aucune copie ne se produit.

Nate Cook
la source
Pour moi, c'est la meilleure réponse car elle a précisé que, par exemple, les propriétés d'occurrence peuvent être modifiées à l'intérieur de la fonction, même le paramètre étant une copie (passage par valeur) car il pointe vers la même référence.
MrAn3
9

Voici un petit exemple de code pour passer par référence. Évitez de faire cela, sauf si vous avez une bonne raison de le faire.

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

Appelez ça comme ça

var val1: String = "";
var val2: Int = -1;
ComputeSomeValues(&val1, &val2);
Chris Amelinckx
la source
Pourquoi devriez-vous éviter de faire cela?
Brainless
1
@Brainless car cela ajoute une complexité inutile au code. Il est préférable de saisir des paramètres et de renvoyer un seul résultat. Le fait de devoir faire cela signale généralement une mauvaise conception. Une autre façon de le dire est que les effets secondaires cachés dans les variables référencées transmises ne sont pas transparents pour l'appelant.
Chris Amelinckx le
Cela ne passe pas par référence. inoutest un opérateur de copie d'entrée et de sortie. Il copiera d'abord l'objet, puis écrasera l'objet d'origine après le retour de la fonction. Bien que cela puisse sembler identique, il existe des différences subtiles.
Hannes Hertach
7

Le blog Apple Swift Developer contient un article intitulé Value and Reference Types qui fournit une discussion claire et détaillée sur ce sujet.

Citer:

Les types dans Swift appartiennent à l'une des deux catégories suivantes: premièrement, les «types valeur», où chaque instance conserve une copie unique de ses données, généralement définies comme une structure, une énumération ou un tuple. Le second, «types de référence», où les instances partagent une seule copie des données, et le type est généralement défini comme une classe.

Le billet de blog Swift continue d'expliquer les différences avec des exemples et suggère quand vous utiliseriez l'un plutôt que l'autre.

pourquoi ce blanc
la source
1
Cela ne répond pas à la question. La question porte sur le passage par valeur par rapport au passage par référence, qui est complètement orthogonal aux types de valeur par rapport aux types de référence.
Jörg W Mittag
2

Les classes sont passées par références et les autres sont passées par valeur par défaut. Vous pouvez passer par référence en utilisant le inoutmot - clé.

Bahram Zangene
la source
Ceci est une erreur. inoutest un opérateur de copie d'entrée et de sortie. Il copiera d'abord l'objet, puis écrasera l'objet d'origine après le retour de la fonction. Bien que cela puisse sembler identique, il existe des différences subtiles.
Hannes Hertach
2

Lorsque vous utilisez inout avec un opérateur infixe tel que + =, le symbole & adresse peut être ignoré. Je suppose que le compilateur suppose un passage par référence?

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

origDictionary + = nouveauDictionaryToAdd

Et joliment ce dictionnaire 'ajouter' n'écrit qu'une seule fois dans la référence d'origine, donc idéal pour le verrouillage!

Jules Burt
la source
2

Classes et structures

L'une des différences les plus importantes entre les structures et les classes est que les structures sont toujours copiées lorsqu'elles sont transmises dans votre code, mais les classes sont passées par référence.

Fermetures

Si vous affectez une fermeture à une propriété d'une instance de classe et que la fermeture capture cette instance en faisant référence à l'instance ou à ses membres, vous créerez un cycle de référence fort entre la fermeture et l'instance. Swift utilise des listes de capture pour briser ces cycles de référence solides

ARC (comptage automatique de références)

Le comptage de références s'applique uniquement aux instances de classes. Les structures et les énumérations sont des types valeur, pas des types référence, et ne sont ni stockées ni transmises par référence.

Mohsen
la source