Swift: Passer le tableau par référence?

126

Je veux passer mon Swift Array account.chatsà chatsViewController.chatspar référence (de sorte que lorsque j'ajoute une discussion à account.chats, chatsViewController.chatspointe toujours vers account.chats). Ie, je ne veux pas que Swift sépare les deux tableaux lorsque la longueur des account.chatschangements.

ma11hew28
la source
1
J'ai fini par faire juste accountune variable globale et la définition de la chatspropriété ChatsViewControllercomme: var chats: [Chat] { return account.chats }.
ma11hew28

Réponses:

73

Les structures dans Swift sont passées par valeur, mais vous pouvez utiliser le inoutmodificateur pour modifier votre tableau (voir les réponses ci-dessous). Les classes sont passées par référence. Arrayet Dictionarydans Swift sont implémentés en tant que structures.

Kaan Dedeoglu
la source
2
Array n'est pas copié / passé par valeur dans Swift - il a un comportement très différent dans Swift par rapport à la structure régulière. Voir stackoverflow.com/questions/24450284/…
Boon
14
@Boon Array est toujours copié / passé par valeur sémantiquement, mais simplement optimisé pour utiliser COW .
eonil
4
Et je ne recommande pas d'utiliser NSArrayparce que NSArrayet le tableau Swift ont des différences sémantiques subtiles (telles que le type de référence), ce qui peut vous conduire à plus de bogues.
eonil
1
Cela me tuait gravement. Je me cognais la tête pourquoi les choses ne fonctionnaient pas comme ça.
khunshan
2
Et si vous utilisez inoutavec Structs?
Alston
137

Pour l'opérateur de paramètre de fonction, nous utilisons:

let (c'est l'opérateur par défaut, nous pouvons donc omettre let ) pour rendre un paramètre constant (cela signifie que nous ne pouvons même pas modifier la copie locale);

var pour le rendre variable (nous pouvons le modifier localement, mais cela n'affectera pas la variable externe qui a été passée à la fonction); et

inout pour en faire un paramètre in-out. In-out signifie en fait passer la variable par référence et non par valeur. Et il faut non seulement accepter la valeur par référence, mais aussi la passer par référence, alors passez-la avec & -foo(&myVar) au lieu de simplementfoo(myVar)

Alors faites-le comme ceci:

var arr = [1, 2, 3]

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

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

Pour être exact, ce n'est pas seulement une référence, mais un véritable alias pour la variable externe, vous pouvez donc faire une telle astuce avec n'importe quel type de variable, par exemple avec un entier (vous pouvez lui attribuer une nouvelle valeur), bien que ce ne soit pas un bonne pratique et il peut être déroutant de modifier les types de données primitifs comme celui-ci.

Gene Karasev
la source
11
Cela n'explique pas vraiment comment utiliser tableau comme variable d'instance référencée non copiée.
Matej Ukmar
2
Je pensais qu'inout utilisait un getter et un setter pour copier le tableau dans un
fichier
2
En fait, in-out utilise un copy-in copy-out ou un résultat d'appel par valeur. Cependant, comme optimisation, il peut être utilisé par référence. "À titre d'optimisation, lorsque l'argument est une valeur stockée à une adresse physique en mémoire, le même emplacement mémoire est utilisé à l'intérieur et à l'extérieur du corps de la fonction. Le comportement optimisé est appelé appel par référence; il satisfait toutes les exigences de le modèle de copie en entrée tout en supprimant les frais généraux de copie. "
Tod Cunningham
11
Dans Swift 3, la inoutposition a changé, c'estfunc addItem(localArr: inout [Int])
elquimista
3
En outre, varn'est plus disponible pour l'attribut de paramètre de fonction.
elquimista
23

Définissez vous-même un BoxedArray<T>qui implémente l' Arrayinterface mais délègue toutes les fonctions à une propriété stockée. En tant que tel

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

  // ...

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

Utilisez le BoxedArraypartout où vous utiliseriez un Array. L'attribution d'un BoxedArraytestament se fait par référence, c'est une classe, et donc les modifications apportées à la propriété stockée, via l' Arrayinterface, seront visibles par toutes les références.

GoZoner
la source
Une solution un peu effrayante :) - pas vraiment élégante - mais il semble que cela fonctionnerait.
Matej Ukmar
Eh bien, c'est certainement mieux que de revenir à «Utiliser NSArray» pour obtenir «passer par la sémantique de référence»!
GoZoner
13
J'ai juste le sentiment que définir Array comme struct au lieu de class est une erreur de conception du langage.
Matej Ukmar
Je suis d'accord. Il y a aussi l'abomination où Stringest un sous-type de AnyMAIS si vous devenez import Foundationalors Stringun sous-type de AnyObject.
GoZoner
19

Pour les versions 3-4 de Swift (XCode 8-9), utilisez

var arr = [1, 2, 3]

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

addItem(&arr)
print(arr)
user6798251
la source
3

Quelque chose comme

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

???

Christian Dietrich
la source
3
Je pense que la question demande un moyen d'avoir des propriétés de deux objets différents pointant vers le même tableau. Si tel est le cas, la réponse de Kaan est correcte: il faut soit envelopper le tableau dans une classe, soit utiliser NSArray.
Wes Campaigne le
1
droite, inout ne fonctionne que pour la durée de vie du corps de fonction (pas de comportement de fermeture)
Christian Dietrich
Mineure: c'est func test(b: inout [Int])... peut-être que c'est une vieille syntaxe; Je ne suis entré dans Swift qu'en 2016 et cette réponse date de 2014, alors peut-être que les choses étaient différentes?
Ray Toal
2

Une autre option consiste à demander au consommateur de la baie de le demander au propriétaire si nécessaire. Par exemple, quelque chose du genre:

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
    }
}

