2015-09-06 10 views
5

私はswiftのstdlibリファレンスで多くの情報を見ていません。たとえば、辞書には、削除のような特定のメソッドがインデックスを無効にすると言われていますが、それだけです。 「安全な」自分自身を呼び出すための言語についてはイテレータ/インデックスを無効にして使用すると、スウィフトコレクションの安全性はどれくらいですか?

、それは古典的なC++ footgunsへの解決策を必要とします:

  1. は、(ポインタが現在無効化された)複数の要素を追加し、ベクトルの要素へのポインタを取得し、今ポインタ、クラッシュを使用する

  2. コレクションを繰り返し開始します。反復中に、いくつかの要素を削除します(現在の反復子の位置の前後)。反復を続行し、クラッシュします。

: - 私は1を信じて

参照しながら、なぜならコレクションを格納クラスあれば迅速により解決される(編集がC++では、あなたはは幸運クラッシュをしているより悪い場合は、メモリの破損があります)(強いポインタなど)を要素に追加すると、参照カウントが増加します。しかし、私は2の答えは分かりません。

swiftで解決されているC++のfootgunsの比較があれば、非常に便利です。奪うの答えのために

EDIT:

および/またはループのための辞書と に行くいくつかの文書化されていないスナップショットのような振る舞いがあることを表示されません。このイテレーションは、起動時にスナップショット/隠しファイル のコピーを作成します。

私は大きな「WAT」と「クール」の両方を提供します。これは安全だと思います。「このコピーはどれくらいの費用がかかりますか?」

Generatorまたはfor-loopのいずれにも記載されていません。

以下のコードは、辞書の2つの論理スナップショットを出力します。最初の スナップショットは、反復ループの開始時点であったのでuserInfoであり、 はループ中に行われた変更を反映しません。

var userInfo: [String: String] = [ 
    "first_name" : "Andrei", 
    "last_name" : "Puni", 
    "job_title" : "Mad scientist" 
] 

userInfo["added_one"] = "1" // can modify because it's var 

print("first snapshot:") 
var hijacked = false 
for (key, value) in userInfo { 
    if !hijacked { 
     userInfo["added_two"] = "2" // doesn't error  
     userInfo.removeValueForKey("first_name") // doesn't error 
     hijacked = true 
    } 
    print("- \(key): \(value)") 
} 

userInfo["added_three"] = "3" // modify again 

print("final snapshot:") 
for (key, value) in userInfo { 
    print("- \(key): \(value)") 
} 

答えて

7

あなたが言うように、#1は問題ではありません。あなたはSwiftにオブジェクトへのポインタを持っていません。あなたはその価値かそれへの参照のいずれかを持っています。あなたがその価値を持っているなら、それはコピーです。参照がある場合、保護されます。だからここに問題はない。

しかし、2番目と実験を考えてみて、驚いて驚いてはいけません。

var xs = [1,2,3,4] 

for x in xs { // (1) 
    if x == 2 { 
     xs.removeAll() // (2) 
    } 
    print(x) // Prints "1\n2\n3\n\4\n" 
} 

xs // [] (3) 

待ち、我々は(2)で値を吹き飛ばす際にどのようにすべての値を印刷しません。私たちは今非常に驚いています。

しかし、そうではありません。スウィフト配列はです。 (1)のxsは値です。それを変えることはできません。これは、「4要素を含む配列構造を含むメモリへのポインタ」ではありません。それは[1,2,3,4]です。 (2)では、「xsが指し示すものからすべての要素を削除しません。「私たちはあなたのすべての要素を(つまり、すべてのケースで[]だろう)削除した場合の結果の配列を作成し、xsにその新しい配列を割り当て、である事はxsを取る。悪い何も起こりません。

だから何「?すべてのインデックスを無効にし、」ドキュメントがでどういう意味それはまさにそれを意味し、我々はインデックスを生成した場合、彼らはもうダメじゃないんだ見てみましょう:。。

var xs = [1,2,3,4] 

for i in xs.indices { 
    if i == 2 { 
     xs.removeAll() 
    } 
    print(xs[i]) // Prints "1\n2\n" and then CRASH!!! 
} 

xs.removeAll()が呼び出されると、古い結果という約束がありませんxs.indicesはもう何も意味するものではありません。これらのインデックスは、収集したコレクションに対して安全に使用することはできません。

Swiftの "Invalidates indices"は、C++の "イテレータを無効にする"と同じではありません。コレクションインデックスを使用することは常に危険なので、コレクションのインデックス付けを避ける必要があります。それを助けることができます。代わりにそれらを繰り返します。何らかの理由でインデックスが必要な場合でも、インデックスを作成することなく、インデックスを取得するにはenumerateを使用してください。

(サイドノート、dict["key"]dictにインデックス付けされていません。その鍵は自分のインデックスではありませんので、辞書は少し混乱している。彼らのDictionaryIndexインデックスで辞書にアクセスする彼らのIntインデックスによって配列にアクセスするのと同じくらい危険です。)

上記はNSArrayには当てはまりません。反復中にNSArrayを変更すると、「反復中に反復されたコレクション」エラーが発生します。私はSwiftデータ型についてのみ議論しています。


EDIT:for-inはそれがどのように動作するかにvery explicitある:

