Swift: Diziyi referansa göre geçir?


126

Ben Swift geçmek istiyorum Array account.chatsiçin chatsViewController.chatsreferans olarak (yani bir sohbet eklediğinizde bu account.chats, chatsViewController.chatshala noktaları için account.chats). Yani, uzunluk account.chatsdeğiştiğinde Swift'in iki diziyi ayırmasını istemiyorum .


1
Sadece yapım bitti accountküresel değişken ve tanımlama chatsözelliği ChatsViewControllerolarak: var chats: [Chat] { return account.chats }.
ma11hew28

Yanıtlar:


73

Swift'deki yapılar değere göre aktarılır, ancak inoutdizinizi değiştirmek için değiştiriciyi kullanabilirsiniz (aşağıdaki yanıtlara bakın). Sınıflar referans olarak aktarılır. Arrayve DictionarySwift'de yapı olarak uygulanmaktadır.


2
Dizi Swift'de değere göre kopyalanmaz / aktarılmaz - Swift'de normal yapıya kıyasla çok farklı davranışları vardır. Stackoverflow.com/questions/24450284/… sayfasına
Boon

14
@Boon Array hala anlamsal olarak kopyalanır / değerle aktarılır, ancak yalnızca COW kullanmak için optimize edilmiştir .
eonil

4
Ve NSArrayçünkü NSArrayve Swift dizisinin ince anlamsal farklılıkları (başvuru türü gibi) olduğunu ve bu da sizi daha fazla hataya götürmesini önermiyorum .
eonil

1
Bu beni cidden öldürüyordu. İşlerin neden yolunda gitmediğini kafama vuruyordum.
khunshan

2
Ya inoutStructs ile kullanılıyorsa ?
Alston

137

Fonksiyon parametre operatörü için kullandığımız:

let (biz atlayabilirsiniz yüzden, varsayılan operatör var let (biz bile yerel kopyasını değiştiremezsiniz anlamına gelir) bir parametre sürekli kılmak); değişken yapmak için

var (yerel olarak değiştirebiliriz, ancak işleve iletilen harici değişkeni etkilemeyecektir); ve bunu bir in-out parametresi yapmak için

inout . İçeri-dışarı, değişkeni değere göre değil, referansa göre geçirmek anlamına gelir. Ve o kadar, referans olarak geçmek ile pass, referans olarak değeri kabul Ayrıca tarafından değil sadece gerektirir & - foo(&myVar)sadece yerinefoo(myVar)

Öyleyse şöyle yap:

var arr = [1, 2, 3]

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

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

Kesin olmak gerekirse, bu sadece bir referans değil, harici değişken için gerçek bir takma addır, bu nedenle herhangi bir değişken türü ile böyle bir numara yapabilirsiniz, örneğin tamsayı ile (ona yeni bir değer atayabilirsiniz), ancak bu bir iyi bir uygulama ve bunun gibi ilkel veri türlerini değiştirmek kafa karıştırıcı olabilir.


11
Bu, dizinin kopyalanmamış başvurulan bir örnek değişkeni olarak nasıl kullanılacağını gerçekten açıklamaz.
Matej Ukmar

2
İnout'un diziyi geçici olarak kopyalamak için bir alıcı ve ayarlayıcı kullandığını düşündüm ve sonra onu
işlevden çıkarken sıfırladı

2
Aslında içeri-dışarı, kopya-dışarı veya değer sonucuna göre arama kullanır. Ancak optimizasyon olarak referans olarak kullanılabilir. "Bir optimizasyon olarak, bağımsız değişken bellekte fiziksel bir adreste depolanan bir değer olduğunda, aynı bellek konumu hem işlev gövdesinin içinde hem de dışında kullanılır. Optimize edilmiş davranış, referansla çağrı olarak bilinir; tüm gereksinimleri karşılar kopyalama işleminin ek yükünü kaldırırken kopyalamalı kopyalama modeli. "
Tod Cunningham

11
Swift 3'te inoutkonum değişti, yanifunc addItem(localArr: inout [Int])
elquimista

3
Ayrıca, varartık işlev parametresi özelliği için kullanılamaz.
elquimista

23

Kendinizi BoxedArray<T>, Arrayarabirimi uygulayan ancak tüm işlevleri depolanan bir özelliğe atayan bir tanımlayın . Gibi

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

  // ...

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

