変数プロパティ
Swift におけるプロパティとは、要するに型を構成する要素の一つ。プロパティなんて一つしかないんじゃないかと思うかもしれないが、実際には Swift には以下の五つのプロパティが存在する。
- Stored
- Computed
- Instance
- Static
- Class
それぞれ何が違い、どのようにして利用するかを考えてみよう。
なお、執筆にあたり【Swift】プロパティについてのまとめを大変参考にさせていただきました。
プロパティの分類
- 再代入可能かどうか
- 値を保持するかどうか
- どこに値を保存するか
再代入可能かどうか
再代入可能かどうかでプロパティをlet
として定義するか、var
として定義するかが変わります。
let
は定数なので一度代入すると、その値を上書きすることはできません。
let
let price: Int = 100price = 200 // NG
var
var
は再代入可能ですが、var
を使っているのに再代入をしていないと「その変数はlet
で十分だよね」とコンパイラに警告されます。
var price: Int = 100price = 200 // OK
値を保持するかどうか
さて、次の分類は値を保持するかどうかです。
値を保持するプロパティをStored Property
、保持しないプロパティをComputed Property
といいます。
Stored
let price: Int = 100 // Stored
これは単なるStored Property
で、この書き方が一番馴染むという方が多いと思います。
let price: Int { willSet { // 処理 } didSet { // 処理 }}
Stored Property
はこのように値が書き換えられる直前と直後に何らかの処理を実行することができます。
Computed
一方、Computed Property
では値がどこかに保持されているわけではなく別のプロパティから値を計算して返すような場合に使います。
例えば、ある商品とその商品の税込みの値段を計算した場合を考えましょう。
class Item { var price: Int var taxIncludedPrice: Int
init(price: Int) { self.price = price self.taxIncludedPrice = Int(Double(price) * 1.1) }}
let apple = Item(price: 200)print(apple.taxIncludedPrice) // -> 220
これをStored Property
だけを使って実装すると上のようになります。
class Item { var price: Int var taxIncludedPrice: Int { get { Int(Double(self.price) * 1.1) } }
init(price: Int) { self.price = price }}
let apple = Item(price: 200)print(apple.taxIncludedPrice) // -> 220
これをComputed Property
を利用すると上のように書けます。要するに、税込価格は常に税抜価格の 1.1 倍なので税抜価格のデータにアクセスするたびに値段を計算して返すという仕様になっているわけです。
値を参照するごとに実際の値を計算するので計算プロパティ(Computed Property
)と呼ばれているわけです。注意点としては、計算に時間がかかるようなデータを計算プロパティに入れてしまうと、アクセスするたびに計算をしてしまうのでアプリが重くなる原因になります。
Stored Property
であれば一度値を計算すればその値をメモリに保存しておくので二回目以降は光速にアクセスできます。
Stored Property
ではwillSet
とdidSet
を使うことができましたが、Computed Property
ではget
とset
が利用できます。
- get その値を読み込んだときに、返すデータを計算する処理を記述する
- set その値にデータを代入したときに行う処理を記述する
さきほどのコードにはget
しか書かれていないため、taxIncludedPrice
に値を代入することはできません。これはlet
と似たような挙動をすることを意味します。
let apple = Item(price: 200)print(apple.taxIncludedPrice) // -> 220apple.taxIncludedPrice = 400 // Cannot assign to property: 'taxIncludedPrice' is get-only peoperty
これでも別に不満はないのですが、税込価格を入力すれば自動で税抜価格を計算し直してくれるようにしましょう。
class Item { var price: Int var taxIncludedPrice: Int { get { Int(Double(self.price) * 1.1) } set { self.price = Int(Double(newValue) / 1.1) } }
init(price: Int) { self.price = price }}
let apple = Item(price: 200)print(apple.price, apple.taxIncludedPrice) // -> 200, 220apple.taxIncludedPrice = 330print(apple.price, apple.taxIncludedPrice) // -> 300, 330
すると、taxIncludedPrice
に代入した時点でprice
の値が更新されました。
::: warning Computed Property について
他の言語の Computed Property と異なり、Swift では自分自身の値をどこかに保存しておくことはできません。
なので、Computed Property のsetter
は別の変数に値を保存するためにあります。
:::
どこに値を保存するか
Instance
Instance Property
はそのインスタンスが保持しているプロパティです。
class Item { var price: Int = 100}
let apple = Item()print(apple.price) // -> 100
なのでクラスや構造体を一度実体化(インスタンスを作成)しなければ利用することができません。
Static
Static Property
は型自身に保存されるプロパティです。
class Item { static var madeIn: String = "Japan"}
print(Item.madeIn)
Static Property
にすればインスタンス化せずに利用することができます。
Class
Class Property
はStatic Property
と同じく、インスタンス化せずに利用できるプロパティです。
どちらも利用方法はほとんど同じなのですが、Class Property
は軽傷クラスからオーバーライド(定義の上書き)が可能です。
また、Class Property
は必ずComputed Property
で宣言しなければいけません。
class Item { class var madeIn: String { "Japan" }}
class Apple: Item { override class var madeIn: String { "Yamanashi" }}
print(Apple.madeIn)
それぞれのプロパティの使い方
利用可能な組み合わせは以下の通り。
要するにClass Property, Stored Property
の組み合わせだけが利用できないだけで、後は全て使えます。
Instance | Static | Class | |
---|---|---|---|
Stored | OK | OK | - |
Computed | OK | OK | OK |
class Item { var priceA: Int = 100 // Stored + Instance var priceB: Int { // Computed + Inscance get { Int(priceA / 2) } set { priceA = Int(newValue * 2) } } static var priceC: Int = 150 // Stored + Static static var priceD: Int { // Computed + Static get { Int(priceC / 2) } set { priceC = Int(newValue * 2) } } class var priceE: Int { // Computed + Class get { Int(priceD / 2) } set { priceD = Int(newValue * 2) } }}
class Apple: Item { override class var priceE: Int { // Computed + Class get { Int(priceD / 3) } set { priceD = Int(newValue * 3) } }}
let apple = Apple()print(apple.priceA, apple.priceB) // -> 100, 50print(Apple.priceC, Apple.priceD, Apple.priceE) // -> 150, 75, 25
ここではItem
ではpriceE
の値はpriceD
の 1/2 と定義されているのですが、Apple
では 1/3 というように再定義しているので、
150/2/3=25
という値が返ってきています。
Stored/Computed
さて、ここでよくあるコーディングミスというか、もっと便利に書けるのに的なコードを紹介します。
class Item { var priceA: Int
init() { priceA = }}