クラスよりも構造を選ぶ理由


476

Javaのバックグラウンドから来たSwiftをいじって、なぜクラスではなく構造体を選択したいのですか?Structが提供する機能は少なく、それらは同じもののようです。なぜそれを選ぶのですか?


11
構造体は、コード内で渡されるときに常にコピーされ、参照カウントを使用しません。ソース:developer.apple.com/library/prerelease/ios/documentation/swift/...
holex

4
構造体は、ロジックではなくデータを保持するのに適しています。Java用語で話すために、構造体を「値オブジェクト」と想像してください。
Vincent Guerci 2014年

6
私はこの会話全体に驚いていますコピーオンライト、つまりレイジーコピーの直接の言及はありません。構造体のコピーのパフォーマンスに関する懸念は、この設計のため、ほとんど問題になりません。
デビッドジェームズ

3
クラスよりも構造体を選択することは意見の問題ではありません。どちらか一方を選択する特定の理由があります。
デビッドジェームズ

なぜArrayがthreadSafeではないのかを確認することを強くお勧めします。配列と構造体はどちらも値型であるため、関連しています。ここでのすべての答えは、構造体/配列/値型を使用すると、スレッドの安全性の問題が発生することは決してないが、そうなる場合があるということです。
蜂蜜

回答:


548

非常に人気のあるWWDC 2015トークによるSwiftのプロトコル指向プログラミング(ビデオトランスクリプト)によると、Swiftは、多くの状況で構造体をクラスよりも優れたものにする多数の機能を提供します。

クラスで発生するように、同じインスタンスへの複数の参照を持つよりもコピーの方がはるかに安全であるため、構造が比較的小さくコピー可能な場合は、構造が望ましいです。これは、変数を多くのクラスに渡す場合や、マルチスレッド環境で特に重要です。常に変数のコピーを他の場所に送信できる場合は、他の場所が自分の下にある変数の値を変更することを心配する必要はありません。

Structsを使用すると、変数の単一のインスタンスにアクセス/変更するためにメモリリークや複数のスレッドが競合することを心配する必要がはるかに少なくなります。(より技術的に考えた場合、例外はクロージャー内の構造体をキャプチャする場合です。コピーするように明示的にマークしない限り、インスタンスへの参照を実際にキャプチャするためです)。

クラスは単一のスーパークラスからしか継承できないため、クラスも肥大化する可能性があります。そのため、大まかに関連しているだけのさまざまな能力を網羅する巨大なスーパークラスを作成することが奨励されます。プロトコルを使用すると、特にプロトコルの実装を提供できるプロトコル拡張機能を使用すると、この種の動作を実現するためのクラスの必要性を排除できます。

講演では、クラスが推奨される次のシナリオについて説明します。

  • インスタンスをコピーまたは比較しても意味がありません(例:ウィンドウ)
  • インスタンスの存続期間は外部効果(TemporaryFileなど)に関連付けられています
  • インスタンスは単なる「シンク」です-外部状態への書き込み専用コンジット(egCGContext)

これは、構造体がデフォルトであり、クラスがフォールバックであることを意味します。

一方、Swiftプログラミング言語のドキュメントは多少矛盾しています。

構造体インスタンスは常に値によって渡され、クラスインスタンスは常に参照によって渡されます。つまり、さまざまな種類のタスクに適しています。プロジェクトに必要なデータ構成と機能を検討するときは、各データ構成をクラスとして定義するか、構造として定義するかを決定します。

一般的なガイドラインとして、次の条件の1つ以上が当てはまる場合は、構造を作成することを検討してください。

  • 構造の主な目的は、いくつかの比較的単純なデータ値をカプセル化することです。
  • カプセル化された値は、その構造体のインスタンスを割り当てたり渡したりするときに、参照されるのではなくコピーされることを期待するのが妥当です。
  • 構造体に格納されているプロパティは、それ自体が値型であり、参照されるのではなくコピーされることも予想されます。
  • 構造は、別の既存のタイプからプロパティまたは動作を継承する必要はありません。

