Javaのバックグラウンドから来たSwiftをいじって、なぜクラスではなく構造体を選択したいのですか?Structが提供する機能は少なく、それらは同じもののようです。なぜそれを選ぶのですか?
Javaのバックグラウンドから来たSwiftをいじって、なぜクラスではなく構造体を選択したいのですか?Structが提供する機能は少なく、それらは同じもののようです。なぜそれを選ぶのですか?
回答:
非常に人気のあるWWDC 2015トークによるSwiftのプロトコル指向プログラミング(ビデオ、トランスクリプト)によると、Swiftは、多くの状況で構造体をクラスよりも優れたものにする多数の機能を提供します。
クラスで発生するように、同じインスタンスへの複数の参照を持つよりもコピーの方がはるかに安全であるため、構造が比較的小さくコピー可能な場合は、構造が望ましいです。これは、変数を多くのクラスに渡す場合や、マルチスレッド環境で特に重要です。常に変数のコピーを他の場所に送信できる場合は、他の場所が自分の下にある変数の値を変更することを心配する必要はありません。
Structsを使用すると、変数の単一のインスタンスにアクセス/変更するためにメモリリークや複数のスレッドが競合することを心配する必要がはるかに少なくなります。(より技術的に考えた場合、例外はクロージャー内の構造体をキャプチャする場合です。コピーするように明示的にマークしない限り、インスタンスへの参照を実際にキャプチャするためです)。
クラスは単一のスーパークラスからしか継承できないため、クラスも肥大化する可能性があります。そのため、大まかに関連しているだけのさまざまな能力を網羅する巨大なスーパークラスを作成することが奨励されます。プロトコルを使用すると、特にプロトコルの実装を提供できるプロトコル拡張機能を使用すると、この種の動作を実現するためのクラスの必要性を排除できます。
講演では、クラスが推奨される次のシナリオについて説明します。
- インスタンスをコピーまたは比較しても意味がありません(例:ウィンドウ)
- インスタンスの存続期間は外部効果(TemporaryFileなど)に関連付けられています
- インスタンスは単なる「シンク」です-外部状態への書き込み専用コンジット(egCGContext)
これは、構造体がデフォルトであり、クラスがフォールバックであることを意味します。
一方、Swiftプログラミング言語のドキュメントは多少矛盾しています。
構造体インスタンスは常に値によって渡され、クラスインスタンスは常に参照によって渡されます。つまり、さまざまな種類のタスクに適しています。プロジェクトに必要なデータ構成と機能を検討するときは、各データ構成をクラスとして定義するか、構造として定義するかを決定します。
一般的なガイドラインとして、次の条件の1つ以上が当てはまる場合は、構造を作成することを検討してください。
- 構造の主な目的は、いくつかの比較的単純なデータ値をカプセル化することです。
- カプセル化された値は、その構造体のインスタンスを割り当てたり渡したりするときに、参照されるのではなくコピーされることを期待するのが妥当です。
- 構造体に格納されているプロパティは、それ自体が値型であり、参照されるのではなくコピーされることも予想されます。
- 構造は、別の既存のタイプからプロパティまたは動作を継承する必要はありません。
構造の良い候補の例は次のとおりです。
- 幾何学的形状のサイズで、幅のプロパティと高さのプロパティをカプセル化します。どちらもDoubleタイプです。
- シリーズ内の範囲を参照する方法。おそらくInt型の開始プロパティと長さプロパティをカプセル化します。
- 3D座標系内のポイント。おそらく、タイプDoubleのx、y、zプロパティをカプセル化します。
それ以外の場合はすべて、クラスを定義し、そのクラスのインスタンスを作成して、参照によって管理および渡されるようにします。実際には、これはほとんどのカスタムデータ構成が構造ではなくクラスであることを意味します。
ここでは、デフォルトでクラスを使用し、特定の状況でのみ構造を使用する必要があると主張しています。最終的に、値型と参照型の実際の関係を理解する必要があり、構造体やクラスをいつ使用するかについて情報に基づいた決定を下すことができます。また、これらの概念は常に進化しており、プロトコル指向プログラミングの講演が行われる前にSwiftプログラミング言語のドキュメントが作成されたことにも注意してください。
In practice, this means that most custom data constructs should be classes, not structures.
読んだ後、ほとんどのデータセットがクラスではなく構造であることをどのように理解できますか?彼らは何かが構造体であるべきであるときに特定のルールのセットを与え、そしてほとんどが「クラスがより良い他のすべてのシナリオ」と述べました。
構造体のインスタンスはスタックに割り当てられ、クラスのインスタンスはヒープに割り当てられるため、構造体が大幅に高速になる場合があります。
ただし、常に自分で測定し、独自のユースケースに基づいて決定する必要があります。
次の例を検討してください。これは、および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倍の速さ)を取りました簡単な例を挙げて、このための要点を作成しました。 https://github.com/objc-swift/swift-classes-vs-structures
構造は迅速に継承できません。お望みならば
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")
これをクラスで取得するには、初期化子を追加し、初期化子を維持する必要があります...
のような基本的なコレクション型Array
は構造体です。独自のコードでそれらを使用すればするほど、参照ではなく値渡しに慣れるようになります。例えば:
func removeLast(var array:[String]) {
array.removeLast()
println(array) // [one, two]
}
var someArray = ["one", "two", "three"]
removeLast(someArray)
println(someArray) // [one, two, three]
どうやら不変性と不変性は大きなトピックですが、多くの賢い人々は不変性(この場合は構造体)が望ましいと考えています。可変オブジェクトと不変オブジェクト
internal
スコープ外で使用できるようにするには、実際にイニシャライザを自分で作成する必要があります。
mutating
変更することはできますが、どの関数が状態を変更するかを明示するために、メソッドにマークを付ける必要があります。しかし、値タイプとしてのその性質が重要です。構造体を宣言すると、その構造体で変更let
関数を呼び出すことができなくなります。値型によるプログラミングの向上に関するWWDC 15ビデオは、これに関する優れたリソースです。
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つの妥当なケースは、古いモデルと新しいモデルのデルタ/差分を実行する場合です。参照型では、それをそのまま使用することはできません。値型では、変異は共有されません。
いくつかの利点:
構造はクラスよりもはるかに高速です。また、継承が必要な場合は、クラスを使用する必要があります。最も重要な点は、クラスは参照型であり、構造体は値型であることです。例えば、
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インスタンスは変更されていないことがわかります。
Here we can see that the FlightB instance is not changed
Structs
ありvalue type
、Classes
ありreference type
value
タイプは次の場合に使用します。
reference
タイプは次の場合に使用します。
さらに詳しい情報は、アップルのドキュメントにも記載されています。
https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
追加情報
Swift値型はスタックに保持されます。プロセスでは、各スレッドに独自のスタックスペースがあるため、他のスレッドが値型に直接アクセスすることはできません。したがって、競合状態、ロック、デッドロック、または関連するスレッド同期の複雑さはありません。
値型は、動的なメモリ割り当てや参照カウントを必要としません。どちらも高価な操作です。同時に、値型のメソッドは静的にディスパッチされます。これらは、パフォーマンスの点で値型を支持する大きな利点を生み出します。
念のためここにSwiftのリストがあります
値のタイプ:
参照タイプ:
値の型と参照型の観点からの質問に答えると、このAppleブログの投稿から、それは非常に単純に見えます。
次の場合、値のタイプ[構造体、列挙型]を使用します。
- インスタンスデータを==と比較することには意味があります
- コピーに独立した状態を持たせたい
- データは複数のスレッドにわたるコードで使用されます
次の場合は、参照タイプ[例:クラス]を使用します。
- インスタンスIDを===と比較することには意味があります
- 変更可能な共有状態を作成したい
その記事で述べたように、無書き込み可能なプロパティを持つクラスは、1つの警告(私が追加されます)と、構造体と同じように動作します:構造体は、最良のあるスレッドセーフなモデル -現代のアプリアーキテクチャでますます差し迫った必要条件。
継承を取得し、参照によって渡されるクラスでは、構造体には継承がなく、値によって渡されます。
Swiftには素晴らしいWWDCセッションがあり、この特定の質問はそのうちの1つで詳細に回答されています。言語ガイドやiBookよりもはるかに速くスピードアップできるので、これらを必ず確認してください。
構造体が提供する機能が少ないとは言えません。
確かに、変化する関数を除いて、自己は不変ですが、それだけです。
継承は、すべてのクラスが抽象または最終のいずれかでなければならないという古き良き考えに固執する限り、正常に機能します。
抽象クラスをプロトコルとして実装し、最終クラスを構造体として実装します。
構造体の良いところは、書き込み時のコピーがそれを処理するので、共有の可変状態を作成せずにフィールドを可変にすることができることです:)
そのため、次の例のプロパティ/フィールドはすべて可変であり、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)")
}
Swiftでは、プロトコル指向プログラミングと呼ばれる新しいプログラミングパターンが導入されました。
創造的パターン:
Swiftでは、Structは自動的に複製される値タイプです。したがって、プロトタイプパターンを無料で実装するために必要な動作が得られます。
一方、クラスは参照型であり、割り当て中に自動的に複製されません。プロトタイプパターンを実装するには、クラスでNSCopying
プロトコルを採用する必要があります。
浅いコピーは、それらのオブジェクトを指す参照のみを複製しますが、深いコピーはオブジェクトの参照を複製します。
参照タイプごとにディープコピーを実装するのは、面倒な作業になっています。クラスにさらに参照タイプが含まれている場合、各参照プロパティのプロトタイプパターンを実装する必要があります。そして、プロトコルを実装して、オブジェクトグラフ全体を実際にコピーする必要があります。NSCopying
class Contact{
var firstName:String
var lastName:String
var workAddress:Address // Reference type
}
class Address{
var street:String
...
}
構造体と列挙型を使用することで、コピーロジックを実装する必要がないため、コードが単純になりました。
多くのCocoa APIにはNSObjectサブクラスが必要であり、クラスを使用する必要があります。ただし、それ以外の場合は、AppleのSwiftブログにある次のケースを使用して、構造体/列挙型の値タイプとクラス参照タイプのどちらを使用するかを決定できます。
構造とクラスはユーザー定義のデータ型です
デフォルトでは、構造はパブリックですが、クラスはプライベートです
クラスはカプセル化の原則を実装します
クラスのオブジェクトはヒープメモリ上に作成されます
クラスは再利用性のために使用されますが、構造は同じ構造内のデータをグループ化するために使用されます
構造体データメンバーは直接初期化できませんが、構造体の外部から割り当てることができます
クラスデータメンバーは、パラメーターなしのコンストラクターによって直接初期化され、パラメーター化されたコンストラクターによって割り当てられます。