Neden Sınıf Üzerinden Yapı?


476

Java ile gelen Swift ile oynamak, neden bir Sınıf yerine bir Yapı seçmek istersiniz? Daha az işlevsellik sunan bir Struct ile aynı şey gibi görünüyorlar. Neden seçmelisin?


11
Yapılar her zaman kodunuzda iletildiklerinde kopyalanır ve referans sayımı kullanmazlar. Kaynak: developer.apple.com/library/prerelease/ios/documentation/swift/…
holex

4
Yapıların, mantığı değil verileri tutmak için daha uygun olduğunu söyleyebilirim. Java terimleriyle konuşmak için yapıları "Değer Nesneleri" olarak düşünün.
Vincent Guerci

6
Tüm bu konuşmada şaşırdım, üzerine yazma kopyası olarak da tembel kopyadan doğrudan söz edilmiyor . Yapısal kopyalama performansı ile ilgili endişeler çoğunlukla bu tasarım nedeniyle tartışmalıdır.
David James

3
Sınıf üzerinden bir yapı seçmek bir fikir meselesi değildir. Birini veya diğerini seçmenin belirli nedenleri vardır.
David James

Array neden threadSafe değil görmek için tavsiye ederim . İlişkilidir çünkü Diziler ve Yapılar her ikisi de değer türleridir. Buradaki tüm cevaplar, yapılar / diziler / değer türleriyle hiçbir zaman bir iş parçacığı Güvenliği sorunu olmayacağını belirtiyor, ancak yapacağınız bir köşe durumu var.
Tatlım

Yanıtlar:


548

Swift'teki çok popüler WWDC 2015 talk Protokol Odaklı Programlamaya ( video , konuşma metni ) göre Swift, birçok durumda yapıları sınıflardan daha iyi yapan bir dizi özellik sunar.

Yapılar, nispeten küçük ve kopyalanabilir olmaları durumunda tercih edilir, çünkü kopyalama, sınıflarda olduğu gibi aynı örneğe birden çok referansa sahip olmaktan çok daha güvenlidir. Bu, bir değişkenin birçok sınıfa ve / veya çok iş parçacıklı bir ortamda aktarılması sırasında özellikle önemlidir. Değişkeninizin bir kopyasını her zaman başka yerlere gönderebiliyorsanız, değişkeninizin değerini altınızdaki değeri değiştirerek asla başka bir yer hakkında endişelenmenize gerek yoktur.

Structs ile, bir değişkenin tek bir örneğine erişmek / değiştirmek için bellek sızıntıları veya yarışan birden çok iş parçacığı hakkında endişelenmenize gerek yoktur. (Teknik açıdan daha fikirli olmak için, istisna, bir yapının bir kapatma içinde yakalanmasıdır, çünkü daha sonra kopyalanacak şekilde açıkça işaretlenmedikçe, örneğe bir referans yakalar).

Sınıflar da şişirilebilir çünkü sınıf yalnızca tek bir üst sınıftan miras alabilir. Bu bizi, sadece gevşek bir şekilde ilişkili olan birçok farklı yeteneği kapsayan büyük üst sınıflar yaratmaya teşvik ediyor. Protokolleri, özellikle protokollere uygulamalar sağlayabileceğiniz protokol uzantılarıyla kullanmak, bu tür davranışları elde etmek için sınıflara olan ihtiyacı ortadan kaldırmanıza olanak tanır.

Konuşma, sınıfların tercih edildiği şu senaryoları ortaya koyar:

  • Örnekleri kopyalamak veya karşılaştırmak anlamlı değildir (örn. Pencere)
  • Eşgörünüm ömrü dış etkilere bağlıdır (örn. TemporaryFile)
  • Örnekler yalnızca "lavabolar" dır - harici duruma yalnızca yazma kanalları (egCGContext)

Yapıların varsayılan olması ve sınıfların yedek olması gerektiği anlamına gelir.

Öte yandan, Swift Programlama Dili belgeleri biraz çelişkilidir:

Yapı örnekleri her zaman değere, sınıf örnekleri de her zaman başvuruya göre geçirilir. Bu, farklı görevlere uygun oldukları anlamına gelir. Bir proje için gereken veri yapılarını ve işlevselliğini göz önünde bulundururken, her veri yapısının bir sınıf mı yoksa bir yapı olarak mı tanımlanacağına karar verin.

