web-dev-qa-db-ja.com

Swiftおよび変化する構造体

Swiftで値型を変更することに関して、私には完全に理解できないことがあります。

「Swiftプログラミング言語」iBookの状態:デフォルトでは、値メソッドのプロパティはインスタンスメソッド内から変更できません。

そのため、これを可能にするために、構造体と列挙型内でmutatingキーワードを使用してメソッドを宣言できます。

私にとって完全に明確ではないことはこれです:構造体の外部からvarを変更できますが、独自のメソッドからは変更できません。これは、オブジェクト指向言語のように、通常は変数をカプセル化して、内部からのみ変更できるようにするため、直感に反するように思えます。構造体では、これは逆のように見えます。詳しく説明するために、次のコードスニペットを示します。

struct Point {
    var x = 0, y = 0
    mutating func moveToX(x: Int, andY y:Int) { //Needs to be a mutating method in order to work
        self.x = x
        self.y = y
    }
}

var p = Point(x: 1, y: 2)
p.x = 3 //Works from outside the struct!
p.moveToX(5, andY: 5) 

構造体は自分のコンテキスト内からコンテンツを変更できないのに、コンテンツは他の場所で簡単に変更できる理由を誰もが知っていますか?

85
Mike Seghers

可変属性は、タイプではなくストレージ(定数または変数)でマークされます。 structにはmutableimmutableの2つのモードがあると考えることができます。構造体の値を不変のストレージに割り当てると(Swiftではletまたはconstantと呼ばれます)、値は不変モードになり、値の状態を変更することはできません。 (変更メソッドの呼び出しを含む)

値が可変ストレージに割り当てられている場合(Swiftではvarまたはvariableと呼ばれます)、それらの状態を自由に変更でき、変更メソッドの呼び出しが許可されます。

さらに、クラスにはこの不変/可変モードがありません。 IMO、これは、クラスが通常reference-ableエンティティを表すために使用されるためです。また、参照可能なエンティティは、適切なパフォーマンスで不変の方法でエンティティの参照グラフを作成および管理するのが非常に難しいため、通常は可変です。彼らはこの機能を後で追加するかもしれませんが、少なくとも今はそうではありません。

Objective-Cプログラマーにとって、可変/不変の概念は非常によく知られています。 Objective-Cでは、概念ごとに2つの別々のクラスがありましたが、Swiftでは、1つの構造体でこれを行うことができます。半分の仕事。

C/C++プログラマーにとっても、これは非常に馴染みのある概念です。これは、C/C++でconstキーワードが行うこととまったく同じです。

また、不変の値は非常にうまく最適化できます。理論的には、Swiftコンパイラー(またはLLVM)は、C++と同様に、letによって渡された値に対してcopy-elisionを実行できます。不変の構造体を賢く使用すると、refcountedクラスよりもパフォーマンスが向上します。

更新

@Josephがwhyを提供しないと主張したように、もう少し追加します。

構造体には2種類のメソッドがあります。 plainおよびmutatingメソッド。 Plainメソッドは不変(または不変)を意味します。この分離は、immutableセマンティクスをサポートするためにのみ存在します。不変モードのオブジェクトは、その状態をまったく変更すべきではありません。

そして、不変のメソッドはこれを保証する必要がありますsemantic immutability。つまり、内部値を変更してはなりません。そのため、コンパイラは不変メソッドでの自身の状態変更を許可しません。対照的に、変更メソッドは状態を自由に変更できます。

そして、あなたはなぜ不変がデフォルトなのか?の質問があるかもしれません。これは、値の変化の将来の状態を予測するのが非常に難しく、通常それが頭痛やバグの主な原因になるからです。多くの人々は、解決策が変更可能なものを回避していることに同意し、C-C++ファミリー言語とその派生物では、何十年もデフォルトで不変がウィッシュリストのトップにありました。

詳細については、 純粋に機能的なスタイル を参照してください。とにかく、不変のものにはいくつかの弱点があるため、私たちはまだ変更可能なものが必要です。これについて議論することは話題外のようです。

これがお役に立てば幸いです。

74
Eonil

構造はフィールドの集合です。特定の構造インスタンスが可変である場合、そのフィールドは可変です。インスタンスが不変である場合、そのフィールドは不変です。したがって、特定のインスタンスのフィールドが可変または不変である可能性に備えて、構造型を準備する必要があります。