構造の良い候補の例は次のとおりです。

  • 幾何学的形状のサイズで、幅のプロパティと高さのプロパティをカプセル化します。どちらもDoubleタイプです。
  • シリーズ内の範囲を参照する方法。おそらくInt型の開始プロパティと長さプロパティをカプセル化します。
  • 3D座標系内のポイント。おそらく、タイプDoubleのx、y、zプロパティをカプセル化します。

それ以外の場合はすべて、クラスを定義し、そのクラスのインスタンスを作成して、参照によって管理および渡されるようにします。実際には、これはほとんどのカスタムデータ構成が構造ではなくクラスであることを意味します。

ここでは、デフォルトでクラスを使用し、特定の状況でのみ構造を使用する必要があると主張しています。最終的に、値型と参照型の実際の関係を理解する必要があり、構造体やクラスをいつ使用するかについて情報に基づいた決定を下すことができます。また、これらの概念は常に進化しており、プロトコル指向プログラミングの講演が行われる前にSwiftプログラミング言語のドキュメントが作成されたことにも注意してください。


12
@ElgsQianChenこの記事の要点は、構造体はデフォルトで選択されるべきであり、クラスは必要な場合にのみ使用されるべきであるということです。特にマルチスレッド環境では、構造体ははるかに安全でバグがありません。はい、いつでも構造体の代わりにクラスを使用できますが、構造体が望ましいです。
drewag 2014

16
@drewagそれはそれが言っていることの正反対のようです。クラスは構造ではなく使用するデフォルトであるべきだと言っていましたが、それをIn practice, this means that most custom data constructs should be classes, not structures.読んだ後、ほとんどのデータセットがクラスではなく構造であることをどのように理解できますか?彼らは何かが構造体であるべきであるときに特定のルールのセットを与え、そしてほとんどが「クラスがより良い他のすべてのシナリオ」と述べました。
Matt

42
最後の行は「私の個人的なアドバイスはドキュメントの反対です:」と言うべきです...そして、それは素晴らしい答えです!
Dan Rosenstark、2015年

5
Swift 2.2の本には、ほとんどの状況でクラスを使用することが記載されています。
David James

6
Struct over Classは間違いなく複雑さを軽減します。しかし、構造体がデフォルトの選択になったときのメモリ使用量への影響は何ですか。参照の代わりにどこにでもコピーされると、アプリによるメモリ使用量が増えるはずです。そうじゃないの?
MadNik 2016年

164

構造体のインスタンスはスタックに割り当てられ、クラスのインスタンスはヒープに割り当てられるため、構造体が大幅に高速になる場合があります。

ただし、常に自分で測定し、独自のユースケースに基づいて決定する必要があります。

次の例を検討してください。これは、およびIntを使用structしてデータ型をラップする2つの方法を示していclassます。複数のフィールドがある実際の世界をよりよく反映するために、10個の繰り返し値を使用しています。

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

パフォーマンスは、

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

コードはhttps://github.com/knguyen2708/StructVsClassPerformanceで見つけることができます

更新(2018年3月27日)

Swift 4.0、Xcode 9.2以降、iPhone 6S、iOS 11.2.6でリリースビルドを実行し、Swiftコンパイラの設定は-O -whole-module-optimization次のとおりです。

  • class バージョンは2.06秒かかりました
  • struct バージョンにかかる時間は4.17e-08秒(50,000,000倍高速)

(分散が非常に小さく、5%未満であるため、複数の実行を平均することはもうありません)

:モジュール全体を最適化しないと、違いはそれほど劇的ではありません。誰かがフラグが実際に何をしているのか指摘できたらうれしいです。


更新(2016年5月7日)

