Mengapa Memilih Struct Over Class?

476

Bermain-main dengan Swift, yang berasal dari latar belakang Java, mengapa Anda ingin memilih Struct daripada Class? Sepertinya mereka adalah hal yang sama, dengan Struct yang menawarkan fungsionalitas yang lebih sedikit. Mengapa memilihnya?

bluedevil2k
sumber
11
Struktur selalu disalin ketika mereka diedarkan dalam kode Anda, dan jangan menggunakan penghitungan referensi. sumber: developer.apple.com/library/prerelease/ios/documentation/swift/…
holex
4
Saya akan mengatakan bahwa struct lebih tepat untuk menyimpan data, bukan logika. Untuk berbicara dalam istilah Java, bayangkan struct sebagai "Nilai Objek".
Vincent Guerci
6
Saya kagum dalam seluruh percakapan ini tidak ada penyebutan langsung copy-on-write alias malas . Kekhawatiran tentang kinerja salinan struktural sebagian besar diperdebatkan karena desain ini.
David James
3
Memilih struct daripada kelas bukan masalah pendapat. Ada alasan khusus untuk memilih satu atau yang lain.
David James
Saya sangat merekomendasikan untuk melihat Mengapa Array bukan threadSafe . Ini terkait karena Array & Structs keduanya tipe nilai. Semua jawaban di sini menyebutkan bahwa dengan tipe struct / array / nilai tidak akan pernah memiliki masalah Keamanan utas, tetapi ada kasus sudut di mana Anda akan melakukannya.
Sayang

Jawaban:

548

Menurut WWDC 2015 talk Protocol Oriented Programming di Swift ( video , transkrip ) yang sangat populer , Swift menyediakan sejumlah fitur yang membuat struct lebih baik daripada kelas dalam banyak keadaan.

Structs lebih disukai jika mereka relatif kecil dan copiable karena menyalin jauh lebih aman daripada memiliki banyak referensi ke contoh yang sama seperti yang terjadi pada kelas. Ini sangat penting ketika memberikan variabel ke banyak kelas dan / atau dalam lingkungan multithreaded. Jika Anda selalu dapat mengirim salinan variabel Anda ke tempat lain, Anda tidak perlu khawatir tentang tempat lain yang mengubah nilai variabel Anda di bawah Anda.

Dengan Structs, ada jauh lebih sedikit perlu khawatir tentang kebocoran memori atau beberapa utas berlomba untuk mengakses / memodifikasi satu contoh variabel. (Untuk yang lebih berpikiran teknis, pengecualian untuk itu adalah ketika menangkap struct di dalam penutupan karena itu sebenarnya menangkap referensi ke instance kecuali Anda secara eksplisit menandai itu untuk disalin).

Kelas juga bisa membengkak karena kelas hanya bisa mewarisi dari superkelas tunggal. Itu mendorong kami untuk membuat kacamata super besar yang mencakup banyak kemampuan berbeda yang hanya terkait secara longgar. Menggunakan protokol, terutama dengan ekstensi protokol di mana Anda dapat menyediakan implementasi protokol, memungkinkan Anda untuk menghilangkan kebutuhan kelas untuk mencapai perilaku semacam ini.

Pembicaraan menjabarkan skenario ini di mana kelas lebih disukai:

  • Menyalin atau membandingkan contoh tidak masuk akal (misalnya, Window)
  • Seumur hidup Instance terkait dengan efek eksternal (misalnya, TemporaryFile)
  • Contoh hanya "tenggelam" - saluran hanya menulis ke keadaan eksternal (egCGContext)

Ini menyiratkan bahwa struct harus menjadi default dan kelas harus menjadi mundur.

Di sisi lain, dokumentasi Bahasa Pemrograman Swift agak kontradiktif:

Instance struktur selalu dilewati oleh nilai, dan instance kelas selalu dilewati oleh referensi. Ini berarti bahwa mereka cocok untuk berbagai jenis tugas. Saat Anda mempertimbangkan konstruk data dan fungsionalitas yang Anda perlukan untuk suatu proyek, putuskan apakah setiap konstruk data harus didefinisikan sebagai kelas atau sebagai struktur.

Sebagai pedoman umum, pertimbangkan untuk membuat struktur ketika satu atau lebih kondisi ini berlaku:

  • Tujuan utama struktur adalah untuk merangkum beberapa nilai data yang relatif sederhana.
  • Adalah masuk akal untuk mengharapkan bahwa nilai-nilai yang dienkapsulasi akan disalin daripada dirujuk ketika Anda menetapkan atau membagikan turunan dari struktur itu.
  • Setiap properti yang disimpan oleh struktur itu sendiri adalah tipe nilai, yang juga diharapkan akan disalin daripada direferensikan.
  • Struktur tidak perlu mewarisi properti atau perilaku dari tipe lain yang sudah ada.