Kullanım BoxedArrayBir kullanacağınız hiçbir yerinde Array. Bir BoxedArrayiradenin atanması referansla olacaktır, bu bir sınıftır ve bu nedenle, Arrayarabirim aracılığıyla depolanan özellikte yapılan değişiklikler tüm referanslar tarafından görülebilir.


Biraz korkutucu bir çözüm :) - tam olarak zarif değil - ama işe yarayacak gibi görünüyor.
Matej Ukmar

Pekala, 'referans semantiğine göre geçiş' elde etmek için 'NSArray'i Kullan'a geri dönmekten daha iyidir!
GoZoner

13
Array'i sınıf yerine yapı olarak tanımlamanın dil tasarım hatası olduğunu hissediyorum.
Matej Ukmar

Katılıyorum. İğrençtir da vardır Stringbir alt tipi olan Anysen Ama eğer import Foundationo zaman Stringbir alt türü haline gelir AnyObject.
GoZoner

19

Swift 3-4 (XCode 8-9) sürümleri için,

var arr = [1, 2, 3]

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

addItem(&arr)
print(arr)

3

Gibi bir şey

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

???


4
Bence soru, iki farklı nesnenin özelliklerinin aynı diziye işaret etmesini sağlamak için bir araç istemektir . Durum buysa, Kaan'ın cevabı doğrudur: Diziyi bir sınıfa sarmalı veya NSArray kullanmalı.
Wes Campaigne

1
doğru, giriş yalnızca işlev gövdesinin ömrü boyunca çalışır (kapanma davranışı yoktur)
Christian Dietrich

Küçük nit: bu func test(b: inout [Int])... belki bu eski bir sözdizimidir; Swift'e sadece 2016'da girdim ve bu cevap 2014'ten geliyor, bu yüzden belki işler farklıydı?
Ray Toal

2

Diğer bir seçenek de, dizinin tüketicisinin gerektiğinde sahibine sormasını sağlamaktır. Örneğin, şu satırlar boyunca bir şey:

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

Ardından, diziyi Account sınıfının içinde istediğiniz kadar değiştirebilirsiniz. Diziyi ayrıca görünüm denetleyicisi sınıfından değiştirmek isterseniz bunun size yardımcı olmayacağını unutmayın.


0

Kullanmak inoutbir çözüm ama diziler değer türleri olduğu için bana pek hızlı gelmiyor. Biçimsel olarak kişisel olarak değiştirilmiş bir kopyayı iade etmeyi tercih ederim:

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
Bu tür şeylerin bir dezavantajı var. Sadece orijinal diziyi değiştirmek için biraz daha az telefon pili kullanıyor :)
David Rector

Saygılarımla katılmıyorum. Bu durumda, arama sitesindeki okunabilirlik genellikle performans isabetinden üstündür. Değişmez kodla başlayın ve daha sonra onu değiştirilebilir hale getirerek optimize edin. Doğru olduğunuz, ancak çoğu uygulamada% 0.01 uç durum olan büyük diziler içeren durumlar vardır.
ToddH

1
Neye katılmıyorsun bilmiyorum. Diziyi kopyalamak için bir performans cezası vardır ve daha düşük performanslı işlemler daha fazla CPU ve dolayısıyla daha fazla pil kullanır. Sanırım bunun daha iyi bir çözüm olduğunu söylediğimi varsaydınız, ki ben değildim. İnsanların bilinçli bir seçim yapmak için mevcut tüm bilgilere sahip olmalarını sağlıyordum.
David Rector

1
Evet, haklısınız, girişin daha verimli olduğuna dair hiçbir soru yok. inoutPil tasarrufu sağladığı için bunun evrensel olarak daha iyi olduğunu öne sürdüğünüzü sanıyordum . Buna göre, bu çözümün okunabilirliği, değişmezliği ve iş parçacığı güvenliğinin evrensel olarak daha iyi olduğunu ve inout'un yalnızca kullanım senaryosunun gerektirdiği ender durumlarda bir optimizasyon olarak kullanılması gerektiğini söyleyebilirim.
ToddH

0

sınıflar olan a NSMutableArrayveya a kullanınNSArray

bu şekilde herhangi bir sargı yerleştirmenize gerek kalmaz ve yapıyı köprülemede kullanabilirsiniz

open class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.