Swift 2.2.1、Xcode 7.3、iPhone 6S、iOS 9.3.1でリリースビルドを実行し、5回以上の平均で、Swiftコンパイラ設定は-O -whole-module-optimization次のとおりです。

  • class バージョンに2.159942142秒かかりました
  • struct バージョンに5.83E-08秒かかりました(37,000,000倍高速)

:実際のシナリオでは、構造体には1つ以上のフィールドが存在する可能性があると誰かが言ったように、1つではなく、10個のフィールドを持つ構造体/クラスのテストを追加しました。


元の結果(2014年6月1日):

(10ではなく、1つのフィールドを持つ構造体/クラスで実行されました)

Swift 1.2、Xcode 6.3.2以降、iPhone 5S、iOS 8.3でリリースビルドを実行し、平均5回以上実行

  • class バージョンは9.788332333秒かかりました
  • struct バージョンに0.010532942秒かかりました(900倍高速)

古い結果(不明な時間から)

(10ではなく、1つのフィールドを持つ構造体/クラスで実行されました)

MacBook Proでリリースビルドを使用する場合:

  • classバージョンは1.10082秒かかりました
  • structバージョンは0.02324秒(50倍の速さ)を取りました

27
確かに、しかし、構造体の束のコピーは、単一のオブジェクトへの参照をコピーするよりも遅いようです。言い換えれば、任意の大きなメモリブロックをコピーするよりも、単一のポインタをコピーする方が高速です。
Tylerc230 2015

14
-1構造体には変数が1つしかないため、このテストは良い例ではありません。複数の値と1つまたは2つのオブジェクトを追加すると、構造体のバージョンはクラスのバージョンと同等になることに注意してください。追加する変数が多いほど、構造体のバージョンの取得が遅くなります。
joshrl

6
@joshrlはあなたの要点を理解しましたが、例は「良い」か、特定の状況に依存していません。このコードは自分のアプリから抽出されたものなので、有効なユースケースであり、構造体を使用するとアプリのパフォーマンスが大幅に向上しました。それはおそらく一般的なユースケースではないでしょう(まあ、一般的なユースケースは、ほとんどのアプリにとって、ボトルネックがどこか他の場所で発生するため、誰もが高速でデータを渡すことができることを気にしていません例えば、ネットワーク接続など、とにかく、最適化はそれではありませんGBまたはRAMを備えたGHzデバイスがある場合に重要です)。
カーングエン2015

26
私が理解している限り、swiftでのコピーはWRITE時に発生するように最適化されています。つまり、新しいコピーが変更されない限り、物理メモリのコピーは作成されません。
Matjan

6
この回答は、非常に些細な例を示しています。非現実的であり、多くの場合正しくありません。より良い答えは「それは依存する」でしょう。
iwasrobbed、2016年

60

構造体とクラスの類似点。

簡単な例を挙げて、このための要点を作成しました。 https://github.com/objc-swift/swift-classes-vs-structures

そして違い

1.継承。

構造は迅速に継承できません。お望みならば

class Vehicle{
}

class Car : Vehicle{
}

クラスに行きます。

2.通り過ぎる

Swift構造は値渡しで、クラスインスタンスは参照渡しです。

コンテキストの違い

構造定数と変数

例(WWDC 2014で使用)

struct Point{

   var x = 0.0;
   var y = 0.0;

} 

Pointと呼ばれる構造体を定義します。

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

ここで、xを変更しようとすると、その有効な式。

point.x = 5

しかし、ポイントを定数として定義した場合。

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

この場合、ポイント全体が不変の定数です。

代わりにクラスPointを使用した場合、これは有効な式です。クラスでは不変定数はインスタンス変数ではなくクラス自体への参照であるため(これらの変数が定数として定義されている場合を除く)



12
上記の要点は、構造体の継承フレーバーを実現する方法についてです。次のような構文が表示されます。A:B. Aと呼ばれる構造体はBと呼ばれるプロトコルを実装します。Appleのドキュメントでは、構造体は純粋な継承をサポートしておらず、サポートしていないことを明確に述べています。
MadNik

