Edited at

プロパティまとめ(Swift)


プロパティとは

プロパティとは、型に紐づいた値のことです。

例えば、TVは色や機種、製造会社、画素数など多数の情報を持っています。この場合型はTV、それに関連するその他の情報をプロパティと言えます。

このようにクラスや構造体、列挙型に紐づく値のことをプロパティと呼びます。


基本的な定義

//Carが構造体名

struct Car {
 //再代入可
var プロパティ名: プロパティの型 = 式
//再代入不可
 let プロパティ名: プロパティの型 = 式
}

上記が基本的なプロパティの定義文になります。

このコードはCarという構造体の内部でプロパティが定義されている形となっているため、これらのプロパティはCar型のプロパティとなります。そしてvarで定義した値が再代入可能な値、letで代入した値が再代入不可なものになります。


サンプルコード

struct Something {

var variable = 123
let constant = 456
}

//インスタンス化
let something = Something()
let a = something.variable
let b = something.constant

//実行
print(a)
print(b)

//結果
123
456

上記のサンプルコードを見ると構造体Something内で定義した変数と定数を別の定数に代入する際に、インスタンス化したsomethingに対して、「.」の後にプロパティ名を指定しています。つまり構造体内で定義したプロパティを使用する場合、インスタンス化した構造体に対して、プロパティ名を「.」の後に指定する必要があるということです。

結果として、代入されたaとbをprintで実行することで結果が出力されました。


プロパティの種類


紐づく対象による分類

プロパティはその値がどこに紐づいているのかにより、2つに分類されます。

それを以下で紹介します。


インスタンスプロパティ

インスタンスプロパティとは先ほどのサンプルコードで定義したプロパティのことを指します。

通常単にプロパティと呼ぶときは、インスタンスプロパティのことを指します。

このインスタンスプロパティは型のインスタンスに紐づくため、インスタンスごとに異なる値を格納できます。そのため下記のコードを見ると、最初にインスタンス化した構造体と2回目にインスタンス化した構造体とでは、2つ目のインスタンスに値を再代入しているため、最終的な結果が異なります。これが先ほど言った異なる値を格納できるインスタンスプロパティの特性です。

//構造体定義

struct Greeting {
 プロパティ定義
var to = "Taro Yamada"
}

//構造体をインスタンス化1
let greeting1 = Greeting()
//構造体をインスタンス化2
let greeting2 = Greeting()
greeting2.to = "Hanako Yamada"

let to1 = greeting.to
let to2 = greeting2.to

print(to1)
Print(to2)

//結果
Taro Yamada
Hanako Yamada


スタティックプロパティ

スタティックプロパティは先ほどのインスタンスプロパティとは異なり、インスタンスではなく型そのものに紐づいているプロパティになります。

このプロパティはインスタンスに紐づいているわけではないため、各インスタンスから変更を加えることも出来ずどのインスタンスでも統一された値となります。そのため、下記コードを見ると別々で定義したインスタンスの実行結果でもスタティックインスタンスに関しては同一になります。

ちなみにこのスタティックプロパティの定義方法は構造体でのプロパティ定義時に文頭にstaicをつけることです。

※注意点としてはインスタンスプロパティの場合宣言さえすれば、インスタンス化後代入のタイミングがあるため、初期値を設定する必要はないが、スタティックプロパティはインスタンス化後代入が不可能なので宣言時に初期値を代入しないとコンパイルエラーになります。

//構造体定義

struct Greeting {
  //スタティックプロパティ定義
static let signature = "Sent form iPhone"

  //インスタンスプロパティ定義
var to = "Kankurou Nekoyashiki"
var body = "Hello!"
}

//関数定義
func print(greeting: Greeting) {
print("to: \(greeting.to)")
print("to: \(greeting.body)")
print("signature: \(Greeting.signature)")
}

let greeting1 = Greeting()
var greeting2 = Greeting()
greeting2.to = "Shinji Hamada"
greeting2.body = "Yeah!"