構造メソッドが基礎となる構造のフィールドを変更するためには、それらのフィールドは変更可能でなければなりません。基礎となる構造体のフィールドを変更するメソッドが不変の構造で呼び出されると、不変のフィールドを変更しようとします。良いものは何も得られないので、そのような呼び出しは禁止する必要があります。

それを実現するために、Swiftは構造メソッドを2つのカテゴリに分割します。基礎となる構造を変更するため、変更可能な構造インスタンスでのみ呼び出すことができるメソッドと、基礎となる構造を変更しないため呼び出し可能なメソッド可変インスタンスと不変インスタンスの両方。後者の使用はおそらくより頻繁であり、したがってデフォルトです。

比較すると、現在(まだ!)は、構造を変更する構造メソッドとそうでない構造メソッドを区別する手段を提供していません。代わりに、不変の構造体インスタンスで構造体メソッドを呼び出すと、コンパイラは構造体インスタンスの可変コピーを作成し、メソッドに必要な処理を行わせ、メソッドが完了したらコピーを破棄します。これは、コピー操作を追加しても意味的に正しくないコードが意味的に正しいコードに変わることはほとんどありませんが、メソッドを変更するかどうかに関係なく、コンパイラーに構造のコピーに時間を浪費させます。ある意味で意味的に間違っているコード(「不変」の値を変更する)を別の方法で間違っている(コードが構造を変更していると考えられるが、試みられた変更は破棄する)だけです。構造体メソッドが基礎となる構造を変更するかどうかを示すことができるようにすることで、無駄なコピー操作の必要性を排除し、誤った使用の試みにフラグを立てることができます。

24
supercat

注意:素人の用語が先にあります。

この説明は、最も重要なコードレベルでは厳密には正しくありません。しかし、実際にSwiftに取り組んでいる人によってレビューされており、基本的な説明としては十分だと彼は言いました。

それで、私は単純かつ直接的に「なぜ」の質問に答えようとします。

正確には:キーワードを変更せずに構造体パラメーターを変更できるのに、構造体関数をmutatingとしてマークする必要があるのはなぜですか?

つまり、全体像は、Swift Swift。を維持するという哲学に大きく関係しています。

実際の物理アドレスを管理する問題のように考えることができます。住所を変更するときに、現在の住所を持っている人が多い場合は、引っ越したことを全員に通知する必要があります。しかし、誰もあなたの現在の住所を持っていない場合、あなたはどこでも好きな場所に移動でき、誰も知る必要はありません。

この状況では、Swiftは郵便局のようなものです。多くの連絡先を持つ多くの人々が頻繁に移動すると、オーバーヘッドが非常に高くなります。これらすべての通知を処理するために多くの人員を支払う必要があり、プロセスには多くの時間と労力がかかります。そのため、Swiftの理想的な状態は、町のすべての人ができるだけ少ない連絡先を持つことです。そうすれば、アドレスの変更を処理するために大きなスタッフを必要とせず、他のすべてをより速く、より良く行うことができます。

これはまた、Swiftの人々が値型と参照型について熱心に取り組んでいる理由でもあります。本質的に、参照型は至る所に「連絡先」を集めており、通常、値型は数個を超える必要はありません。値のタイプは「Swift」です。

小さい画像に戻ります:structs。 Swiftでは、オブジェクトができることのほとんどを実行できるため、構造体は大したことですが、値型です。

misterStructにあるsomeObjectVilleを想像して、物理アドレスの類推を続けましょう。この例えはここで少しうんざりしますが、それでもまだ役に立つと思います。

したがって、structの変数の変更をモデル化するには、misterStructに緑の髪があり、青の髪に切り替える命令を取得したとします。私が言ったように、類推はまばたきしますが、何が起こるかというと、misterStructの髪の毛を変える代わりに、古い人が出て、青い髪の新しい人が動きます。新しい人は自分をmisterStructと呼び始めます。誰もアドレス変更通知を受け取る必要はありませんが、だれかがそのアドレスを見ると、青い髪の男が見えます。

structで関数を呼び出すとどうなるかをモデル化しましょう。この場合、misterStructchangeYourHairBlue()などの順序を取得します。そのため、郵便局はmisterStruct「髪を青に変えて、終わったら教えて」と指示を出します。

彼が以前と同じルーチンに従っている場合、変数が直接変更されたときに彼がしたことをしている場合、misterStructがすることは自分の家から出るで、新しい人を呼び出す青い髪。しかし、それが問題です。