Genel bir kılavuz olarak, bu koşullardan biri veya daha fazlası geçerli olduğunda bir yapı oluşturmayı düşünün:

  • Yapının birincil amacı, nispeten basit birkaç veri değerini kapsamaktır.
  • Bu yapının bir örneğini atadığınızda veya aktardığınızda, kapsüllenmiş değerlerin başvurulmak yerine kopyalanmasını beklemek mantıklıdır.
  • Yapı tarafından saklanan herhangi bir özellik, kendileri referanslı olmak yerine kopyalanması beklenen değer türleridir.
  • Yapının özellikleri veya davranışı var olan başka bir türden devralması gerekmez.

Yapılar için iyi adaylara örnekler:

  • Her ikisi de Double tipinde bir width özelliği ve height özelliği içeren geometrik bir şeklin boyutu.
  • Bir seri içindeki aralıklara atıfta bulunmanın bir yolu, belki de her ikisi de Int türünde bir başlangıç ​​özelliği ve bir uzunluk özelliği kapsülleme.
  • 3D koordinat sistemindeki bir nokta, belki de her biri Double tipinde olan x, y ve z özelliklerini kapsayan.

Diğer tüm durumlarda, bir sınıf tanımlayın ve referans ile yönetilecek ve iletilecek o sınıfın örneklerini oluşturun. Uygulamada bu, çoğu özel veri yapısının yapı değil sınıf olması gerektiği anlamına gelir.

Burada varsayılan olarak sınıfları kullanmamız ve yapıları yalnızca belirli durumlarda kullanmamız gerektiği iddia edilmektedir. Sonuçta, değer türlerinin referans türlere karşı gerçek dünyadaki anlamını anlamanız gerekir ve daha sonra yapıların veya sınıfların ne zaman kullanılacağı hakkında bilinçli bir karar verebilirsiniz. Ayrıca, bu kavramların her zaman gelişmekte olduğunu ve Protokol Odaklı Programlama konuşması yapılmadan önce Swift Programlama Dili belgelerinin yazıldığını unutmayın.


12
Bu yazının asıl amacı, yapının varsayılan olarak seçilmesi ve sınıfın yalnızca gerektiğinde kullanılması gerektiğidir. Özellikle çok iş parçacıklı bir ortamda yapılar çok daha güvenli ve hatasızdır. Evet, bir yapı yerine her zaman bir sınıf kullanabilirsiniz, ancak yapılar tercih edilir.
drewag

16
@drewag Bu söylediklerinin tam tersi gibi görünüyor. Bir yapının değil, bir sınıfın kullandığınız varsayılan sınıf olması gerektiğini söylüyordu. Bunu In practice, this means that most custom data constructs should be classes, not structures.okuduktan sonra, çoğu veri kümesinin sınıflar değil, yapılar olması gerektiğini nasıl anlarsınız? Bir şeyin bir yapı olması gerektiğinde ve "sınıfın daha iyi olduğu diğer tüm senaryolar" derken belirli bir kural seti verdiler.
Matt

42
Son satırda "Kişisel tavsiyem belgelerin tam tersi:" yazmalı ve harika bir cevap!
Dan Rosenstark

5
Swift 2.2 kitabı hala çoğu durumda sınıfları kullanıyor diyor.
David James

6
Sınıf üzerindeki yapı kesinlikle karmaşıklığı azaltır. Ancak, yapılar varsayılan seçim haline geldiğinde bellek kullanımı üzerindeki etkisi nedir? İşler referans yerine her yere kopyalandığında, uygulamanın bellek kullanımını artırması gerekir. Olmamalı mı?
MadNik

164

Yapı örnekleri yığın üzerinde ve sınıf örnekleri yığın üzerinde tahsis edildiğinden, yapılar bazen çok daha hızlı olabilir.

Ancak, her zaman kendiniz ölçmeli ve benzersiz kullanım durumunuza göre karar vermelisiniz.

Ve Intkullanarak veri türünü kaydırmanın 2 stratejisini gösteren aşağıdaki örneği ele alalım . Birden fazla alanın olduğu gerçek dünyayı daha iyi yansıtmak için 10 tekrarlanan değer kullanıyorum.structclass

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