print(greeting: greeting1)
print("------------------")
print(greeting: greeting2)

//実行結果
to: Kankurou Nekoyashiki
to: Hello!
signature: Sent form iPhone
------------------
to: Shinji Hamada
to: Yeah!
signature: Sent form iPhone


値を保持するかしないかの分類


ストアドプロパティ

プロパティは値を保持するストアドプロパティと、値を保持しないコンピューテッドプロパティに分類されます。

このプロパティは先ほどのサンプルコードでも出てきた値を保持することが可能なプロパティのことを指します。そのためインスタンスプロパティとスタティックプロパティの双方ともに、このストアドプロパティに分類されることがあります。

下記のように値を保持可能はプロパティは全てこのストアドに分類されます。

struct SomeStruct {

var vaiable = 123 //再代入可能
 let constant = 456 //再代入不可
 static var staticVariable = 789 //再代入可、型自身に紐づく
static let staticVariable = 890 //再代入不可、型自身に紐づく
}


プロパティオブザーバ

プロパティオブザーバとはストアドプロパティに使える機能です。これはストアドプロパティの値の変更を監視し、変更前と変更後に処理を実行する際に使えます。

以下がプロパティオブザーバの定義文になります。下記は構造体内で定義したプロパティを表しており、いつものプロパティ定義文の後に中括弧を記述することでプロパティオブザーバが定義可能になります。

var プロパティ名 = 初期値 {

willSet {
プロパティ変更前に実行する文
変更後の値には定数newValueとしてアクセスできます。
}
didSet{
プロパティ変更後に実行する文
}
}

サンプルコード

//構造体定義

struct Greeting {
 //プロパティオブザーバ
var to = "Yosuke Ishikawa" {
  //変更前の処理
willSet {
print("willSet: (to: \(self.to), newValue: \(newValue))")
}
//変更後の処理
didSet {
print("didSet: (to: \(self.to))")
}
}
}

//インスタンス化
var greeting = Greeting()

//実行
greeting.to = "Yusei Nishiyama"

//結果
willSet: (to: Yosuke Ishikawa, newValue: Yusei Nishiyama)
didSet : (to: Yusei Nishiyama)

上記のようにwillSetの内部で変更前の処理、didSetの内部で変更後の処理を記述することで、実装できます。


レイジーストアドプロパティ

レイジーストアドプロパティとは、アクセスされるまで初期化を遅延させるストアドプロパティのことです。定義方法は文頭に「lazy」と記述することで、記述順序としては「static lazy var」と言う流れになります。

※注意点としては、lazyは再代入不可な「let」での定義では使用できないので注意してください。

lazy var インスタンスプロパティ名: プロパティの型 = 式

static lazy var インスタンスプロパティ名: プロパティの型 = 式

サンプルコード

struct SomeStruct {

var value: Int = {
print("valueの値を生成します")
return1
}()

lazy var lazyValue: Int = {
print("lazyValueの値を生成します")
return 2
}()
}

//実行
var someStruct = someStruct()
print("SomeStructをインスタンス化しました")
print("valueの値は\(someStruct.value)です")
print("lazyValueの値は\(someStruct.lazyValue)です")

//結果
valueの値は生成します
SomeStructをインスタンス化しました
valueの値は1です
lazyValueの値を生成します
lazyValueの値は2です

上記のコードのポイントは、初期化処理が行われた際にprint()関数が呼ばれるように仕込んだプロパティを定義している点です。そのため、プロパティの初期化時にprintでログが出力されるようになります。

その結果valueに対しては「valueの値を生成します」と言う文字列がインスタンス化した時に出力され、lazyValueに対しては「lazyValueの値を生成します」が値を呼び出された時に出力されています。これは通常インスタンス化した際に行われる初期化が、lazyValueの場合値を呼び出されてた時に初めて初期化したため、起こった現象になります。

