Swift: Lewati array dengan referensi?

126

Saya ingin meneruskan Swift saya Array account.chatske sebagai chatsViewController.chatsreferensi (sehingga ketika saya menambahkan obrolan ke account.chats, chatsViewController.chatstetap menunjuk ke account.chats). Yaitu, saya tidak ingin Swift memisahkan dua larik ketika panjangnya account.chatsberubah.

ma11hew28
sumber
1
Saya akhirnya hanya membuat accountvariabel global dan mendefinisikan chatsmilik ChatsViewControllersebagai: var chats: [Chat] { return account.chats }.
ma11hew28

Jawaban:

73

Struktur di Swift diteruskan oleh nilai, tetapi Anda dapat menggunakan inoutpengubah untuk mengubah larik Anda (lihat jawaban di bawah). Kelas dilewatkan dengan referensi. Arraydan Dictionarydi Swift diimplementasikan sebagai struct.

Kaan Dedeoglu
sumber
2
Array tidak disalin / diteruskan oleh nilai di Swift - ia memiliki perilaku yang sangat berbeda di Swift dibandingkan dengan struct biasa. Lihat stackoverflow.com/questions/24450284/…
Boon
14
@Boon Array masih disalin secara semantik / melewati-nilai, tetapi hanya dioptimalkan untuk menggunakan COW .
eonil
4
Dan saya tidak merekomendasikan penggunaan NSArraykarena NSArraydan array Swift memiliki perbedaan semantik yang halus (seperti tipe referensi), dan itu mungkin membawa Anda ke lebih banyak bug.
eonil
1
Ini benar-benar membunuhku. Saya membenturkan kepala saya mengapa hal-hal tidak berjalan sesuai keinginan.
khunshan
2
Bagaimana jika menggunakan inoutdengan Structs?
Alston
137

Untuk operator parameter fungsi kita menggunakan:

let (itu operator default, jadi kita bisa menghilangkan let ) untuk membuat parameter konstan (itu berarti kita tidak bisa memodifikasi bahkan salinan lokal);

var untuk membuatnya menjadi variabel (kita dapat memodifikasinya secara lokal, tetapi tidak akan mempengaruhi variabel eksternal yang telah diteruskan ke fungsi); dan

inout untuk menjadikannya sebuah parameter inout. In-out berarti meneruskan variabel dengan referensi, bukan dengan nilai. Dan itu membutuhkan tidak hanya untuk menerima nilai dengan referensi, dengan juga untuk meneruskannya dengan referensi, jadi teruskan dengan & - foo(&myVar)bukan hanyafoo(myVar)

Jadi lakukan seperti ini:

var arr = [1, 2, 3]

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

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

Tepatnya, ini bukan hanya referensi, tetapi alias nyata untuk variabel eksternal, jadi Anda dapat melakukan trik seperti itu dengan tipe variabel apa pun, misalnya dengan integer (Anda dapat menetapkan nilai baru untuk itu), meskipun itu mungkin bukan praktik yang baik dan mungkin akan membingungkan untuk mengubah tipe data primitif seperti ini.

Gene Karasev
sumber
11
Ini tidak benar-benar menjelaskan bagaimana menggunakan array sebagai variabel instan yang direferensikan tidak disalin.
Matej Ukmar
2
Saya pikir inout menggunakan getter dan setter untuk menyalin array ke sementara dan kemudian mengatur ulang saat keluar dari fungsi - yaitu menyalin
dumbledad
2
Sebenarnya in-out menggunakan copy-in copy-out atau call by value result. Namun, sebagai pengoptimalan, ini dapat digunakan sebagai referensi. "Sebagai pengoptimalan, ketika argumen adalah nilai yang disimpan di alamat fisik dalam memori, lokasi memori yang sama digunakan baik di dalam maupun di luar badan fungsi. Perilaku yang dioptimalkan dikenal sebagai panggilan dengan referensi; ini memenuhi semua persyaratan model copy-in copy-out sambil menghilangkan overhead penyalinan. "
Tod Cunningham
11
Di Swift 3, inoutposisinya telah berubah, yaitufunc addItem(localArr: inout [Int])
elquimista
3
Juga, vartidak lagi tersedia untuk atribut parameter fungsi.
elquimista
23

Definisikan diri Anda sebagai BoxedArray<T>yang mengimplementasikan Arrayantarmuka tetapi mendelegasikan semua fungsi ke properti tersimpan. Dengan demikian

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

  // ...

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