2
あなたの最後の段落が素晴らしかった男。私はあなたが定数を変更できることを常に知っていました...しかし、時々私はあなたができない場所を見たので私は困惑しました。この違いがそれを目に見えるものにしました
ハニー

28

考慮すべきその他の理由は次のとおりです。

  1. 構造体は、コードで維持する必要のない自動初期化子を取得します。

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

これをクラスで取得するには、初期化子を追加し、初期化子を維持する必要があります...

  1. のような基本的なコレクション型Arrayは構造体です。独自のコードでそれらを使用すればするほど、参照ではなく値渡しに慣れるようになります。例えば:

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
  2. どうやら不変性と不変性は大きなトピックですが、多くの賢い人々は不変性(この場合は構造体)が望ましいと考えています。可変オブジェクトと不変オブジェクト


4
自動イニシャライザを取得するのは本当です。また、すべてのプロパティがオプションの場合、空の初期化子を取得します。ただし、フレームワークに構造体がある場合、internalスコープ外で使用できるようにするには、実際にイニシャライザを自分で作成する必要があります。
Abizern 2016年

2
@Abizernは確認- stackoverflow.com/a/26224873/8047迷惑ですし、男を- 。
Dan Rosenstark 2016年

2
@AbizernにはSwiftのすべてに大きな理由がありますが、ある場所で何かが真になり、別の場所では真ではない場合、開発者はより多くのことを知る必要があります。ここで私が言うべきだと思うのは、「そのような難しい言語で働くことはエキサイティングです!」
Dan Rosenstark 2016年

4
構造体の不変性ではないので便利です(ただし、これは非常に良いことです)。構造体をmutating変更することはできますが、どの関数が状態を変更するかを明示するために、メソッドにマークを付ける必要があります。しかし、値タイプとしてのその性質が重要です。構造体を宣言すると、その構造体で変更let関数を呼び出すことができなくなります。値型によるプログラミングの向上に関するWWDC 15ビデオは、これに関する優れたリソースです。
Abizern 2016年

1
@Abizernに感謝します。コメントを読む前に、これを理解することはできませんでした。オブジェクトの場合、vs。varはそれほど大きな違いはありませんが、構造体の場合は大きな違いがあります。これを指摘してくれてありがとう。
Dan Rosenstark 2016年

27

Struct値型でClass参照型であることを知っていると仮定します。

値の型と参照の型がわからない場合は、参照渡しと値渡しの違いを参照してください

mikeashの投稿に基づく:

...まず、極端で明白な例をいくつか見てみましょう。整数は明らかにコピー可能です。それらは値型である必要があります。ネットワークソケットを適切にコピーすることはできません。それらは参照型でなければなりません。x、yのペアのように、ポイントはコピー可能です。それらは値型である必要があります。ディスクを表すコントローラーは、適切にコピーできません。それは参照型でなければなりません。

一部のタイプはコピーできますが、常に実行する必要があるものではない場合があります。これは、それらが参照型であることを示唆しています。たとえば、画面上のボタンを概念的にコピーできます。コピーは元のコピーとまったく同じにはなりません。コピーをクリックしてもオリジナルはアクティブになりません。コピーは画面上の同じ場所を占有しません。ボタンを渡したり、新しい変数に入れたりする場合は、おそらく元のボタンを参照し、明示的に要求された場合にのみコピーを作成します。つまり、ボタンタイプは参照タイプである必要があります。

ビューコントローラーとウィンドウコントローラーも同様の例です。それらはおそらくコピー可能かもしれませんが、あなたがやりたいことはほとんど決してありません。それらは参照型でなければなりません。

モデルタイプはどうですか?システム上のユーザーを表すユーザータイプや、ユーザーが行ったアクションを表す犯罪タイプがある場合があります。これらはかなりコピー可能であるため、おそらく値型である必要があります。ただし、プログラム内の1か所で行われたユーザー犯罪に対する更新をプログラムの他の部分から見えるようにしたい場合があります。 これは、ユーザーが参照タイプとなるある種のユーザーコントローラーによって管理される必要があることを示唆しています。例えば

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

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

