web-dev-qa-db-ja.com

Swiftで変数をどの程度適切に宣言しますか?

Swiftで変数を宣言するこれらのさまざまな方法は非常に興味深いと思いました。

// METHOD 1
var dogName: String = "Charlie"

// METHOD 2
var dogName: String {
    return "Charlie"
}

// METHOD 3
let dogName = {
    return "Charlie"
}

// METHOD 4
var dogName: String = {
    return "Charlie"
}()

明らかに、メソッド3はletを宣言し、違いを知っています。しかし、なぜSwiftはメソッド4を許可するのですか?

これら4つの方法の違いは何ですか?

特に方法2と4の間でかなり混乱しています。さらに、方法3が方法4と比較して最後の括弧を失うのはなぜですか?

12

メソッド1は、文字列の標準変数宣言です。セッターとゲッターがあります

var dogName: String = "Charlie"

print(dogName) -> "Charlie"
dogName = "Rex" // Valid

方法2は、String型の計算されたプロパティであり、読み取り専用です。

var dogName: String {
    return "Charlie"
}

print(dogName) -> "Charlie"
dogName = "Rex" // Invalid as property is read-only

メソッド3は、タイプ()-> Stringの読み取り専用プロパティであるため、基本的にラムダ関数です。

let dogName = {
    return "Charlie"
}

print(dogName) -> "(Function)"
print(dogName()) -> "Charlie"
dogName = "Rex" // Invalid as property is read-only

方法4は、包含オブジェクトが初期化されたときに実行されるクロージャです。 varなので、別の値に置き換えることができます

var dogName: String = {
    return "Charlie"
}()

print(dogName) -> "Charlie"
dogName = "Rex" // Valid

そうは言っても、方法4はクロージャであるため、その中で他のコマンドを実行できます。この構成を使用してUILabelを初期化できる例を次に示します。

var dogNameLabel: UILabel = {
    let label = UILabel(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
    label.text = "Charlie"
    return label
}()
10
Thomas

違いを理解するために、より説明的な例を使用しましょう

これはクラスFooです

_class Foo {

    var className = "Foo"

    var dogName1 : String { return "Charlie " + className }

    let dogName2 = {
        return "My name is Charlie"
    }

    var dogName3 : String = {
        var string = "My"
        string += " name"
        string += " is"
        string += " Charlie"
        print(string)
        return string
    }()

}
_

それでは、インスタンスを作成しましょう

_let foo = Foo()
_
  • メソッド1は、setterとgetterを使用して保存されたプロパティclassNameです。

    _let name = foo.className
    foo.className = "Bar"
    print(foo.className) // "Bar"
    _
  • メソッド2は、ゲッターのみを使用して計算されたプロパティ_dogName1_です。値を動的に計算するために使用できます。

    _print(foo.dogName1) // "Charlie Bar"
    _
  • メソッドは、タイプ_dogName2_のクロージャー_() -> String_です。変数に割り当てて後で実行することができます

    _let dogName = foo.dogName2 // assigns the closure but does not return the string.
    print(dogName()) // "My name is Charlie"
    _
  • メソッド4は変数_dogName3_であり、すぐにクロージャを実行しますonceインスタンスFoo()が初期化されると(print行で証明されます) )

    _print(foo.dogName3) // "My name is Charlie" but does not execute the closure and print `string` again 
    _
  • メソッド5もあります:_dogName3_をlazyとして宣言した場合

    _lazy var dogName3 : String = {
    _

    変数が最初にアクセスされるまで、クロージャは実行されません。利点は、クロージャでselfを使用することもできることです。これは、方法4では不可能です。

2
vadian

クイックテストを設定し、すべての名前をdogName1dogName2dogName3、およびdogName4に変更しました。次に、それぞれの名前を「スヌーピー」に変更するコードを追加しました。

#2と#3はどちらも読み取り専用であることがコンパイラーにわかっていたため、ビルドされませんでした。 (#2は、varとして宣言されているにもかかわらず、常に「Charlie」を返すように設定されています。

これらの2行をコメントアウトした後、2つのブレークポイントを設定しました。1つは初期化後、もう1つは更新を試みた後です。

最後に、それぞれのprintを実行してみました。

ブレークポイント#1:#1と#4は「チャーリー」に設定され、#2は存在しない(初期化されていないため)、#3は初期化されているように見えますが、値がありません(まだ呼び出されていません。はい、最後の()はメモリ内の何かを初期化します。

ブレークポイント#2:#1と#4が「スヌーピー」に更新されました。

print#1と#4の結果は「スヌーピー」、#2は「チャーリー」、#3は「(関数)」でした。

結論:#1と#4の間に違いはありません。それぞれがvarと宣言され、デフォルトは「Charlie」です。 #2は、letのために読み取り専用であり、常に「Charlie」を返します。 #3?インスタンスを作成し、変更しようとしてもビルドされませんが、使用方法がわかりません。

#3についてさらに追加する必要がある場合は、この回答を更新します。

2
dfd

方法1は非常に明白です。

方法2では、指定された変数のゲッターを定義しました。

メソッド3のdogNameのタイプは() -> StringではなくString。ここで初期化したのは、名前付きラムダです。

メソッド4では、文字列を返す無名(名前なし)ラムダ関数を使用して変数を初期化します。

3と4の違いは、4番目に()を使用してその関数を呼び出すため、Stringを取得し、以前は取得しないため、関数であるということです。

1
Maroš Beťko