Contoh kandidat yang baik untuk struktur meliputi:

  • Ukuran bentuk geometris, mungkin merangkum properti lebar dan properti tinggi, keduanya bertipe Double.
  • Cara untuk merujuk ke rentang dalam rangkaian, mungkin merangkum properti awal dan properti panjang, keduanya dari tipe Int.
  • Suatu titik dalam sistem koordinat 3D, mungkin merangkum properti x, y dan z, masing-masing bertipe Double.

Dalam semua kasus lain, tentukan kelas, dan buat instance kelas tersebut untuk dikelola dan diteruskan dengan referensi. Dalam praktiknya, ini berarti bahwa sebagian besar konstruk data khusus harus kelas, bukan struktur.

Di sini dikatakan bahwa kita harus menggunakan kelas secara default dan menggunakan struktur hanya dalam keadaan tertentu. Pada akhirnya, Anda perlu memahami implikasi dunia nyata dari tipe nilai vs tipe referensi dan kemudian Anda dapat membuat keputusan tentang kapan harus menggunakan struct atau kelas. Juga, perlu diingat bahwa konsep-konsep ini selalu berkembang dan dokumentasi Bahasa Pemrograman Swift ditulis sebelum ceramah Pemrograman Berorientasi Protokol diberikan.

drewag
sumber
12
@ ElgsQianChen seluruh inti dari artikel ini adalah bahwa struct harus dipilih secara default dan kelas hanya boleh digunakan jika diperlukan. Struct jauh lebih aman dan bebas bug, terutama di lingkungan multithreaded. Ya, Anda selalu dapat menggunakan kelas sebagai ganti struct, tetapi struct lebih disukai.
drewag
16
@rewag Itu tampaknya kebalikan dari apa yang dikatakannya. Itu mengatakan bahwa kelas harus menjadi default yang Anda gunakan, bukan struktur. In practice, this means that most custom data constructs should be classes, not structures.Bisakah Anda menjelaskan kepada saya bagaimana, setelah membaca itu, Anda mendapatkan bahwa sebagian besar set data harus struktur dan bukan kelas? Mereka memberikan seperangkat aturan khusus ketika sesuatu harus menjadi struct dan cukup banyak mengatakan "semua skenario lain kelas lebih baik."
Matt
42
Baris terakhir harus mengatakan, "Nasihat pribadi saya adalah kebalikan dari dokumentasi:" ... dan kemudian itu jawaban yang bagus!
Dan Rosenstark
5
Buku Swift 2.2 masih mengatakan penggunaan kelas di sebagian besar situasi.
David James
6
Struct over Class jelas mengurangi kerumitannya. Tapi apa implikasinya pada penggunaan memori ketika struct menjadi pilihan default. Ketika sesuatu disalin di mana-mana alih-alih referensi itu harus meningkatkan penggunaan memori oleh aplikasi. Bukan?
MadNik
164

Karena instance struct dialokasikan pada stack, dan instance kelas dialokasikan pada heap, struct kadang-kadang dapat secara drastis lebih cepat.

Namun, Anda harus selalu mengukurnya sendiri dan memutuskan berdasarkan kasus penggunaan unik Anda.

Pertimbangkan contoh berikut, yang menunjukkan 2 strategi pembungkus Inttipe data menggunakan structdan class. Saya menggunakan 10 nilai berulang untuk lebih mencerminkan dunia nyata, di mana Anda memiliki banyak bidang.

class Int10Class {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

struct Int10Struct {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

func + (x: Int10Class, y: Int10Class) -> Int10Class {
    return IntClass(x.value + y.value)
}

func + (x: Int10Struct, y: Int10Struct) -> Int10Struct {
    return IntStruct(x.value + y.value)
}

Kinerja diukur menggunakan

// Measure Int10Class
measure("class (10 fields)") {
    var x = Int10Class(0)
    for _ in 1...10000000 {
        x = x + Int10Class(1)
    }
}

// Measure Int10Struct
measure("struct (10 fields)") {
    var y = Int10Struct(0)
    for _ in 1...10000000 {
        y = y + Int10Struct(1)
    }
}

func measure(name: String, @noescape block: () -> ()) {
    let t0 = CACurrentMediaTime()

    block()

    let dt = CACurrentMediaTime() - t0
    print("\(name) -> \(dt)")
}

Kode dapat ditemukan di https://github.com/knguyen2708/StructVsClassPerformance

UPDATE (27 Mar 2018) :

Mulai dari Swift 4.0, Xcode 9.2, menjalankan Release build di iPhone 6S, iOS 11.2.6, pengaturan Swift Compiler adalah -O -whole-module-optimization:

  • class versi butuh 2,06 detik
  • struct versi butuh 4,17e-08 detik (50.000.000 kali lebih cepat)

(Saya tidak lagi menjalankan berulang rata-rata, karena varians sangat kecil, di bawah 5%)

Catatan : perbedaannya jauh lebih dramatis tanpa optimasi seluruh modul. Saya akan senang jika seseorang dapat menunjukkan apa yang sebenarnya dilakukan oleh bendera.


PEMBARUAN (7 Mei 2016) :

Pada Swift 2.2.1, Xcode 7.3, menjalankan Release build di iPhone 6S, iOS 9.3.1, rata-rata lebih dari 5 run, pengaturan Swift Compiler adalah -O -whole-module-optimization:

  • class versi mengambil 2.159942142s
  • struct versi mengambil 5.83E-08s (37.000.000 kali lebih cepat)

Catatan : ketika seseorang menyebutkan bahwa dalam skenario dunia nyata, kemungkinan akan ada lebih dari 1 bidang dalam sebuah struct, saya telah menambahkan tes untuk struct / kelas dengan 10 bidang alih-alih 1. Anehnya, hasilnya tidak banyak berbeda.


HASIL ASLI (1 Juni 2014):

(Berlari pada struct / kelas dengan 1 bidang, bukan 10)

Pada Swift 1.2, Xcode 6.3.2, menjalankan Release build di iPhone 5S, iOS 8.3, rata-rata lebih dari 5 run

  • class versi mengambil 9,788332333s
  • struct versi mengambil 0,010532942s (900 kali lebih cepat)

HASIL LAMA (dari waktu yang tidak diketahui)

(Berlari pada struct / kelas dengan 1 bidang, bukan 10)

Dengan rilis rilis di MacBook Pro saya:

  • The classVersi mengambil 1,10082 detik
  • The structVersi mengambil 0,02324 detik (50 kali lebih cepat)
Khanh Nguyen
sumber
27
Benar, tetapi tampaknya menyalin sekelompok struct di sekitar akan lebih lambat daripada menyalin referensi ke objek tunggal. Dengan kata lain, lebih cepat menyalin satu penunjuk saja daripada menyalin blok memori yang besar dan sewenang-wenang.
Tylerc230
14
-1 Tes ini bukan contoh yang baik karena hanya ada satu var pada struct. Perhatikan bahwa jika Anda menambahkan beberapa nilai dan satu atau dua objek, versi struct dapat dibandingkan dengan versi kelas. Semakin banyak vars yang Anda tambahkan, semakin lambat versi struct.
joshrl
6
@ joshrl mengerti maksud Anda, tetapi sebuah contoh "baik" atau tidak tergantung pada situasi tertentu. Kode ini diekstraksi dari aplikasi saya sendiri, jadi ini adalah kasus penggunaan yang valid, dan menggunakan struct benar-benar meningkatkan kinerja untuk aplikasi saya. Mungkin saja itu bukan kasus penggunaan umum (well, kasus penggunaan umum adalah, untuk sebagian besar aplikasi, tidak ada yang peduli tentang seberapa cepat data dapat diedarkan, karena kemacetan terjadi di tempat lain, misalnya koneksi jaringan, bagaimanapun, optimasi bukan kritis ketika Anda memiliki perangkat GHz dengan GB atau RAM).
Khanh Nguyen
26
Sejauh yang saya mengerti menyalin dalam cepat dioptimalkan terjadi pada waktu WRITE. Yang berarti bahwa tidak ada salinan memori fisik yang dibuat kecuali salinan baru akan diubah.
Matjan
6
Jawaban ini menunjukkan contoh yang sangat sepele, sampai-sampai tidak realistis dan karenanya salah untuk banyak contoh. Jawaban yang lebih baik adalah "itu tergantung."
iwasrobbed
60

Kesamaan antara struct dan kelas.

Saya membuat inti untuk ini dengan contoh-contoh sederhana. https://github.com/objc-swift/swift-classes-vs-strures

Dan perbedaan

1. Warisan.

struktur tidak dapat mewarisi dengan cepat. jika kamu mau

class Vehicle{
}

class Car : Vehicle{
}

Pergi untuk kelas.

2. Lewati

Struktur cepat melewati nilai dan instance kelas melewati referensi.

Perbedaan Kontekstual

Konstanta dan variabel struktur

Contoh (Digunakan di WWDC 2014)

struct Point{