コレクションは興味深いケースです。これには、文字列だけでなく、配列や辞書なども含まれます。それらはコピー可能ですか?明らかに。コピーしたいことを簡単かつ頻繁に実行したいですか?それはあまり明確ではありません。

ほとんどの言語はこれに対して「いいえ」と言い、コレクションを参照型にします。これは、Objective-CとJava、PythonとJavaScript、そして私が考えることができる他のほとんどすべての言語に当てはまります。(主な例外の1つは、STLコレクション型を使用したC ++ですが、C ++は、すべてを奇妙に実行する言語の世界で熱狂的です)

Swiftは「はい」と言っています。つまり、ArrayやDictionaryやStringなどの型は、クラスではなく構造体です。それらは、割り当て時、およびパラメーターとして渡すときにコピーされます。コピーが安価である限り、これはまったく賢明な選択であり、Swiftはこれを達成するために非常に懸命に努力しています。...

私は自分のクラスにそのような名前を付けていません。私は通常UserControllerではなくUserManagerと名付けますが、考え方は同じです

さらに、関数のすべてのインスタンスをオーバーライドする必要がある場合は、クラスを使用しないでください。つまり、共有機能を持たないものです。

したがって、クラスのいくつかのサブクラスを持つ代わりに。プロトコルに準拠するいくつかの構造体を使用します。


構造体のもう1つの妥当なケースは、古いモデルと新しいモデルのデルタ/差分を実行する場合です。参照型では、それをそのまま使用することはできません。値型では、変異は共有されません。


1
まさに私が探していた説明。素敵な書き込み:)
androCoder-BD 2018

非常に役立つコントローラーの例
Pに質問

1
@AskP私はマイク自身にメールを送り、その余分なコードを取得しました:)
ハニー

18

いくつかの利点:

  • 共有できないため自動的にスレッドセーフ
  • isaとrefcountがないため、メモリ使用量が少なくなります(実際、一般的にスタックが割り当てられます)
  • メソッドは常に静的にディスパッチされるため、インライン化できます(@finalはクラスに対してこれを行うことができます)
  • スレッドセーフと同じ理由で(NSArray、NSStringなどで一般的な「防御的にコピー」する必要がない)

それがこの回答の範囲外であるかどうかはわかりませんが、「メソッドは常に静的にディスパッチされる」ポイントを説明(またはリンク)できますか?
Dan Rosenstark 2016年

2
承知しました。また、警告を添付することもできます。動的ディスパッチの目的は、使用する実装が事前にわからない場合に実装を選択することです。Swiftでは、これは継承(サブクラスでオーバーライドされる可能性があります)、または関数がジェネリックであること(ジェネリックパラメーターが何であるかわからない)が原因です。構造体は継承できず、モジュール全体の最適化+ジェネリックの特殊化によって未知のジェネリックがほとんど排除されるため、メソッドは、何を呼び出すかを調べる必要がなく、直接呼び出すことができます。ただし、
特殊化

1
ありがとう、素晴らしい説明。ですから、ランタイムの速度を上げるか、IDEの観点から曖昧さを減らすか、あるいはその両方を期待していますか?
Dan Rosenstark 2016年

1
主に前者。
Catfish_Man 2016年

プロトコルを介して構造体を参照する場合、メソッドは静的にディスパッチされないことに注意してください。
Cristik

12

構造はクラスよりもはるかに高速です。また、継承が必要な場合は、クラスを使用する必要があります。最も重要な点は、クラスは参照型であり、構造体は値型であることです。例えば、

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  
}

次に、両方のインスタンスを作成します。

var flightA = Flight()

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

次に、これらのインスタンスを2つの関数に渡して、ID、説明、宛先などを変更します。

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