以上がレイジーストアドプロパティの特性になります。


コンピューテッドプロパティ

コンピューテッドプロパティは、プロパティ自身では値を保存せず、すでに存在しているストアドプロパティなどから計算して値を返すプロパティです。このプロパティはアクセスするたびに計算し直すため、計算元の値との整合性が常に保たれているという性質があります。

このプロパティの定義には、varに続けてプロパティと型名を指定し、{}内にgetキーワードでゲッタを、setキーワードでセッタを指定します。ゲッタはプロパティの値を返す処理、セッタはプロパティの値を更新する処理です。

var プロパティ名: 型名 {

get {
return文によって値を返す処理
}
 set {
値を更新処理
  プロパティに代入された値には定数newValueとしてアクセスできる
}
}

文量が多いですが上記のコードで一つのプロパティということになります。

それでは、以下でゲッタとセッタについて解説していきます。

ゲッタ

ゲッタは、他のストアドプロパティなどから値を取得して、コンピューテッドプロパティの値として返す処理です。値の返却にはreturn文を使用します。

下記のコードを見るとストアドプロパティの初期値をコンピューテッドプロパティがgetで参照し、自らの値として結果に返しています。

これがゲッタの使用方法です。

struct Greeting {

 //ストアドプロパティ
var to = "Haru Suzumiya"
//コンピューテッドプロパティ
var body: String {
get {
return "Hello, \(to)!"
}
}
}

//インスタンス化
let greeting = Greeting()
//実行
greeting.body

//結果
Hello, Haru Suzumiya!

セッタ

セッタは、プロパティに代入された値を使用して、他のストアドプロパティなどを更新する処理です。

コンピューテッドプロパティに代入された値は暗黙的に宣言されたnewValueという定数を通じてセッタ内で扱われ、結果的にセッタで返された値が対象のストアドプロパティに代入されます。

下記のコードを見ると、最初の実行結果は初期値のまま実行しています。そのため、摂氏温度はそのまま出力され、華氏温度はgetで計算され、その返り値が出力されています。

次の実行結果はストアドプロパティを変更しているため、摂氏温度は変更された値が出力され、華氏温度は変更された値をgetで再計算された値が返り値として出力されています。

最後の実行結果はコンピューテッドプロパティが代入されたため、setでその値をnewValueとして計算し、摂氏温度に代入しています。そしてその値が摂氏温度として出力され、その値を再度getで計算して華氏温度として返り値を返しています。その結果最初にコンピューテッドプロパティに代入された値と、再計算して返した値が等しくなり整合性が取れる結果となりました。

stuct Temperature {

//摂氏温度
 var celsius: Double = 0.0

//華氏温度
var fahrenheit: Double {
get {
return (0.9 / 5.0) * celsius + 32.0
}
set {
celsius = (5.0 / 9.0) * (newValue - 32.0)
}
}
}

//初期値で出力
var temperature = Temperature()
temperature.celsius //0
temperature.fahrenheit //32

//ストアドプロパティを更新して、出力
temperature.celsius = 20
temperature.celsius //20
temperature.fahrenheit //68

//コンピューテッドプロパティを更新して出力
temperature.fahreheit = 32
temperature.celsius //0
temperature.fahrenheit //32

セッタの省略

コンピューテッドプロパティを使用する上でゲッタの定義は必須ですが、セッタの定義は任意です。そのため、セッタが存在しない場合は、getキーワードと{}を省略してゲッタが記述可能になります。

struct Greeting {

var to = "Taro Yamada"
var body: String {
return "Hello, \(to)!"
}
}

上記のように直接ストアドプロパティを参照して、値を返すことでゲッタの役割を果たします。


最後に

今回はプロパティについてざっとまとめてみました。

自己学習用なのでみにくい点あると思うので、その点ご了承ください!

最後まで読んでくれた方ありがとうございました!!!!!!!!!!!