   var x = 0.0;
   var y = 0.0;

} 

Menentukan struct yang disebut Point.

var point = Point(x:0.0,y:2.0)

Sekarang jika saya mencoba mengubah x. Itu ungkapan yang valid.

point.x = 5

Tetapi jika saya mendefinisikan suatu titik sebagai konstanta.

let point = Point(x:0.0,y:2.0)
point.x = 5 //This will give compile time error.

Dalam hal ini seluruh titik konstan konstan.

Jika saya menggunakan Point kelas sebagai gantinya ini adalah ekspresi yang valid. Karena dalam sebuah kelas konstanta yang tetap adalah referensi ke kelas itu sendiri, bukan variabel instansinya (Kecuali variabel-variabel tersebut didefinisikan sebagai konstanta)

MadNik
sumber
Anda dapat mewarisi struct di Swift gist.github.com/AliSoftware/9e4946c8b6038572d678
thatguy
12
Inti di atas adalah tentang bagaimana kita dapat mencapai rasa warisan untuk struct. Anda akan melihat seperti sintaks. A: B. Ini adalah struct yang disebut A mengimplementasikan protokol yang disebut B. Dokumentasi Apple dengan jelas menyebutkan bahwa struct tidak mendukung warisan murni dan itu tidak.
MadNik
2
Orang yang paragraf terakhir Anda hebat. Saya selalu tahu bahwa Anda dapat mengubah konstanta ... tetapi kemudian dari waktu ke waktu saya melihat di mana Anda tidak bisa jadi saya bingung. Perbedaan ini membuatnya terlihat
Sayang
28

Berikut adalah beberapa alasan lain untuk dipertimbangkan:

  1. struct mendapatkan penginisialisasi otomatis yang tidak harus Anda pertahankan dalam kode sama sekali.

    struct MorphProperty {
       var type : MorphPropertyValueType
       var key : String
       var value : AnyObject
    
       enum MorphPropertyValueType {
           case String, Int, Double
       }
     }
    
     var m = MorphProperty(type: .Int, key: "what", value: "blah")

Untuk mendapatkan ini di kelas, Anda harus menambahkan inisialisasi, dan mempertahankan intializer ...

  1. Jenis koleksi dasar seperti Arraystruct. Semakin banyak Anda menggunakannya dalam kode Anda sendiri, semakin Anda akan terbiasa melewati nilai dibandingkan dengan referensi. Contohnya:

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
  2. Rupanya kekekalan vs ketidakmampuan adalah topik yang besar, tetapi banyak orang pintar berpikir kekekalan - struct dalam hal ini - lebih disukai. Dapat berubah vs objek yang tidak berubah

Dan Rosenstark
sumber
4
Memang benar Anda mendapatkan inisialisasi otomatis. Anda juga mendapatkan penginisialisasi kosong ketika semua properti Opsional. Tetapi, jika Anda memiliki struct dalam Kerangka Kerja Anda harus benar-benar menulis penginisialisasi sendiri jika Anda ingin itu tersedia di luar internallingkup.
Abizern
2
@Abizern dikonfirmasi - stackoverflow.com/a/26224873/8047 - dan pria yang menjengkelkan.
Dan Rosenstark
2
@Abizern ada alasan bagus untuk semuanya di Swift, tetapi setiap kali ada sesuatu yang benar di satu tempat dan tidak di tempat lain, pengembang harus mengetahui lebih banyak hal. Saya kira di sinilah saya seharusnya mengatakan, "senang bekerja dengan bahasa yang begitu menantang!"
Dan Rosenstark
4
Dapatkah saya juga menambahkan bahwa itu bukan kekekalan dari struct yang membuatnya berguna (meskipun itu hal yang sangat baik). Anda dapat bermutasi struct, Tapi Anda harus menandai metode karena mutatingAnda secara eksplisit tentang fungsi apa yang mengubah keadaan mereka. Tetapi sifat mereka sebagai tipe nilai adalah yang penting. Jika Anda mendeklarasikan struct dengan letAnda tidak dapat memanggil fungsi yang bermutasi padanya. Video WWDC 15 tentang pemrograman yang lebih baik melalui tipe nilai adalah sumber yang bagus untuk ini.
Abizern
1
Terima kasih @Abizern, saya tidak pernah benar-benar memahami ini sebelum membaca komentar Anda. Untuk objek, misalkan vs var tidak banyak perbedaan, tetapi untuk struct itu besar. Terima kasih telah menunjukkan ini.
Dan Rosenstark
27

Dengan asumsi bahwa kita tahu Struct adalah tipe nilai dan Kelas adalah tipe referensi .

Jika Anda tidak tahu apa tipe nilai dan tipe referensi, lalu lihat Apa perbedaan antara meneruskan dengan referensi vs meneruskan dengan nilai?

Berdasarkan pos mikeash :

... Mari kita lihat beberapa contoh ekstrem dan nyata terlebih dahulu. Bilangan bulat jelas dapat disalin. Mereka harus menjadi tipe nilai. Soket jaringan tidak dapat disalin dengan bijaksana. Mereka harus menjadi tipe referensi. Poin, seperti dalam pasangan x, y, dapat disalin. Mereka harus menjadi tipe nilai. Pengontrol yang mewakili disk tidak dapat disalin dengan bijaksana. Itu harus menjadi tipe referensi.

Beberapa jenis dapat disalin tetapi mungkin bukan sesuatu yang Anda inginkan terjadi setiap saat. Ini menunjukkan bahwa mereka harus menjadi tipe referensi. Misalnya, tombol pada layar secara konseptual dapat disalin. Salinan tidak akan sama persis dengan aslinya. Klik pada salinan tidak akan mengaktifkan aslinya. Salinan tidak akan menempati lokasi yang sama di layar. Jika Anda melewatkan tombol atau memasukkannya ke variabel baru, Anda mungkin ingin merujuk ke tombol asli, dan Anda hanya ingin membuat salinan ketika diminta secara eksplisit. Itu berarti bahwa jenis tombol Anda harus menjadi jenis referensi.

Pengontrol tampilan dan jendela adalah contoh serupa. Mereka mungkin dapat disalin, mungkin, tetapi hampir tidak pernah apa yang ingin Anda lakukan. Mereka harus menjadi tipe referensi.

Bagaimana dengan tipe model? Anda mungkin memiliki tipe Pengguna yang mewakili pengguna di sistem Anda, atau tipe Kejahatan yang mewakili tindakan yang diambil oleh Pengguna. Ini cukup dapat disalin, jadi mereka mungkin harus tipe nilai. Namun, Anda mungkin ingin pembaruan Kejahatan Pengguna yang dibuat di satu tempat di program Anda agar dapat dilihat oleh bagian lain dari program ini. Ini menunjukkan bahwa Pengguna Anda harus dikelola oleh semacam pengontrol pengguna yang akan menjadi tipe referensi . misalnya

struct User {}
class UserController {
    var users: [User]