Gunakan di BoxedArraymana pun Anda akan menggunakan file Array. Penetapan BoxedArrayakan menjadi dengan referensi, itu adalah kelas, dan dengan demikian perubahan pada properti yang disimpan, melalui Arrayantarmuka, akan terlihat oleh semua referensi.

GoZoner
sumber
Solusi yang sedikit menakutkan :) - tidak terlalu elegan - tetapi tampaknya itu akan berhasil.
Matej Ukmar
Yah, itu pasti lebih baik daripada kembali ke 'Gunakan NSArray' untuk mendapatkan 'semantik referensi berlalu'!
GoZoner
13
Saya hanya merasa mendefinisikan Array sebagai struct alih-alih kelas adalah kesalahan desain bahasa.
Matej Ukmar
Saya setuju. Ada juga kekejian yang Stringmerupakan subtipe dari AnyTAPI jika Anda import Foundationkemudian Stringmenjadi subtipe dari AnyObject.
GoZoner
19

Untuk Swift versi 3-4 (XCode 8-9), gunakan

var arr = [1, 2, 3]

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

addItem(&arr)
print(arr)
pengguna6798251
sumber
3

Sesuatu seperti

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

???

Christian Dietrich
sumber
3
Saya pikir pertanyaannya adalah menanyakan cara untuk memiliki properti dari dua objek yang berbeda mengarah ke array yang sama. Jika demikian, jawaban Kaan benar: salah satu harus membungkus array di kelas, atau menggunakan NSArray.
Wes Campaigne
1
benar, masuk bekerja hanya untuk seumur hidup fungsi tubuh (tidak ada perilaku penutupan)
Christian Dietrich
Minor nit: itu func test(b: inout [Int])... mungkin ini adalah sintaks lama; Saya baru masuk ke Swift pada 2016 dan jawaban ini dari 2014 jadi mungkin dulu semuanya berbeda?
Ray Toal
2

Satu opsi lainnya adalah meminta konsumen dari array meminta pemiliknya sesuai kebutuhan. Misalnya, sesuatu di sepanjang baris:

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

Anda kemudian dapat memodifikasi array sebanyak yang Anda suka di dalam kelas Akun. Perhatikan bahwa ini tidak membantu Anda jika Anda juga ingin mengubah larik dari kelas pengontrol tampilan.

elRobbo
sumber
0

Menggunakan inoutadalah salah satu solusi tetapi tidak terasa cepat bagi saya karena array adalah tipe nilai. Secara gaya, saya pribadi lebih suka mengembalikan salinan yang dimutasi:

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
sumber
1
Ada penurunan kinerja untuk hal semacam ini. Ini menggunakan baterai ponsel sedikit lebih sedikit untuk hanya memodifikasi susunan aslinya :)
David Rector
Saya dengan hormat tidak setuju. Dalam hal ini keterbacaan di situs panggilan biasanya mengalahkan kinerja yang dicapai. Mulailah dengan kode yang tidak dapat diubah lalu optimalkan dengan membuatnya dapat berubah nanti. Ada kasus dengan array besar di mana Anda benar tetapi di sebagian besar aplikasi itu kasus tepi 0,01%.
ToddH
1
Saya tidak tahu apa yang tidak Anda setujui. Ada hukuman kinerja untuk menyalin larik dan operasi kinerja yang lebih rendah memang menggunakan lebih banyak CPU dan karena itu lebih banyak baterai. Saya pikir Anda berasumsi bahwa saya mengatakan bahwa ini adalah solusi yang lebih baik, padahal sebenarnya tidak. Saya memastikan orang-orang memiliki semua informasi yang tersedia untuk membuat pilihan yang tepat.
David Rector
1
Ya, Anda benar, tidak ada pertanyaan inout yang lebih efisien. Saya pikir Anda menyarankan itu inoutsecara universal lebih baik karena menghemat baterai. Yang menurut saya keterbacaan, keabadian, dan keamanan utas dari solusi ini secara universal lebih baik dan inout hanya boleh digunakan sebagai pengoptimalan dalam kasus yang jarang terjadi di mana kasus penggunaan menjaminnya.
ToddH
0

menggunakan a NSMutableArrayatau a NSArray, yang merupakan kelas

Dengan cara ini Anda tidak perlu menerapkan pembungkus apa pun dan dapat menggunakan build in bridging

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