コチラの記事は、【Swift】型の構成要素〜プロパティ前編〜の続きになります。
本記事を読んでも分からない箇所が多い方は一度前編をご覧ください。
では、前編の続きから参ります。
##ストアドプロパティ
プロパティは、値を保持するストアドプロパティと、
値を保持しないコンピューテッドプロパティの2種類に分類することもできます。
一つ注意していただきたいのが、
ストアドプロパティもコンピューテッドプロパティも値を保持するかどうかによる分類なので
インスタンスプロパティにもスタティックプロパティにもなり得ます。
まずは、ストアドプロパティについて説明していきます。
ストアドプロパティは、変数や定数のように値を代入して保存するプロパティです。
定義方法としては、変数や定数と同様にvarキーワードやletキーワードを使用します。
var インスタンスプロパティ名: プロパティの型 = 式
let インスタンスプロパティ名: プロパティの型 = 式
static var インスタンスプロパティ名: プロパティの型 = 式
static let インスタンスプロパティ名: プロパティの型 = 式
値を保持するプロパティなので上記のように記述すればOKですね!
実際のコードにするとこんな感じです。
struct Sample {
var sampleA = 123
let sampleB = 456
static var staticSampleA = 789
static let staticSampleB = 890
}
let sample = Sample()
sample.sampleA // 123
sample.sampleB // 456
Sample.staticSampleA // 789
Sample.staticSampleB // 890
###プロパティオブザーバ
プロパティオブザーバは、ストアドプロパティの値の変更を監視し、
変更前と変更後に文を実行するものです。
定義方法は、ストアドプロパティの定義の末尾に{ }を追加し、
willSetキーワードで変更前に実行する文を記述します。
また変更後に実行する文は、didSetキーワードで指定します。
var プロパティ名 = 初期値 {
willSet {
プロパティの変更前に実行される文
変更後の値には、"newValue"としてアクセスが可能
}
didSet {
プロパティの変更後に実行される文
}
}
willSetの時点では名前の通りストアドプロパティは更新されません。
その代わり、newValue
で新しい値にアクセスすることができます。
次のようなコードを書いてみました。
nameプロパティの値が変更されたら、
print("change name \(name) -> \(newValue)")
で現在の値と新しい値を出力します。
その後didSetで新しくなった現在の値を出力します。
struct Sample {
var name = "Saitou" {
willSet {
print("change name \(name) -> \(newValue)")
}
didSet {
print("now name value is \(name)")
}
}
}
var sample = Sample()
sample.name = "Takada"
実行結果
change name Saitou -> Takada
now name value is Takada
###レイジーストアドプロパティ
レイジーストアドプロパティは、アクセスされるまで初期化を遅延するプロパティです。
プロパティの前にlazyキーワード
を追加して定義します。
lazy var インスタンスプロパティ名: プロパティの型 = 式
static lazy var スタティックプロパティ名: プロパティの型 = 式
上のコードを見て察した通り、再代入不可能なプロパティでは使用することができません。
実際に初期化のタイミングを調べてみます。
方法としては初期化時にprint( )関数を仕込んだプロパティを記述し、
順にプロパティにアクセスしていきます。
struct Sample {
var value: Int = {
print("valueの値を生成します。")
return 1
}()
lazy var lazyValue: Int = {
print("lazyValueの値を生成します。")
return 2
}()
}
var sample = Sample()
print("Sampleをインスタンス化しました。")
print("valueの値は\(sample.value)です。")
print("lazyValueの値は\(sample.lazyValue)です。")
実行結果
valueの値を生成します。
Sampleをインスタンス化しました。
valueの値は1です。
lazyValueの値を生成します。
lazyValueの値は2です。
実行結果から、通常のプロパティはインスタンス化時に初期化が行われ、
レイジーストアドプロパティはアクセス時に初期化が行われていることがわかります。
実は、この遅延初期化が結構便利で、
コストの高いプロパティの初期化をアクセス時まで延ばし、
アプリケーションのパフォーマンスを向上させることができます、
また、通常ストアドプロパティの初期化時には、
他のプロパティやメソッドを利用することができません。
ですが、レイジーストアドプロパティの初期化はインスタンス作成よりも後に行われるため、
初期化時に他のプロパティやインスタンスにアクセスすることができます。
lazy var lazyValue = double(of: 10)
の部分で
lazyValueの初期化にはdouble(of:)メソッドを使用しています。
double(of:)メソッドは、引数の値を2倍にするメソッドです。
引数にvar value = 10
の値を渡しているので20が返ってきます。
struct Sample {
var value = 10
lazy var lazyValue = double(of: 10)
func double(of value: Int) -> Int {
value * 2
}
}
var sample = Sample()
sample.lazyValue // 20
##コンピューテッドプロパティ
コンピューテッドプロパティは、プロパティ自身では値を保存せず、
既存のストアドプロパティなどから計算して値を返すプロパティです。
コンピューテッドプロパティの利点は、アクセスごとに値を計算し直すため、
計算元の値との整合性が常に保たれるという点になります。
コンピューテッドプロパティは、varキーワードに続けてプロパティ名と型名を指定し、
{ }内にgetキーワードでゲッタを、setキーワードでセッタを定義します。
ゲッタはプロパティを返す処理で、
セッタはプロパティの値を更新する処理です。
var プロパティ名: 型名 {
get {
return分によって値を返す
}
set {
値を更新する
プロパティに代入された値にはnewValueでアクセスできる
}
}
###ゲッタ
ゲッタは他のストアドプロパティなどから値を取得して、
コンピューテッドプロパティの値として返す処理になります。
値を返却にはreturn文を用います。
ゲッタの例は下記のようになります。
valueプロパティはゲッタ内でストアドプロパティであるcountの値を返しています。
よって、値を保持しないコンピューテッドプロパティvalueにアクセスすると10が返ってきます。
また、sample.count += 10
でcountに+10した値を代入してからvalueにアクセスすると、
最新のvalueの値が返ってくるようになっています。
このように、整合性が常に保たれるのがゲッタの強みです。
struct Sample {
var count = 10
var value: Int {
get {
return count
}
}
}
var sample = Sample()
sample.value // 10
sample.count += 10
sample.value // 20
コンピューテッドプロパティではセッタの省略は可能ですが、ゲッタの省略はできません。
また、セッタを省略した場合はgetキーワードと{ }を省略して記述することが可能です。
ただ、getキーワードを省略するとぱっと見分からなくなりそうなので、
個人的には可読性を高めるために記述した方がいいのかなと思っています・・・。
(慣れれば大丈夫なのかな?)
ちなみに、ゲッタに値を代入しようとするとコンパイルエラーが発生します。
struct Sample {
var value: Int {
return 10
}
}
var sample = Sample()
sample.value = 10 // コンパイルエラー
エラー内容:Cannot assign to property: 'value' is a get-only property
和訳:プロパティに割り当てることができません: 'value'は取得専用プロパティです
###セッタ
セッタは、プロパティに代入された値を使用して、
他のストアドプロパティなどを更新する処理になります。
セッタ内では、暗黙的に宣言されたnewValueという定数を通じて、
代入された値にアクセスすることが可能です。
この値を使用して、他のプロパティの値を変更したりします。
下記に自動販売機の機能を一部コード化しました。
var money = 0
に記述されている通り、初期の合計金額は0円です。
お金を入れるとセッタ内のmoney += newValue
で合計金額がプラスされていきます。
sample.money
でゲッタ処理を行い、moneyの値を取得します。
struct Sample {
var money = 0
var addMoney: Int {
get {
return money
}
set {
money += newValue
}
}
}
var sample = Sample()
sample.money // 0
sample.addMoney = 100
sample.money // 100
sample.addMoney = 50
sample.money // 150
次に、ほんの少し難易度の上がったセッタの使い方です。
set(value) { }
とセッタを定義することにより、
代入された値をnewValueではなくvalueとして扱うことができます。
つまり、set( )
の( )内に名前を追加することで暗黙的に宣言されるnewValueではなく、
任意の名前を与えた定数を使用することができます。
また、セッタで必ず別のプロパティに値を入れる必要はないので、
switch value { }
で代入する値とそうでない値を仕分けています。
struct Sample {
var money = 0
var addMoney: Int {
get {
return money
}
set(value) {
switch value {
case 1, 5, 5000, 10000:
print("\(value)円は使えません")
case 10, 50, 100, 500, 1000:
print("\(value)円入れました。")
money += value
print("現在の合計金額は\(money)円です。")
default:
print("自動販売機でこのお金は使えません。")
}
}
}
}
var sample = Sample()
sample.addMoney = 10000
sample.addMoney = 500
sample.money // 500
実行結果
10000円は使えません
500円入れました。
現在の合計金額は500円です。
以上がプロパティの説明になります。
セッタやゲッタはプログラムの整合性の向上や安全面の向上に繋がると思うので、
ぜひ覚えて使用してみてください!
型の構成要素については別の記事でも詳しく記載していますのでぜひご覧ください!
・【Swift】型の構成要素〜型の基本〜
・【Swift】型の構成要素〜メソッド〜
・【Swift】型の構成要素〜イニシャライザ〜
・【Swift】型の構成要素〜サブスクリプト〜
・【Swift】型の構成要素〜エクステンション〜
・【Swift】型の構成要素〜型のネスト〜
最後までご覧いただきありがとうございました。