    func add(user: User) { ... }
    func remove(userNamed: String) { ... }
    func ...
}

Koleksi adalah kasus yang menarik. Ini termasuk hal-hal seperti array dan kamus, serta string. Apakah mereka dapat disalin? Jelas sekali. Apakah menyalin sesuatu yang Anda inginkan terjadi dengan mudah dan sering? Itu kurang jelas.

Sebagian besar bahasa mengatakan "tidak" untuk ini dan membuat jenis referensi koleksi mereka. Ini benar dalam Objective-C dan Java dan Python dan JavaScript dan hampir setiap bahasa lain yang bisa saya pikirkan. (Satu pengecualian utama adalah C ++ dengan tipe koleksi STL, tetapi C ++ adalah orang gila yang mengoceh di dunia bahasa yang melakukan semuanya dengan aneh.)

Swift berkata "ya," yang berarti bahwa tipe seperti Array dan Kamus dan String adalah struct daripada kelas. Mereka disalin pada tugas, dan meneruskannya sebagai parameter. Ini adalah pilihan yang sepenuhnya masuk akal asalkan salinannya murah, yang berusaha dengan susah payah dicapai oleh Swift. ...

Saya pribadi tidak memberi nama kelas saya seperti itu. Saya biasanya menamai UserManager milik saya alih-alih UserController tetapi idenya sama

Selain itu, jangan gunakan kelas ketika Anda harus menimpa setiap contoh fungsi yaitu mereka tidak memiliki fungsionalitas bersama .

Jadi alih-alih memiliki beberapa subclass dari suatu kelas. Gunakan beberapa struct yang sesuai dengan protokol.


Kasus wajar lainnya untuk struct adalah ketika Anda ingin melakukan delta / diff dari model lama dan baru Anda. Dengan tipe referensi Anda tidak dapat melakukannya di luar kotak. Dengan tipe nilai, mutasi tidak dibagikan.

Madu
sumber
1
Penjelasan persis yang saya cari. Selamat menulis :)
androCoder-BD
Contoh kontroler yang sangat membantu
Tanyakan
1
@AskP Saya mengirim email mike sendiri dan mendapat sepotong kode ekstra :)
Honey
18

Beberapa keuntungan:

  • secara otomatis threadsafe karena tidak dapat dibagikan
  • menggunakan lebih sedikit memori karena tidak ada isa dan refcount (dan sebenarnya stack dialokasikan secara umum)
  • metode selalu dikirimkan secara statis, sehingga dapat diuraikan (meskipun @final dapat melakukan ini untuk kelas)
  • lebih mudah untuk dipikirkan (tidak perlu "menyalin secara defensif" seperti pada NSArray, NSString, dll ...) untuk alasan yang sama seperti keamanan utas