Performans,

// 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)")
}

Kod https://github.com/knguyen2708/StructVsClassPerformance adresinde bulunabilir.

GÜNCELLEME (27 Mar 2018) :

Swift 4.0, Xcode 9.2'den itibaren, iPhone 6S, iOS 11.2.6'da Sürüm derlemesini çalıştıran Swift Derleyici ayarı -O -whole-module-optimization:

  • class sürüm 2.06 saniye sürdü
  • struct sürüm 4.17e-08 saniye sürdü (50.000.000 kat daha hızlı)

(Varyanslar% 5'in altında çok küçük olduğu için artık birden fazla çalışmayı ortalama yapmıyorum)

Not : bütün modül optimizasyonu olmadan fark çok daha az dramatiktir. Birisi bayrağın gerçekte ne yaptığını gösterebilirse sevinirim.


GÜNCELLEME (7 Mayıs 2016) :

Swift 2.2.1, Xcode 7.3, iPhone 6S, iOS 9.3.1'de Sürüm derlemesini çalıştıran ve ortalama 5 çalışmanın ortalaması olan Swift Compiler ayarı -O -whole-module-optimization:

  • class sürüm 2.159942142s aldı
  • struct sürüm 5.83E-08 aldı (37.000.000 kat daha hızlı)

Not : Birisinin gerçek dünya senaryolarında, bir yapıda muhtemelen 1'den fazla alan olacağından bahsettiği gibi, 1 yerine 10 alanlı yapılar / sınıflar için testler ekledim. Şaşırtıcı bir şekilde, sonuçlar çok fazla değişmiyor.


ORİJİNAL SONUÇLAR (1 Haziran 2014):

(10 değil, 1 alanlı yapı / sınıfta koş)

Swift 1.2, Xcode 6.3.2'den itibaren, iPhone 5S, iOS 8.3'te Release derlemesini çalıştırıyor, ortalama 5'ten fazla çalışıyor

  • class sürüm 9.788332333s aldı
  • struct sürüm 0.010532942s aldı (900 kat daha hızlı)

ESKİ SONUÇLAR (bilinmeyen zamandan)

(10 değil, 1 alanlı yapı / sınıfta koş)

MacBook Pro'mda sürüm oluşturma ile:

  • classSürüm 1,10082 saniye sürdü
  • structVersiyon (50 kat daha hızlı) 0,02324 saniye sürdü

27
Doğru, ancak bir grup yapıyı kopyalamanın, referansı tek bir nesneye kopyalamaktan daha yavaş olduğu görülüyor. Başka bir deyişle, tek bir işaretçiyi kopyalamak, keyfi olarak büyük bir bellek bloğunu kopyalamaktan daha hızlıdır.
Tylerc230

14
-1 Bu test iyi bir örnek değildir, çünkü yapıda sadece tek bir var. Birkaç değer ve bir veya iki nesne eklerseniz, yapı sürümünün sınıf sürümüyle karşılaştırılabileceğini unutmayın. Ne kadar çok değişken eklerseniz, yapı sürümü yavaşlar.
joshrl

6
@joshrl senin fikrini aldı, ama bir örnek "iyi" veya belirli bir duruma bağlı değildir. Bu kod kendi uygulamamdan çıkarıldı, bu yüzden geçerli bir kullanım durumu ve yapıları kullanmak uygulamamın performansını büyük ölçüde artırdı. Muhtemelen yaygın bir kullanım durumu değildir (yaygın kullanım durumu, çoğu uygulama için hiç kimse verilerin ne kadar hızlı aktarılabileceğini umursamaz, çünkü darboğaz başka bir yerde gerçekleşir, örneğin ağ bağlantıları, yine de optimizasyon, GB veya RAM içeren GHz cihazlarınız olduğunda kritik).
Khanh Nguyen

26
Anladığım kadarıyla, hızlı bir şekilde kopyalamanın WRITE zamanında gerçekleşmesi için optimize edildi. Bu, yeni kopya değiştirilmek üzereyken fiziksel bellek kopyası yapılmadığı anlamına gelir.
Matjan

6
Bu cevap, gerçekçi olmayan ve bu nedenle birçok örnek için yanlış olma noktasına son derece önemsiz bir örnek gösteriyor. Daha iyi bir cevap "duruma göre değişir" olacaktır.
19'da iwasrobbed

60

Yapılar ve sınıflar arasındaki benzerlikler.

Bunun için basit örneklerle özgeçmiş oluşturdum. https://github.com/objc-swift/swift-classes-vs-structures

Ve farklılıklar

1. Kalıtım.

yapılar hızla miras alamaz. Eğer istersen

class Vehicle{
}

class Car : Vehicle{
}

Derse git.

2. Geçmek

Hızlı yapılar değere göre geçer ve sınıf örnekleri referans ile geçer.

Bağlamsal Farklılıklar

Yapı sabiti ve değişkenler

Örnek (WWDC 2014'te kullanılmıştır)

struct Point{

   var x = 0.0;
   var y = 0.0;

} 

Nokta adlı bir yapı tanımlar.

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

Şimdi x'i değiştirmeye çalışırsam. Geçerli bir ifade.

point.x = 5

Ama bir noktayı sabit olarak tanımlasam.

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

Bu durumda tüm nokta değişmez sabittir.

Bunun yerine bir sınıf Point kullandıysam bu geçerli bir ifadedir. Çünkü bir sınıfta değişmez sabit, sınıf değişkenine referans olarak sınıfın kendisine referans değildir (Sabitler olarak tanımlanan değişkenler olmadığı sürece)


Sen Swift içinde yapılar devralabilir gist.github.com/AliSoftware/9e4946c8b6038572d678
thatguy

12
Yukarıdaki öz, yapı için kalıtım aromalarını nasıl elde edebileceğimizle ilgilidir. Gibi sözdizimini göreceksiniz. C: B. A adı verilen bir yapıdır. B adı verilen bir uygulama protokolüdür. Apple belgeleri, yapının saf mirası desteklemediğini ve desteklemediğini açıkça belirtmektedir.
MadNik

2
sizin son paragrafınız harikaydı. Her zaman sabitleri değiştirebileceğinizi biliyordum ... ama zaman zaman nerede yapamayacağınızı gördüm ve şaşkına döndüm. Bu ayrım görünür kıldı
Honey

28

Dikkate almanız gereken başka nedenler:

  1. yapılar, kodda hiç bakım yapmanız gerekmeyen otomatik bir başlatıcı alır.

    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")

Bunu bir sınıfta almak için başlatıcıyı eklemeniz ve başlatıcıyı korumanız gerekir ...

  1. Gibi temel koleksiyon türleri Arrayyapılardır. Bunları kendi kodunuzda ne kadar çok kullanırsanız, referansın aksine değere göre o kadar çok alışacaksınız. Örneğin:

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
  2. Görünüşe göre değişmezlik ve değişebilirlik muazzam bir konudur, ancak birçok akıllı insan değişmezliğin - bu durumda yapılar - tercih edilir olduğunu düşünmektedir. Değişken ve değişmeyen nesneler


4
Otomatik başlatıcılar aldığınız doğrudur. Ayrıca, tüm özellikler İsteğe bağlı olduğunda boş bir başlatıcı alırsınız. Ancak, bir Çerçevede bir yapınız varsa, internalkapsam dışında kullanılabilir olmasını istiyorsanız, başlatıcıyı kendiniz yazmanız gerekir .
Abizern

2
@Abizern doğruladı - stackoverflow.com/a/26224873/8047 - ve can sıkıcı bir adam.
Dan Rosenstark

2
@Abizern, Swift'teki her şeyin harika nedenleri var, ancak her şey bir yerde ve başka bir yerde doğru olmadığında, geliştirici daha fazla şey bilmek zorundadır. Sanırım burada, "böylesine zorlu bir dilde çalışmak heyecan verici!"
Dan Rosenstark

4
Yapıları değişmez kılmalarının onları yararlı kılan olmadığını da ekleyebilir miyim (çok iyi bir şey olmasına rağmen). Yapıları mutasyona geçirebilirsiniz, ancak yöntemleri mutatinghangi işlevlerin durumlarını değiştirdiği konusunda açık olmanız için işaretlemelisiniz . Ancak değer türleri olarak doğaları önemlidir. Birlikte bir yapı letbildirirseniz, üzerinde herhangi bir mutasyon işlevini çağıramazsınız. WWDC 15 değer türleri ile daha iyi programlama videosu bu konuda mükemmel bir kaynaktır.
Abizern

1
@Abizern, yorumunuzu okumadan önce bunu gerçekten anlamadım. Nesneler için, let vs. var çok fazla bir fark değil, ancak yapılar için çok büyük. Bunu işaret ettiğiniz için teşekkürler.
Dan Rosenstark

27

Struct'ın bir değer türü ve Class'ın bir referans türü olduğunu bildiğimizi varsayarsak .

Bir değer türünün ve bir referans türünün ne olduğunu bilmiyorsanız, bkz . Referanstan geçerken değere göre geçiş arasındaki fark nedir?

Dayanarak mikeash gönderide :

... Önce bazı aşırı, açık örneklere bakalım. Tamsayılar açık bir şekilde kopyalanabilir. Değer türleri olmalıdır. Ağ soketleri hassas bir şekilde kopyalanamaz. Referans türleri olmalıdır. X, y çiftleri gibi noktalar kopyalanabilir. Değer türleri olmalıdır. Diski temsil eden bir denetleyici makul bir şekilde kopyalanamaz. Bu bir referans türü olmalıdır.

Bazı türler kopyalanabilir, ancak her zaman olmasını istediğiniz bir şey olmayabilir. Bu onların referans türleri olması gerektiğini gösterir. Örneğin, ekrandaki bir düğme kavramsal olarak kopyalanabilir. Kopya orijinal ile tamamen aynı olmayacaktır. Kopyanın üzerine tıklanması orijinali etkinleştirmez. Kopya ekranda aynı yeri işgal etmez. Düğmeyi geçerseniz veya yeni bir değişkene koyarsanız, muhtemelen orijinal düğmeye başvurmak istersiniz ve yalnızca açıkça istendiğinde bir kopya oluşturmak istersiniz. Bu, düğme türünüzün bir referans türü olması gerektiği anlamına gelir.

Görünüm ve pencere denetleyicileri de buna benzer bir örnektir. Muhtemelen kopyalanabilir olabilirler, ama neredeyse hiçbir zaman yapmak istediğiniz şey bu değildi. Referans türleri olmalıdır.

Model türleri ne olacak? Sisteminizde bir kullanıcıyı temsil eden bir Kullanıcı türünüz veya bir Kullanıcı tarafından gerçekleştirilen bir eylemi temsil eden bir Suç türünüz olabilir. Bunlar oldukça uyumludur, bu yüzden muhtemelen değer türleri olmalıdır. Ancak, muhtemelen programınızın tek bir yerinde yapılan Kullanıcı Suçuna yönelik güncellemelerin programın diğer bölümleri tarafından görülmesini istersiniz. Bu, Kullanıcılarınızın bir referans türü olabilecek bir tür kullanıcı denetleyicisi tarafından yönetilmesi gerektiğini gösterir . Örneğin

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

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

Koleksiyonlar ilginç bir durum. Bunlar diziler ve diziler gibi şeyleri içerir. Kopyalanabilir mi? Açıkçası. Kopyalamak kolay ve sık sık olmasını istediğiniz bir şey mi? Bu daha az açık.

Çoğu dil buna "hayır" der ve koleksiyonlarını referans türlerine dönüştürür. Bu Objective-C ve Java ve Python ve JavaScript ve aklıma gelen hemen hemen her dilde doğrudur. (Büyük bir istisna, STL toplama türlerine sahip C ++, ancak C ++ her şeyi garip bir şekilde yapan dil dünyasının çılgın delisidir.)

Swift, "evet" dedi, yani Dizi ve Sözlük ve Dize gibi türler sınıflardan ziyade yapılardır. Ödev üzerine kopyalanır ve parametre olarak iletilirler. Bu, kopya ucuz olduğu sürece tamamen mantıklı bir seçimdir ve Swift'in başarması çok zordur. ...

Ben şahsen derslerimi böyle adlandırmam. Ben genellikle UserController yerine benim UserManager adını ama fikir aynı

Ayrıca, bir işlevin her bir örneğini, yani paylaşılan bir işlevselliğe sahip olmadıklarını geçersiz kılmak zorunda kaldığınızda sınıfı kullanmayın .

Yani bir sınıfın birkaç alt sınıfına sahip olmak yerine. Bir protokole uygun çeşitli yapılar kullanın.


Yapılar için bir başka makul durum, eski ve yeni modelinizin bir delta / farkını yapmak istediğinizde. Referans türleri ile bunu kutudan çıkartamazsınız. Değer türleri ile mutasyonlar paylaşılmaz.


1
Tam olarak aradığım açıklama. Güzel yazma :)
androCoder-BD

Çok yararlı denetleyici örneği
P'ye

1
@AskP mike'ın kendisine e-posta gönderdim ve bu ekstra kod parçasını aldım :)
Honey

18

Bazı avantajlar:

  • paylaşılamadığı için otomatik olarak güvenli
  • isa ve refcount olmadığından daha az bellek kullanır (ve aslında yığın genel olarak ayrılır)
  • yöntemler her zaman statik olarak gönderilir, bu nedenle satır içi olabilir (@final sınıflar için bunu yapabilir)
  • iplik güvenliği ile aynı nedenden ötürü daha kolay (NSArray, NSString vb.

Bu yanıtın kapsamı dışında olup olmadığından emin değilim, ancak "yöntemler her zaman statik olarak gönderiliyor" noktasını açıklayabilir (veya bağlayabilirim)?
Dan Rosenstark

2
Elbette. Ayrıca bir uyarı da ekleyebilirim. Dinamik dağıtımın amacı hangisini kullanacağınızı bilmediğinizde bir uygulama seçmektir. Swift'te bu, kalıtımdan (bir alt sınıfta geçersiz kılınabilir) veya işlevin genel olması nedeniyle (genel parametrenin ne olacağını bilmiyor olabilirsiniz) olabilir. Yapılar devralınamaz ve tüm modül optimizasyonu + jenerik uzmanlık çoğunlukla bilinmeyen jenerikleri ortadan kaldırır, bu nedenle yöntemler neyi arayacağınıza bakmak yerine doğrudan çağrılabilir. Uzmanlaşmamış jenerikler yine de yapılar için dinamik sevkiyat
yapıyorlar

1
Teşekkürler, harika bir açıklama. Yani IDE perspektifinden daha fazla çalışma süresi veya daha az belirsizlik mi bekliyoruz?
Dan Rosenstark

1
Çoğunlukla eski.
Catfish_Man

Yapıyı bir protokol aracılığıyla yönlendirirseniz yöntemlerin statik olarak gönderilmediğini unutmayın.
Cristik

12

Yapı Sınıftan çok daha hızlıdır. Ayrıca, mirasa ihtiyacınız varsa Class'ı kullanmalısınız. En önemli nokta Sınıf'ın referans tipi, Yapı ise değer tipidir. Örneğin,

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  
}

Şimdi her ikisinin de örneğini oluşturalım.

var flightA = Flight()

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

Şimdi bu örneği id, açıklama, hedef vb. değiştiren iki fonksiyona geçirelim.

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

Ayrıca,

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

yani,

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

Şimdi flightA'nın kimliğini ve açıklamasını yazdırırsak,

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

Burada, changeA yöntemine iletilen parametre aslında flightA nesnesinin (başvuru türü) bellek adresini gösterdiğinden FlightA'nın kimliğinin ve açıklamasının değiştiğini görebiliriz.

şimdi elde ettiğimiz FLightB örneğinin kimliğini ve açıklamasını yazdırırsak,

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

Burada, FlightF örneğinin değiştirilmediğini görebiliriz, çünkü changeFlight2 yönteminde gerçek Flight2 örneği başvuru (değer türü) yerine geçer.


2
hiç FLightB örneği oluşturmadınız
David Seek

1
o zaman neden FlightB abi hakkında konuşuyorsun? Here we can see that the FlightB instance is not changed
David Seek

@ManojKarki, harika cevap. Sadece FlightA, sonra FlightB ilan etmek istediğinizi düşündüğümde iki kez flightA ilan ettiğinizi belirtmek istedim.
ScottyBlades

11

Structsvar value typeve Classesvarreference type

  • Değer türleri Referans türlerinden daha hızlıdır
  • Birden çok iş parçacığı, yarış koşulları veya kilitlenmeler hakkında endişelenmeden örneği değiştirebileceğinden, değer türü örnekleri çok iş parçacıklı bir ortamda güvenlidir
  • Değer türünün, referans türünün aksine referansı yoktur; bu nedenle bellek sızıntısı yoktur.

Aşağıdaki durumlarda bir valuetür kullanın :

  • Kopyaların bağımsız durumuna sahip olmasını istiyorsunuz, veriler kodda birden çok iş parçacığında kullanılacak

Aşağıdaki durumlarda bir referencetür kullanın :

  • Paylaşılan, değiştirilebilir bir durum oluşturmak istiyorsunuz.

Daha fazla bilgi Apple belgelerinde de bulunabilir

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


ek bilgi

Swift değer türleri yığın içinde tutulur. Bir işlemde, her iş parçacığının kendi yığın alanı vardır, dolayısıyla başka hiçbir iş parçacığı doğrudan değer türünüze erişemez. Bu nedenle, yarış koşulları, kilitler, kilitlenmeler veya ilgili iplik senkronizasyonu karmaşıklığı yoktur.

Değer türleri, her ikisi de pahalı işlemler olan dinamik bellek ayırma veya referans saymaya ihtiyaç duymaz. Aynı zamanda değer türlerine ilişkin yöntemler statik olarak gönderilir. Bunlar performans açısından değer türleri lehine büyük bir avantaj yaratır.

Hatırlatma olarak, Swift'in bir listesi

Değer türleri:

  • struct
  • Sıralama
  • Kayıt düzeni
  • İlkeller (Int, Double, Bool vb.)
  • Koleksiyonlar (Dizi, Dizi, Sözlük, Küme)

Referans türleri:

  • Sınıf
  • NSObject'ten gelen her şey
  • fonksiyon
  • kapatma

5

Soruyu değer türleri ve referans türleri açısından cevaplamak, bu Apple blog yazısından çok basit görünecektir:

Aşağıdaki durumlarda bir değer türü [örn. Struct, enum] kullanın:

  • Örnek verileri == ile karşılaştırmak mantıklı
  • Kopyaların bağımsız durumuna sahip olmasını istiyorsunuz
  • Veriler kodda birden çok iş parçacığında kullanılacaktır

Aşağıdaki durumlarda bir referans türü [örn. Sınıf] kullanın:

  • Örnek kimliğini === ile karşılaştırmak mantıklı
  • Paylaşılan, değiştirilebilir durum oluşturmak istiyorsunuz

Bu makalede belirtildiği gibi, yazılabilir özelliği olmayan bir sınıf bir yapı ile aynı şekilde davranacaktır (bir ekleyeceğim): yapılar, güvenli uygulama modelleri için en iyisidir - modern uygulama mimarisinde giderek yaklaşan bir gereksinimdir.


3

Sınıflarda kalıtım elde edersiniz ve referans olarak aktarılırlar, yapıların kalıtım yoktur ve değere göre aktarılır.

Swift'te harika WWDC oturumları var, bu özel soru bunlardan birinde ayrıntılı olarak cevaplanıyor. Bunları izlediğinizden emin olun, çünkü Dil kılavuzundan veya iBook'dan çok daha hızlı hızlanmanızı sağlar.


Bahsettiklerinizden bazı bağlantılar verebilir misiniz? Çünkü WWDC'de aralarından seçim yapabileceğiniz çok az şey var, bu konu hakkında konuşan birini izlemek istiyorum
MMachinegun

Benim için bu iyi bir başlangıç: github.com/raywenderlich/…
MMachinegun

2
Muhtemelen bu harika oturumdan bahsediyor: Swift'te Protokol Odaklı Programlama. (Bağlantılar: video ,
konuşma

2

Yapıların daha az işlevsellik sunduğunu söyleyemem.

Elbette, benlik, mutasyona uğrayan bir işlev dışında değişmezdir, ama hepsi bu kadar.

Kalıtım, her sınıfın soyut ya da nihai olması gerektiği eski iyi fikrine bağlı kaldığınız sürece iyi çalışır.

Soyut sınıfları protokol, son sınıfları da yapı olarak uygular.

Yapılarla ilgili güzel bir şey, paylaşılan mutable durumu oluşturmadan alanlarınızı değiştirilebilir hale getirebilmenizdir çünkü yazma üzerine kopya bununla ilgilenir :)