生成()メソッドは、その準拠タイプ、ジェネレータタイプでの値を得るために、収集式で呼び出されGeneratorTypeプロトコルに追加します。プログラムは、ストリーム上でnext()メソッドを呼び出すことによってループの実行を開始します。返された値がNoneでない場合、それは項目パターンに割り当てられ、プログラムはステートメントを実行してから、ループの最初で実行を続けます。そうでなければ、プログラムは代入を実行せず、ステートメントを実行しないため、for-inステートメントの実行が終了します。

返されたGeneratorは、structであり、コレクション値を含んでいます。その振る舞いを変更するために他の値を変更することは期待できません。覚えておいてください。[1,2,3]4と変わりません。それらは両方とも価値です。それらを割り当てると、コピーが作成されます。そのため、コレクション値を使ってジェネレータを作成すると、ジェネレータを4番に作成した場合と同じように、その値をスナップショットすることになります(これは、ジェネレータが本当に値ではないので、興味深い問題を引き起こします。 Swift stdlibはこれを修正しています。例えば、AnyGeneratorを参照してください。しかし、配列の値はまだ残っていますが、他の配列の値を変更することは決してありません。

"Structures and Enumerations Are Value Types"も参照してください。これは、Swiftの値タイプの重要性について詳しく説明しています。配列は単なる構造体です。

はい、論理的にコピーしています。 Swiftには、不要なときに実際のコピーを最小限に抑えるための最適化が多数用意されています。あなたのケースでは、辞書が反復されている間にその辞書を突然変異させると、それは強制的にコピーを起こします。特定の値のバッキングストレージの唯一の消費者であれば、変異は安いです。しかし、そうでない場合はO(n)です。長いスモール・コレクション:スウィフト・コレクションはコピー・オン・ライトであり、単純に配列を渡しても実際のメモリーは割り振られたりコピーされたりしません。これはスウィフトの組み込みによって決定されます(isUniquelyReferenced())。

あなたは自由のためのCOWを得ることはありません。独自の構造体はではなく、牛です。 Swiftがstdlibで行うことです。 (再作成する方法については、Mike Ashのgreat discussionを参照してください。)独自のカスタム構造体を渡すと、実際のコピーが作成されます。つまり、ほとんどの構造体のメモリの大部分はコレクションに格納されており、そのコレクションはCOWなので、構造体をコピーするコストは通常​​かなり低くなります。

本は(それはそれをすべて説明し、それだけで「ちょっと、これはそれが意味するものである」と言って保持しません)スウィフトの値のタイプにドリル多くの時間を費やすことはありません。一方、WWDCでは常に話題になっていました。特にこのトピックについては、Building Better Apps with Value Types in Swiftに興味があります。私はSwift in Practiceもそれについて議論していると信じています。


EDIT2:

@KarlPは、以下のコメントで興味深い点を提起し、それが取り組む価値があります。私たちが議論している価値安全性の約束はどれもfor-inに関連していません。それらはArrayに基づいています。 for-inは、コレクションが反復されている間にコレクションを変異させた場合に起こることについて何の約束もしていません。それは意味をなさないだろう。 for-inは「コレクションを繰り返し処理しません」Generatorsnext()を呼び出します。したがって、コレクションが変更された場合にGeneratorが未定義になると、Generatorが爆発して、for-inが爆発します。

func nukeFromOrbit<C: RangeReplaceableCollectionType>(var xs: C) { 
    var hijack = true 
    for x in xs { 
     if hijack { 
      xs.removeAll() 
      hijack = false 
     } 
     print(x) 
    } 
} 

そして、コンパイラはここであなたを助けにはなりません。以下は、あなたが仕様を読んでどのように厳密に応じて、安全ではないかもしれないということを意味

。すべてのSwiftコレクションでうまくいくでしょう。しかし、の突然変異の後にnext()を呼び出すと、コレクションが未定義の動作である場合、これは未定義の動作です。

私の意見は、そのGenerator、この場合に未定義になることができますコレクションを作るために貧しいスウィフトだろうということです。もしあなたがそうなら、Generator仕様を壊してしまったと主張することさえできます(ジェネレータがコピーされていないか、nilを返していない限り、UBは "出"しません)。だから、あなたは上記のコードは完全にスペック内であり、あなたのジェネレータは壊れていると主張することができます。これらの議論は、スウィフトのような "仕様"でちょっと乱雑になりがちですが、これはすべてのコーナーケースに浸透しません。これは、あなたが明確な警告を得ることなくスウィフトで危険なコードを書くことができます

を意味するのでしょうか?絶対に。しかし、実世界のバグを引き起こす多くの場合、Swiftの組み込みの動作は正しいことです。そして、それは他のいくつかのオプションよりも安全です。

+0

コードを実行したところ、出力が期待通りに見えます。 'popFirst()'はディクショナリ上で定義されていません(その順序では定義されていません)。しかし、確かに正しいことをしています。出力は良好に見えますが、エラーの欠落は正しいです。あなたはその考えを持っているように見えます。 –

+0

は前のコメントを無視します。私は、どこにも書かれていない「スナップショットみたいな」振る舞いで投稿を編集しました。あなたがこれについてより正確で公式な説明を提供できるなら、私はあなたにアップアップします。 – KarlP

+0

実際の理由は、* generator *は値型であり、ArrayまたはDictionaryは値型ではないということですか?あるいは、具体的なジェネレータ(IndexingGenerator、DictionaryGenerator)が構造体ですか? SequenceType *プロトコル*が 'generate()'が構造体を返すように強制することは私には分かりません。 (私はちょうど同じ問題を一度疑問に思ったので、私は不思議です:https://forums.developer.apple.com/thread/8551)。 –

関連する問題