Catfish_Man
sumber
Tidak yakin apakah itu di luar cakupan jawaban ini, tetapi dapatkah Anda menjelaskan (atau menautkan, saya kira) poin "metode selalu dikirim secara statis"?
Dan Rosenstark
2
Tentu. Saya juga bisa melampirkan peringatan untuk itu. Tujuan dari pengiriman dinamis adalah untuk memilih implementasi ketika Anda tidak tahu di muka mana yang akan digunakan. Di Swift, itu bisa karena pewarisan (mungkin ditimpa dalam subkelas), atau karena fungsi menjadi generik (Anda tidak tahu apa parameter generiknya). Structs tidak dapat diwarisi dari dan seluruh-modul-optimasi + spesialisasi generik sebagian besar menghilangkan generik yang tidak diketahui, sehingga metode hanya dapat dipanggil secara langsung daripada harus mencari apa yang harus dipanggil. Obat generik tidak terspesifikasi masih melakukan pengiriman dinamis untuk struct
Catfish_Man
1
Terima kasih, penjelasan yang bagus. Jadi kami mengharapkan lebih banyak kecepatan runtime, atau kurang ambiguitas dari perspektif IDE, atau keduanya?
Dan Rosenstark
1
Kebanyakan yang pertama.
Catfish_Man
Hanya perhatikan bahwa metode tidak dikirim secara statis jika Anda merujuk struct melalui protokol.
Cristik
12

Struktur jauh lebih cepat daripada Kelas. Juga, jika Anda membutuhkan warisan maka Anda harus menggunakan Kelas. Poin paling penting adalah bahwa Kelas adalah tipe referensi sedangkan Struktur adalah tipe nilai. sebagai contoh,

class Flight {
    var id:Int?
    var description:String?
    var destination:String?
    var airlines:String?
    init(){
        id = 100
        description = "first ever flight of Virgin Airlines"
        destination = "london"
        airlines = "Virgin Airlines"
    } 
}

struct Flight2 {
    var id:Int
    var description:String
    var destination:String
    var airlines:String  
}

sekarang mari kita buat instance dari keduanya.

var flightA = Flight()

var flightB = Flight2.init(id: 100, description:"first ever flight of Virgin Airlines", destination:"london" , airlines:"Virgin Airlines" )

sekarang mari kita lewati instance ini ke dua fungsi yang mengubah id, deskripsi, tujuan dll.

func modifyFlight(flight:Flight) -> Void {
    flight.id = 200
    flight.description = "second flight of Virgin Airlines"
    flight.destination = "new york"
    flight.airlines = "Virgin Airlines"
}

juga,

func modifyFlight2(flight2: Flight2) -> Void {
    var passedFlight = flight2
    passedFlight.id = 200
    passedFlight.description = "second flight from virgin airlines" 
}

begitu,

modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)

sekarang jika kita mencetak id dan deskripsi flightA, kita dapatkan

id = 200
description = "second flight of Virgin Airlines"

Di sini, kita dapat melihat id dan deskripsi FlightA diubah karena parameter yang diteruskan ke metode modifikasi sebenarnya menunjuk ke alamat memori objek flightA (tipe referensi).

sekarang jika kita mencetak id dan deskripsi instance FLightB yang kita dapatkan,

id = 100
description = "first ever flight of Virgin Airlines"

Di sini kita dapat melihat bahwa instance FlightB tidak berubah karena dalam metode modFlight2, instance sebenarnya dari Flight2 melewati daripada referensi (tipe nilai).

Manoj Karki
sumber
2
Anda tidak pernah membuat instance dari FLightB
David Seek
1
lalu mengapa Anda berbicara tentang FlightB bro? Here we can see that the FlightB instance is not changed
David Seek
@ManojKarki, jawaban yang bagus. Hanya ingin menunjukkan bahwa Anda menyatakan flightA dua kali ketika saya pikir Anda ingin mendeklarasikan FlightA, kemudian FlightB.
ScottyBlades
11

Structssedang value typedan Classessedangreference type

  • Jenis nilai lebih cepat daripada tipe Referensi
  • Instans tipe nilai aman di lingkungan multi-utas karena beberapa utas dapat mengubah instance tanpa harus khawatir tentang kondisi balapan atau kebuntuan
  • Jenis nilai tidak memiliki referensi seperti jenis referensi; oleh karena itu tidak ada kebocoran memori.

Gunakan valuetipe saat:

  • Anda ingin salinan memiliki status independen, data akan digunakan dalam kode di beberapa utas

Gunakan referencetipe saat:

  • Anda ingin membuat status bersama yang dapat diubah.

Informasi lebih lanjut dapat ditemukan di dokumentasi Apple

https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html


informasi tambahan