Bu nedenle, aşağıdaki örnekteki özelliklerin / alanların hepsi değiştirilebilir, bu da Java veya C # veya swift sınıflarında yapmayacağım .

"Örnek" işlevinde alt kısımda biraz kirli ve anlaşılır kullanım içeren örnek kalıtım yapısı:

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

2

Swift'te Protokol Odaklı Programlama olarak bilinen yeni bir programlama modeli tanıtıldı.

Yaratıcı Desen:

Hızlı olarak Struct, otomatik olarak klonlanan bir değer türüdür . Bu nedenle, prototip modelini ücretsiz olarak uygulamak için gerekli davranışı elde ederiz.

Oysa sınıfları otomatik olarak görev sırasında klonlanmış değildir referans tipi vardır. Prototip modelini uygulamak için sınıfların NSCopyingprotokolü benimsemesi gerekir .


Sığ kopya yalnızca bu nesnelere işaret eden başvuruyu çoğaltırken derin kopya nesnenin başvurusunu çoğaltır.


Her referans türü için derin kopya uygulamak yorucu bir iş haline gelmiştir. Sınıflar daha fazla referans türü içeriyorsa, referans özelliklerinin her biri için prototip deseni uygulamalıyız. Ve sonra protokolü uygulayarak tüm nesne grafiğini kopyalamamız gerekiyor .NSCopying

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