Vous pouvez ensuite modifier le tableau autant que vous le souhaitez dans la classe Account. Notez que cela ne vous aide pas si vous souhaitez également modifier le tableau à partir de la classe de contrôleur de vue.

elRobbo
la source
0

L'utilisation inoutest une solution, mais cela ne me semble pas très rapide car les tableaux sont des types valeur. Stylistiquement, je préfère personnellement renvoyer une copie mutée:

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]
ToddH
la source
1
Il y a un inconvénient de performance à ce genre de chose. Il utilise un peu moins de batterie de téléphone pour simplement modifier le tableau d'origine :)
David Rector
Je suis respectueusement en désaccord. Dans ce cas, la lisibilité sur le site d'appel l'emporte généralement sur les performances. Commencez avec du code immuable, puis optimisez-le en le rendant mutable plus tard. Il y a des cas avec des tableaux massifs où vous avez raison, mais dans la plupart des applications, c'est un cas à 0,01%.
ToddH le
1
Je ne sais pas sur quoi vous êtes en désaccord. Il y a une pénalité de performances pour copier la baie et les opérations de moindre performance utilisent plus de CPU et donc plus de batterie. Je pense que vous avez supposé que je disais que c'était une meilleure solution, ce que je n'étais pas. Je m'assurais que les gens disposent de toutes les informations nécessaires pour faire un choix éclairé.
David Rector
1
Oui, vous avez raison, il ne fait aucun doute qu'Inout est plus efficace. Je pensais que vous suggériez que inoutc'est universellement meilleur parce que cela permet d'économiser de la batterie. Ce à quoi je dirais que la lisibilité, l'immuabilité et la sécurité des threads de cette solution sont universellement meilleures et que inout ne devrait être utilisé comme optimisation que dans les rares cas où le cas d'utilisation le justifie.
ToddH
0

utiliser un NSMutableArrayou unNSArray , qui sont des classes

de cette façon, vous n'avez pas besoin d'implémenter de wraper et pouvez utiliser la construction en pontage

open class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration
Peter Lapisu
la source