注文は「髪を青に変えて、完了したら教えてください」でしたが、その注文を受け取ったのはgreenの男です。青い男が入ってきた後も、「ジョブ完了」通知を送り返す必要があります。 しかし、青い男はそれについて何も知らない。

[この類推をひどくひどくするために、緑髪の男に技術的に起こったのは、彼が引っ越した後、彼はすぐに自殺した。だからheは通知できないタスクが完了している人either!]

この問題を回避するには、このような場合only、Swiftはその住所の家に直接入る必要があり、現在の住民の髪の毛を実際に変更する 。これは、新しい人を派遣することとはまったく異なるプロセスです。

そして、それがSwiftがmutatingキーワードの使用を望んでいる理由です!

最終結果は、構造体を参照する必要があるものと同じに見えます。家の住人は青い髪になりました。しかし、それを達成するためのプロセスは実際には完全に異なります。同じことをしているように見えますが、非常に異なることをしています。それはSwift構造体は一般に決してしないことをしています。

そのため、貧弱なコンパイラに少し助けを与え、関数がstructを変更するかどうかを独自に判断する必要がないように、これまでのすべてのstruct関数について同情してmutatingキーワードを使用するよう求められます。

本質的に、SwiftがSwiftを維持できるようにするために、私たち全員が自分の役割を果たさなければなりません。 :)

編集:

私を落胆させたちょっとおい/ダデット、私はただ完全に私の答えを書き直した。それがあなたとうまく座っている場合、あなたは下票を削除しますか?

11
Le Mot Juiced

Swift構造体は、定数(let経由)または変数(var経由)としてインスタンス化できます。

SwiftのArray構造体を検討してください(はい、それは構造体です)。

var petNames: [String] = ["Ruff", "Garfield", "Nemo"]
petNames.append("Harvey") // ["Ruff", "Garfield", "Nemo", "Harvey"]

let planetNames: [String] = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
planetNames.append("Pluto") //Error, sorry Pluto. No can do

アペンドが惑星名で機能しなかったのはなぜですか? appendはmutatingキーワードでマークされているためです。 planetNamesletを使用して宣言されているため、このようにマークされたすべてのメソッドは立ち入り禁止です。

あなたの例では、コンパイラはinitの外部の1つ以上のプロパティに割り当てることで、構造体を変更していることを通知できます。コードを少し変更すると、xyが構造体の外から常にアクセスできるとは限らないことがわかります。最初の行のletに注意してください。

let p = Point(x: 1, y: 2)
p.x = 3 //error
p.moveToX(5, andY: 5) //error
7
Lance

C++との類推を検討してください。 Swiftがmutating/not -mutatingであるstructメソッドは、C++がconst/constでないメソッドに類似しています。 C++でconstとマークされたメソッドは、同様に構造体を変更できません。

Varは構造体の外部から変更できますが、独自のメソッドからは変更できません。

C++では、「構造体の外部からvarを変更」することもできますが、非const構造体変数がある場合はonlyです。 const構造体変数がある場合、varに割り当てることはできません。また、const以外のメソッドを呼び出すこともできません。同様に、Swiftでは、構造体変数が定数でない場合にのみ、構造体のプロパティを変更できます。構造体定数がある場合、プロパティに割り当てることができず、mutatingメソッドを呼び出すこともできません。

5
newacct

Swiftを学習し始めたとき、私は同じことを疑問に思いました。これらの答えはそれぞれ、おそらくいくつかの洞察を加えながら、それ自体が一種の冗長でわかりにくいものです。あなたの質問への答えは実際にはかなり簡単だと思います…

定義された変更メソッドinside構造体は、将来作成されるすべてのインスタンス自体を変更する許可を必要とします。それらのインスタンスの1つがletで不変定数に割り当てられたらどうなりますか?ええとああ。自分自身からあなたを守るために(そしてエディターとコンパイラーにあなたが何をしているのかを知らせるためにtryingすること)、インスタンスメソッドにこの種の力を与えたいとき、あなたは明示的にせざるを得ません。

対照的に、outsideからのプロパティの設定は、その構造の既知のインスタンスで動作しています。定数に割り当てられている場合、Xcodeはメソッド呼び出しで入力した瞬間にそのことを知らせます。

これは、Swiftをもっと使い始めてから、私が愛しているものの1つです。エラーを入力するたびにアラートが出されます。 JavaScriptの不明瞭なバグのトラブルシューティングで、確実に成功します!

0
Kal