class Address{
   var street:String
   ...
} 

Yapılar ve numaralandırmalar kullanarak , kopya mantığını uygulamak zorunda olmadığımız için kodumuzu basitleştirdik.


1

Birçok Kakao API'sı, sizi sınıf kullanmaya zorlayan NSObject alt sınıfları gerektirir. Ancak bunun dışında, bir yapı / numaralandırma değeri türü veya bir sınıf başvuru türü kullanıp kullanmayacağınıza karar vermek için Apple'ın Swift blogundaki aşağıdaki durumları kullanabilirsiniz.

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


0

Bu cevaplarda dikkat çekmeyen bir nokta, bir yapıya karşı bir sınıfı tutan bir değişkenin let , nesnenin özelliklerinde değişikliklere izin verirken süre olabileceği, ancak bunu bir yapı ile yapamazsınız.

Değişkenin başka bir nesneyi hiç göstermesini istemiyorsanız, ancak yine de nesneyi değiştirmeniz gerekiyorsa, örneğin birbiri ardına güncellemek istediğiniz birçok örnek değişkeni olması durumunda bu yararlıdır. Bir yapı ise, bunu yapmak için değişkenin tamamen başka bir nesneye sıfırlanmasına izin vermelisiniz var, çünkü Swift'teki sabit bir değer türü düzgün bir şekilde sıfır mutasyona izin verirken, referans türleri (sınıflar) bu şekilde davranmaz.