また、

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

そう、

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

今度は、flightAのIDと説明を出力すると、

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

ここでは、変更メソッドに渡されたパラメーターが実際にFlightAオブジェクト(参照タイプ)のメモリアドレスを指しているため、FlightAのIDと説明が変更されていることがわかります。

ここで、取得したFLightBインスタンスのIDと説明を出力すると、

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

ここで、modifyFlight2メソッドではFlight2の実際のインスタンスが参照(値タイプ)ではなくパスであるため、FlightBインスタンスは変更されていないことがわかります。


2
FLightBのインスタンスを作成したことはありません
David Seek

1
では、なぜFlightB仲間について話しているのですか?Here we can see that the FlightB instance is not changed
David Seek

@ManojKarki、素晴らしい答え。FlightAを宣言してからFlightBを宣言したいと思ったときに、flightAを2回宣言したことを指摘したかっただけです。
ScottyBlades 2017

11

Structsありvalue typeClassesありreference type

  • 値型は参照型よりも高速です
  • 値タイプのインスタンスは、マルチスレッド環境で安全です。複数のスレッドが、競合状態やデッドロックを心配することなくインスタンスを変更できるためです。
  • 参照タイプとは異なり、値タイプには参照がありません。したがって、メモリリークはありません。

valueタイプは次の場合に使用します。

  • コピーに独立した状態を持たせたい場合、データは複数のスレッドにわたるコードで使用されます

referenceタイプは次の場合に使用します。

  • 共有された変更可能な状態を作成します。

さらに詳しい情報は、アップルのドキュメントにも記載されています。

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


追加情報

Swift値型はスタックに保持されます。プロセスでは、各スレッドに独自のスタックスペースがあるため、他のスレッドが値型に直接アクセスすることはできません。したがって、競合状態、ロック、デッドロック、または関連するスレッド同期の複雑さはありません。

値型は、動的なメモリ割り当てや参照カウントを必要としません。どちらも高価な操作です。同時に、値型のメソッドは静的にディスパッチされます。これらは、パフォーマンスの点で値型を支持する大きな利点を生み出します。

念のためここにSwiftのリストがあります

値のタイプ:

  • ストラクト
  • 列挙型
  • タプル
  • プリミティブ(Int、Double、Boolなど)
  • コレクション(配列、文字列、辞書、セット)

参照タイプ:

  • クラス
  • NSObjectからのもの
  • 関数
  • 閉鎖

5

値の型と参照型の観点からの質問に答えると、このAppleブログの投稿からそれは非常に単純に見えます。

次の場合、値のタイプ[構造体、列挙型]を使用します。

  • インスタンスデータを==と比較することには意味があります
  • コピーに独立した状態を持たせたい
  • データは複数のスレッドにわたるコードで使用されます

次の場合は、参照タイプ[例:クラス]を使用します。

  • インスタンスIDを===と比較することには意味があります
  • 変更可能な共有状態を作成したい

その記事で述べたように、無書き込み可能なプロパティを持つクラスは、1つの警告(私が追加されます)と、構造体と同じように動作します:構造体は、最良のあるスレッドセーフなモデル -現代のアプリアーキテクチャでますます差し迫った必要条件。


3

継承を取得し、参照によって渡されるクラスでは、構造体には継承がなく、値によって渡されます。

Swiftには素晴らしいWWDCセッションがあり、この特定の質問はそのうちの1つで詳細に回答されています。言語ガイドやiBookよりもはるかに速くスピードアップできるので、これらを必ず確認してください。


あなたが言及したことからいくつかのリンクを提供できますか?WWDCの原因から選択するかなりの数がありますが、私はこの特定のトピックについて話1を見てみたい
MMachinegun

私にとって、これはここで良いスタートです:github.com/raywenderlich/...
MMachinegun