Jenis nilai cepat disimpan di tumpukan. Dalam suatu proses, setiap utas memiliki ruang tumpukannya sendiri, jadi tidak ada utas lain yang dapat mengakses tipe nilai Anda secara langsung. Karenanya tidak ada kondisi balapan, kunci, deadlock atau kompleksitas sinkronisasi utas terkait.

Tipe nilai tidak memerlukan alokasi memori dinamis atau penghitungan referensi, yang keduanya merupakan operasi yang mahal. Pada saat yang sama metode pada tipe nilai dikirim secara statis. Ini menciptakan keuntungan besar dalam mendukung tipe nilai dalam hal kinerja.

Sebagai pengingat di sini adalah daftar Swift

Jenis nilai:

  • Struct
  • Enum
  • Tuple
  • Primitif (Int, Double, Bool dll.)
  • Koleksi (Array, String, Kamus, Set)

Jenis referensi:

  • Kelas
  • Apa pun yang datang dari NSObject
  • Fungsi
  • Penutupan
casillas
sumber
5

Menjawab pertanyaan dari sudut pandang tipe nilai vs tipe referensi, dari posting blog Apple ini akan tampak sangat sederhana:

Gunakan tipe nilai [mis. Struct, enum] ketika:

  • Membandingkan data instan dengan == masuk akal
  • Anda ingin salinan memiliki status independen
  • Data akan digunakan dalam kode di beberapa utas

Gunakan tipe referensi [misalnya kelas] ketika:

  • Membandingkan identitas instan dengan === masuk akal
  • Anda ingin membuat status bersama yang dapat diubah

Seperti yang disebutkan dalam artikel itu, kelas tanpa properti yang dapat ditulisi akan berperilaku identik dengan struct, dengan (saya akan menambahkan) satu peringatan: struct adalah yang terbaik untuk model aman-thread - persyaratan yang semakin dekat dalam arsitektur aplikasi modern.

David James
sumber
3

Dengan kelas Anda mendapatkan warisan dan diteruskan dengan referensi, struct tidak memiliki warisan dan diteruskan oleh nilai.

Ada sesi WWDC hebat di Swift, pertanyaan khusus ini dijawab dengan sangat terperinci di salah satunya. Pastikan Anda menontonnya, karena kecepatan Anda akan jauh lebih cepat daripada panduan Bahasa atau iBook.

Joride
sumber
Bisakah Anda memberikan beberapa tautan dari apa yang Anda sebutkan? Karena di WWDC ada beberapa yang bisa dipilih, saya ingin menonton yang berbicara tentang topik khusus ini
MMachinegun
Bagi saya ini adalah awal yang baik di sini: github.com/raywenderlich/…
MMachinegun
2
Dia mungkin berbicara tentang sesi hebat ini: Pemrograman Berorientasi Protokol di Swift. (Tautan: video , transkrip )
zekel
2

Saya tidak akan mengatakan bahwa struct menawarkan fungsionalitas yang lebih sedikit.

Tentu, diri tidak dapat diubah kecuali dalam fungsi yang bermutasi, tetapi hanya itu saja.

Warisan bekerja dengan baik selama Anda tetap pada gagasan lama yang baik bahwa setiap kelas harus abstrak atau final.

Menerapkan kelas abstrak sebagai protokol dan kelas akhir sebagai struct.

Yang menyenangkan tentang struct adalah bahwa Anda dapat membuat bidang Anda bisa berubah tanpa membuat keadaan yang bisa dibagikan bersama karena copy on write menangani hal itu :)

Itu sebabnya properti / bidang dalam contoh berikut semuanya bisa berubah, yang tidak akan saya lakukan di Java atau C # atau kelas swift .

Contoh struktur pewarisan dengan sedikit penggunaan kotor dan langsung di bagian bawah dalam fungsi bernama "contoh":

protocol EventVisitor
{
    func visit(event: TimeEvent)
    func visit(event: StatusEvent)
}

protocol Event
{
    var ts: Int64 { get set }

    func accept(visitor: EventVisitor)
}

struct TimeEvent : Event
{
    var ts: Int64
    var time: Int64

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }
}

protocol StatusEventVisitor
{
    func visit(event: StatusLostStatusEvent)
    func visit(event: StatusChangedStatusEvent)
}

protocol StatusEvent : Event
{
    var deviceId: Int64 { get set }

    func accept(visitor: StatusEventVisitor)
}

struct StatusLostStatusEvent : StatusEvent
{
    var ts: Int64
    var deviceId: Int64
    var reason: String

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }

    func accept(visitor: StatusEventVisitor)
    {
        visitor.visit(self)
    }
}

struct StatusChangedStatusEvent : StatusEvent
{
    var ts: Int64
    var deviceId: Int64
    var newStatus: UInt32
    var oldStatus: UInt32

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }

    func accept(visitor: StatusEventVisitor)
    {
        visitor.visit(self)
    }
}