0

Yapı değer türleridir ve yığını içine depolayan hafızayı kolayca oluşturabilirsiniz.Yapıya kolayca erişilebilir ve işin kapsamından sonra yığının üstünden pop ile yığın hafızasından kolayca aktarılır. Öte yandan sınıf, yığın halinde depolanan ve bir sınıf nesnesinde yapılan değişikliklerin sıkı bir şekilde bağlandıkları ve referans tür olarak diğer nesneyi etkileyeceği bir referans türüdür. .

Yapının dezavantajları, miras alınamamasıdır.


-7
  • Yapı ve sınıf kullanıcı tarafından tanımlanan veri türleridir

  • Varsayılan olarak, yapı halka açıkken sınıf özeldir

  • Sınıf kapsülleme prensibini uygular

  • Sınıftaki nesneler yığın belleğinde oluşturulur

  • Sınıf yeniden kullanılabilirlik için kullanılırken, yapı aynı yapıdaki verilerin gruplanması için kullanılır

  • Yapı veri üyeleri doğrudan başlatılamaz, ancak yapı dışından atanabilir

  • Sınıf veri üyeleri, daha az parametre yapıcısı tarafından doğrudan başlatılabilir ve parametrelenmiş kurucu tarafından atanabilir


2
En kötü cevap!
J. Doe

kopyala yapıştır
jawadAli
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.