2
彼はおそらくこの素晴らしいセッションについて話しているでしょう:Swiftのプロトコル指向プログラミング。(リンク:ビデオトランスクリプト
zekel

2

構造体が提供する機能が少ないとは言えません。

確かに、変化する関数を除いて、自己は不変ですが、それだけです。

継承は、すべてのクラスが抽象または最終のいずれかでなければならないという古き良き考えに固執する限り、正常に機能します。

抽象クラスをプロトコルとして実装し、最終クラスを構造体として実装します。

構造体の良いところは、書き込み時のコピーがそれを処理するので、共有の可変状態を作成せずにフィールドを可変にすることができることです:)

そのため、次の例のプロパティ/フィールドはすべて可変であり、JavaやC#、Swift クラスでは変更できません。

「example」という名前の関数の下部にある、ダーティでわかりやすい使用法を使用した継承構造の例:

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では、プロトコル指向プログラミングと呼ばれる新しいプログラミングパターンが導入されました。

創造的パターン:

Swiftでは、Structは自動的に複製される値タイプです。したがって、プロトタイプパターンを無料で実装するために必要な動作が得られます。

一方、クラスは参照型であり、割り当て中に自動的に複製されません。プロトタイプパターンを実装するには、クラスでNSCopyingプロトコルを採用する必要があります。


浅いコピーは、それらのオブジェクトを指す参照のみを複製しますが、深いコピーはオブジェクトの参照を複製します。


参照タイプごとにディープコピーを実装するのは、面倒な作業になっています。クラスにさらに参照タイプが含まれている場合、各参照プロパティのプロトタイプパターンを実装する必要があります。そして、プロトコルを実装して、オブジェクトグラフ全体を実際にコピーする必要があります。NSCopying

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

class Address{
   var street:String
   ...
} 

構造体と列挙型を使用することで、コピーロジックを実装する必要がないため、コードが単純になりました。


1

多くのCocoa APIにはNSObjectサブクラスが必要であり、クラスを使用する必要があります。ただし、それ以外の場合は、AppleのSwiftブログにある次のケースを使用して、構造体/列挙型の値タイプとクラス参照タイプのどちらを使用するかを決定できます。

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


0

これらの回答で注意が払われていない1つの点は、クラスと構造体を保持する変数はlet、オブジェクトのプロパティの変更を許可しながら、構造体ではこれを実行できないことです。

これは、変数が別のオブジェクトを指すことを望まないが、オブジェクトを変更する必要がある場合、つまり、次々に更新するインスタンス変数が多い場合に役立ちます。構造体の場合、これを行うには、変数を使用して変数を別のオブジェクトに完全にリセットできるようにする必要があります。Swift varの定数値型は、ゼロミューテーションを適切に許可しますが、参照型(クラス)はこのように動作しません。


0

構造体は値の型であり、スタックに格納するメモリを非常に簡単に作成できます。構造体は簡単にアクセスでき、作業のスコープの後で、スタックの一番上からのポップを通じてスタックメモリから簡単に割り当て解除できます。一方、クラスは参照型であり、ヒープに格納されます。1つのクラスオブジェクトで行われた変更は、密結合で参照型であるため、他のオブジェクトに影響します。構造体のすべてのメンバーはパブリックですが、クラスのすべてのメンバーはプライベートです。

構造体の欠点は、それを継承できないことです。


-7
  • 構造とクラスはユーザー定義のデータ型です

  • デフォルトでは、構造はパブリックですが、クラスはプライベートです

  • クラスはカプセル化の原則を実装します

  • クラスのオブジェクトはヒープメモリ上に作成されます

  • クラスは再利用性のために使用されますが、構造は同じ構造内のデータをグループ化するために使用されます

  • 構造体データメンバーは直接初期化できませんが、構造体の外部から割り当てることができます

  • クラスデータメンバーは、パラメーターなしのコンストラクターによって直接初期化され、パラメーター化されたコンストラクターによって割り当てられます。


2
史上最悪の答え!
J. Doe

コピーペーストの回答
jawadAli
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.