Javaのバックグラウンドから来たSwiftで遊んでみて、なぜクラスではなくStructを選びたいのですか?それらは同じものであるように思えますが、Structはより少ない機能を提供します。なぜそれを選ぶのですか?
非常に人気のあるWWDC 2015のSwiftでのプロトコル指向プログラミング( video 、 transcript )によると、Swiftは多くの状況で構造体をクラスよりも良くする多くの機能を提供します。
コピーがクラスで起こるように同じインスタンスへの複数の参照を持つよりはるかに安全であるので、それらが比較的小さくてコピー可能であるならば、構造体は好ましいです。これは、変数を多くのクラスに渡したり、マルチスレッド環境で渡したりする場合に特に重要です。あなたが常にあなたの変数のコピーを他の場所に送ることができるのであれば、あなたはその他の場所があなたの下のあなたの変数の値を変えることを心配する必要は決してありません。
Structsを使用すると、変数の単一のインスタンスにアクセスしたり変更したりするためのメモリリークや複数のスレッドの競合について心配する必要がはるかに少なくなります。 (もっと技術的なことを言うと、クロージャの中で構造体をキャプチャするときは例外です。なぜなら、明示的にコピーするようにマークしない限り、インスタンスへの参照をキャプチャしているからです)。
クラスは単一のスーパークラスからしか継承できないため、クラスも肥大化する可能性があります。それは私達が大まかに関連しているだけである多くの異なる能力を包含する巨大なスーパークラスを作成することを奨励します。プロトコルを使用すると、特にプロトコルに実装を提供できるプロトコル拡張機能を使用すると、このような動作を実現するためのクラスが不要になります。
この講演では、クラスが優先される次のシナリオについて説明します。
- インスタンスをコピーまたは比較することは意味がありません(例:Window)
- インスタンスの存続期間は、外部効果(TemporaryFileなど)に関連付けられています
- インスタンスは単なる「シンク」 - 外部状態への書き込み専用コンジット(例:CGContext)
これは、構造体がデフォルトでクラスがフォールバックであるべきであることを意味します。
一方、 The Swift Programming Language のドキュメントは多少矛盾しています。
構造体インスタンスは常に値によって渡され、クラスインスタンスは常に参照によって渡されます。つまり、さまざまな種類の作業に適しています。プロジェクトに必要なデータ構造と機能を検討しながら、各データ構造をクラスとして定義するか構造として定義するかを決定します。
一般的なガイドラインとして、以下の条件が1つ以上当てはまる場合に構造を作成することを検討してください。
- この構造の主な目的は、いくつかの比較的単純なデータ値をカプセル化することです。
- その構造のインスタンスを代入または受け渡しするときに、カプセル化された値が参照されるのではなくコピーされることを期待するのは妥当です。
- 構造体に格納されているプロパティはすべて値型であり、参照されるのではなくコピーされることも想定されます。
- 構造体は、他の既存の型からプロパティや動作を継承する必要はありません。
構造の良い候補の例は次のとおりです。
- 幾何学的図形のサイズ。おそらくwidthプロパティとheightプロパティをカプセル化します。どちらもDouble型です。
- 系列内の範囲を参照する方法。おそらく、両方ともInt型のstartプロパティとlengthプロパティをカプセル化します。
- それぞれがDouble型の、x、y、およびzプロパティをカプセル化している可能性がある3D座標系内の点。
それ以外の場合はすべて、クラスを定義し、そのクラスのインスタンスを作成して参照および参照で管理します。実際には、これはほとんどのカスタムデータ構成体が構造体ではなくクラスであるべきであることを意味します。
ここでは、特定の状況でのみクラスを使用し、構造を使用することをデフォルトとすべきだと主張しています。最終的には、値型と参照型の現実の関係を理解する必要があります。それから、いつ構造体またはクラスを使用するかについて十分な情報に基づいた決定を下すことができます。また、これらの概念は常に進化しており、Swift Programming Languageのドキュメントはプロトコル指向プログラミングの講演が行われる前に作成されたものであることにも注意してください。
構造体インスタンスはスタック上に割り当てられ、クラスインスタンスはヒープ上に割り当てられるため、構造体は時々劇的に速くなることがあります。
しかし、あなたはいつも自分でそれを測定し、あなたのユニークなユースケースに基づいて決めるべきです。
Int
とstruct
を使用してclass
データ型をラップする2つの方法を示す次の例を検討してください。複数のフィールドがある現実の世界をよりよく反映するために、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 にあります。
UPDATE(2018年3月27日) :
IPhone 6S、iOS 11.2.6でリリースビルドを実行しているSwift 4.0、Xcode 9.2、Swift Compilerの設定は-O -whole-module-optimization
です。
class
バージョンは2.06秒かかりましたstruct
バージョンは4.17e-08秒かかりました(50,000,000倍高速)(分散が非常に小さいので、私はもはや複数回の実行を平均化していません、5%以下)
注 :モジュール全体を最適化しない限り、違いはそれほど劇的ではありません。誰かがフラグが実際に何をするのかを指摘できればうれしいです。
UPDATE(2016年5月7日) :
Swift 2.2.1、Xcode 7.3、iPhone 6S、iOS 9.3.1でリリースビルドを実行し、平均5回の実行で、Swift Compiler設定は-O -whole-module-optimization
です。
class
のバージョンは2.159942142sでしたstruct
バージョンは5.83E-08秒(37,000,000倍高速)注 :実際のシナリオでは、1つの構造体に1つ以上のフィールドが存在する可能性があると誰かが言ったように、1ではなく10フィールドの構造体/クラスのテストを追加しました。 tはさまざまです。
当初の結果 (2014年6月1日):
(10ではなく1つのフィールドを持つ構造体/クラスで実行されました)
Swift 1.2、Xcode 6.3.2、リリースビルドをiPhone 5S、iOS 8.3上で実行し、平均5回の実行
class
バージョンの所要時間は9.788332333sstruct
バージョンの値は0.010532942秒(900倍高速)古い結果 (未知の時間から)
(10ではなく1つのフィールドを持つ構造体/クラスで実行されました)
私のMacBook Proのリリースビルドでは:
class
バージョンは1.10082秒かかりましたstruct
バージョンは0.02324秒かかりました(50倍高速)私は簡単な例でこれのための要旨を作成しました。 https://github.com/objc-Swift/swift-classes-vs-structures
構造はSwiftでは継承できません。お望みならば
class Vehicle{
}
class Car : Vehicle{
}
クラスに行きます。
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クラスを使用した場合、これは有効な式です。クラス内では不変定数はそのインスタンス変数ではなくクラス自体への参照であるため(これらの変数が定数として定義されている場合を除く)
考慮すべきその他の理由は次のとおりです。
構造体は自動初期化子を取得するので、コード内でメンテナンスする必要はまったくありません。
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")
これをクラスで取得するには、イニシャライザと maintain イニシャライザを追加する必要があります。
Array
のような基本的なコレクション型は構造体です。あなた自身のコードでそれらを使うほど、参照ではなく値渡しに慣れるでしょう。例えば:
func removeLast(var array:[String]) {
array.removeLast()
println(array) // [one, two]
}
var someArray = ["one", "two", "three"]
removeLast(someArray)
println(someArray) // [one, two, three]
不変性と可変性は明らかに大きなトピックですが、多くの頭の良い人は不変性(この場合は構造体)が望ましいと考えています。 可変オブジェクトと不変オブジェクト
Structがvalue typeで、Classがreference typeであることがわかっているとします。
値型と参照型が何であるかわからない場合は、 参照渡しと値渡しの違いは何ですか?を参照してください。
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などの型はクラスではなく構造体です。それらは代入時に、そしてパラメータとしてそれらを渡す時にコピーされます。コピーが安価である限り、これは完全に賢明な選択です。 ...
さらに、関数のすべてのインスタンスをオーバーライドする必要がある場合、つまりshared機能を持たない場合は、classを使用しないでください。
それで、クラスのいくつかのサブクラスを持つ代わりに。プロトコルに準拠したいくつかの構造体を使用してください。
いくつかの利点:
構造はClassよりはるかに速いです。また、継承が必要な場合はClassを使用する必要があります。最も重要な点は、Classが参照型であるのに対し、Structureは値型であるということです。例えば、
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" )
これらのインスタンスを、ID、説明、宛先などを変更する2つの関数に渡します。
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"
ここでは、modifyメソッドに渡されたパラメータが実際にflightAオブジェクトのメモリアドレス(参照型)を指しているため、FlightAのIDと説明が変更されていることがわかります。
fLightBインスタンスのIDと説明を表示すると、
id = 100
description = "first ever flight of Virgin Airlines"
ここで、FlightBインスタンスは変更されていないことがわかります。これは、modifyFlight2メソッドでは、参照(値型)ではなく実際のFlight2のインスタンスが渡されるためです。
値タイプと参照タイプの観点から質問に答えると、 このAppleのブログ投稿 /それは非常に単純に見えるでしょう。
値型を使用する構造体、列挙型]
- インスタンスデータと==を比較することは理にかなっています
- あなたはコピーに独立した状態を持たせたい
- データは複数のスレッドにわたるコードで使用されます
参照型を使用するいつ]
- インスタンスのアイデンティティと===を比較することは理にかなっています
- 共有された可変状態を作成したい
その記事で述べたように、書き込み可能なプロパティを持たないクラスは、1つ注意を払って(追加します)、structと同じように振る舞います:structは スレッドセーフなモデルに最適です アプリのアーキテクチャ.
あなたが継承を得て、参照によって渡されるクラスでは、構造体は継承を持たず、値によって渡されます。
Swiftには素晴らしいWWDCセッションがあります。この特定の質問は、そのうちの1つで詳細に回答されています。言語ガイドやiBookよりもはるかに速くスピードアップできるようになるので、必ずそれらを見てください。
構造体が提供する機能が少ないとは言えません。
もちろん、selfは変化する関数を除いて不変ですが、それだけです。
すべてのクラスは抽象クラスまたは最終クラスのどちらかでなければならないという古き良き考えに固執する限り、継承はうまく機能します。
抽象クラスをプロトコルとして実装し、最後のクラスを構造体として実装します。
構造体についての素晴らしいところは、コピーオンライトがそれを処理するので、共有可変状態を作成せずにフィールドを可変にできることです。
そのため、次の例のプロパティ/フィールドはすべて可変になっています。JavaやC#、Swift classes にはありません。
"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)")
}
Swiftでは、プロトコル指向プログラミングと呼ばれる新しいプログラミングパターンが導入されました。
作成パターン:
Swiftでは、Structは 値型 で、自動的に複製されます。したがって、プロトタイプパターンを無料で実装するために必要な動作が得られます。
classes は参照型ですが、代入時に自動的に複製されることはありません。プロトタイプパターンを実装するために、クラスはNSCopying
プロトコルを採用しなければなりません。
シャローコピー はそれらのオブジェクトを指す参照のみを複製しますが、 ディープコピー はオブジェクトの参照を複製します。
ディープコピー をそれぞれの 参照型 に実装するのは面倒な作業になりました。クラスにさらに参照型が含まれる場合は、各参照プロパティに対してプロトタイプパターンを実装する必要があります。それからNSCopying
プロトコルを実装することによってオブジェクトグラフ全体を実際にコピーしなければなりません。
class Contact{
var firstName:String
var lastName:String
var workAddress:Address // Reference type
}
class Address{
var street:String
...
}
structsとenums を使用することで、コピーロジックを実装する必要がないので、コードをよりシンプルにしました。
Structs
はvalue type
で、Classes
はreference type
です
次の場合はvalue
型を使用してください。
次の場合はreference
型を使用してください。
詳しい情報はAppleのドキュメントにもあります。
https://docs.Swift.org/Swift-book/LanguageGuide/ClassesAndStructures.html
追加情報
高速値タイプはスタックに保持されます。プロセス内では、各スレッドは独自のスタックスペースを持っているため、他のスレッドが自分の値型に直接アクセスすることはできません。したがって、競合状態、ロック、デッドロック、または関連するスレッド同期の複雑さはありません。
値型は動的なメモリ割り当てや参照カウントを必要としません。どちらも高価な操作です。同時に値型のメソッドは静的にディスパッチされます。これらは、パフォーマンスの点で値型を優先するという大きな利点を生み出します。
念のため、Swiftのリストをここに示します。
値のタイプ:
参照タイプ:
多くのCocoa APIはNSObjectサブクラスを必要とします。ただし、それ以外に、AppleのSwiftブログの以下のケースを使用して、構造体/列挙型またはクラス参照型のどちらを使用するかを決定できます。
構造体は値型なので、スタックに格納するメモリを非常に簡単に作成できます。構造体は簡単にアクセスでき、作業の範囲を超えた後はスタックの一番上からpopを通じてスタックメモリから簡単に割り当て解除されます。一方、classはヒープに格納する参照型であり、1つのクラスオブジェクトに加えられた変更は密接に結合され参照型として他のオブジェクトに影響を与えます。 。
構造体の不利な点は、それを継承できないことです。
これらの答えで注目されていない1つの点は、クラスを保持する変数と構造体を保持する変数はlet
になり得ますが、それでもオブジェクトのプロパティを変更することはできますが、構造体ではできません。
これは、変数が他のオブジェクトを指し示したくないが、それでもオブジェクトを変更する必要がある場合、つまり、次々に更新したいインスタンス変数が多数ある場合に役立ちます。それが構造体である場合、Swiftの定数値型は正しくゼロ変換を許可しますが、参照型(クラス)はこのように動作しないため、これを行うにはvar
を使用して変数を別のオブジェクトにリセットできます。 。