func readEvent(fd: Int) -> Event
{
    return TimeEvent(ts: 123, time: 56789)
}

func example()
{
    class Visitor : EventVisitor
    {
        var status: UInt32 = 3;

        func visit(event: TimeEvent)
        {
            print("A time event: \(event)")
        }

        func visit(event: StatusEvent)
        {
            print("A status event: \(event)")

            if let change = event as? StatusChangedStatusEvent
            {
                status = change.newStatus
            }
        }
    }

    let visitor = Visitor()

    readEvent(1).accept(visitor)

    print("status: \(visitor.status)")
}
petani kecil
sumber
2

Di Swift, pola pemrograman baru telah diperkenalkan dikenal sebagai Protokol Berorientasi Pemrograman.

Pola Penciptaan:

Dalam cepat, Struct adalah tipe nilai yang dikloning secara otomatis. Oleh karena itu kita mendapatkan perilaku yang diperlukan untuk menerapkan pola prototipe secara gratis.

Sedangkan kelas adalah tipe referensi, yang tidak secara otomatis dikloning selama penugasan. Untuk mengimplementasikan pola prototipe, kelas harus mengadopsi NSCopyingprotokol.


Salinan duplikat hanya referensi, yang menunjuk ke objek-objek sedangkan salinan duplikat jauh referensi objek.


Menerapkan salinan dalam untuk setiap jenis referensi telah menjadi tugas yang membosankan. Jika kelas menyertakan tipe referensi lebih lanjut, kita harus menerapkan pola prototipe untuk masing-masing properti referensi. Dan kemudian kita harus benar-benar menyalin seluruh grafik objek dengan mengimplementasikan NSCopyingprotokol.

class Contact{
  var firstName:String
  var lastName:String
  var workAddress:Address // Reference type
}

class Address{
   var street:String
   ...
} 

Dengan menggunakan struct dan enum , kami membuat kode kami lebih sederhana karena kami tidak harus menerapkan logika salin.

Balasubramanian
sumber
1

Banyak Cocoa API membutuhkan subkelas NSObject, yang memaksa Anda menggunakan kelas. Tetapi selain itu, Anda dapat menggunakan case-case berikut dari blog Swift Apple untuk memutuskan apakah akan menggunakan tipe nilai struct / enum atau tipe referensi kelas.

https://developer.apple.com/swift/blog/?id=10

akshay
sumber
0

Satu hal yang tidak mendapat perhatian dalam jawaban ini adalah bahwa variabel yang memegang kelas vs sebuah struct bisa letsementara masih memungkinkan perubahan pada properti objek, sementara Anda tidak bisa melakukan ini dengan sebuah struct.

Ini berguna jika Anda tidak ingin variabel menunjuk ke objek lain, tetapi masih perlu memodifikasi objek, yaitu dalam kasus memiliki banyak variabel instan yang ingin Anda perbarui satu demi satu. Jika itu adalah sebuah struct, Anda harus mengizinkan variabel untuk direset ke objek lain sama sekali menggunakan varuntuk melakukan ini, karena tipe nilai konstan di Swift memungkinkan nol mutasi, sedangkan tipe referensi (kelas) tidak berperilaku seperti ini.

pembuat johnbakers
sumber
0

Karena struct adalah tipe nilai dan Anda dapat membuat memori dengan sangat mudah yang menyimpan ke dalam stack. Bangunan dapat dengan mudah diakses dan setelah lingkup pekerjaan itu dengan mudah deallocated dari memori stack melalui pop dari atas stack. Di sisi lain kelas adalah tipe referensi yang menyimpan di heap dan perubahan yang dibuat dalam satu objek kelas akan berdampak pada objek lain karena mereka erat digabungkan dan tipe referensi. Semua anggota struktur adalah publik sedangkan semua anggota kelas adalah pribadi .

Kerugian dari struct adalah tidak dapat diwarisi.

Tapash Mollick
sumber
-7
  • Struktur dan kelas adalah tipe data yang ditentang pengguna

  • Secara default, struktur adalah publik sedangkan kelas adalah pribadi

  • Kelas mengimplementasikan prinsip enkapsulasi

  • Objek kelas dibuat pada memori tumpukan

  • Kelas digunakan untuk kegunaan kembali sedangkan struktur digunakan untuk mengelompokkan data dalam struktur yang sama

  • Struktur data anggota tidak dapat diinisialisasi secara langsung tetapi mereka dapat ditugaskan oleh luar struktur

  • Anggota data kelas dapat diinisialisasi secara langsung oleh konstruktor parameter kurang dan ditugaskan oleh konstruktor parameterized

Ridaa Zahra
sumber
2
Jawaban terburuk yang pernah ada!
J. Doe
salin tempel jawaban
jawadAli