見出し画像

Lesson2.3 Structures

Swiftには、数値、テキスト、コレクション、trueまたはfalseの値などのデータを表すための多くの便利な型が付属しています。しかし、アプリの構築に入ると、独自のデザインのプロパティと機能を使用して、独自のデータ型を作成したいと思うでしょう。

構造体を宣言することで、カスタムデータ型を作成します。構造体は、1つ以上の変数を1つの型に結合します。構造体に型メソッドとインスタンスメソッドを追加することで、機能を定義できます。

このレッスンでは、構造体の作成方法を学び、構造体がどのようにコードの構成要素を形成するかを発見します。

あなたが学ぶこと

・カスタム構造を作成する方法

・構造体のプロパティを定義する方法

・メソッドまたは関数を構造体に追加する方法

Vocabulary
computed property
function
initializer
initialization
instance method
memberwise initializer
method
property
self
structure
type

p134 
気づいていないかもしれませんが、このコースの初めから構造物に取り組んできました。
Swiftには、一般的なタイプのデータを表すための事前定義された構造が付属しています。プログラムやアプリの特定のニーズに合ったタイプが必要な場合は、独自の構造を定義できます。

最も単純な形式では、構造体は、型を構成する1つ以上のプロパティの名前付きグループです。プロパティは、構造体のインスタンスに関する情報を表します。

構造体は、構造体キーワードと一意の名前を使用して定義します。その後、適切な型注釈で定数または変数宣言をリストすることで、構造体の一部としてプロパティを定義できます。慣習は、型の名前を大文字にし、プロパティの名前に小文字を使用することです。

nameプロパティを持つPerson構造の単純な宣言を考えてみましょう。

“struct Person {
  var name: String

ドット構文を使用して、人の名前などのプロパティに保存されているデータにアクセスできます。

“let firstPerson = Person(name: "Jasmine")
print(firstPerson.name)

Console Output:
Jasmine
 

例の名前が示すように、プログラムの存続期間中に作成される人が複数いる可能性があります。次に作成するインスタンスは、st2Personと呼ぶことも、複数のPersonオブジェクトを保持するpersonというコレクションを作成することもできます。単一のPersonのみを作成することを期待する場合、SwiftではPersonという名前の人のインスタンスを作成できます。

let person = Person(name: "Zane")

メソッドを追加することで、構造体に機能を追加できます。メソッドは、特定の型に割り当てられる関数です。この例では、Personインスタンスが「こんにちは」と言えるようになりました。

“struct Person {
  var name: String
  func sayHello() {
    print("Hello, there! My name is \(name)!")
  }
}

ドット構文を使用してインスタンスメソッドを直接呼び出すことができるようになりました。

let person = Person(name: "Jasmine")
person.sayHello()
Console Output:
Hello, there! My name is Jasmine!

次のセクションでは、インスタンスとインスタンスメソッドについて詳しく学習します。

p.136

“struct Shirt {
  var size: Size
  var color: Color
}
 // Defines the attributes of a shirt. 
 
let myShirt = Shirt(size: .xl, color: .blue)
 // Creates an instance of an individual shirt.
let yourShirt = Shirt(size: .m, color: .red)
 // Creates a separate instance of an individual shirt. 

これは、今回は機能が追加された構造定義の別の例です。この構造は、Carオブジェクトの属性と機能を定義し、firstCarとstCarを2つのインスタンスとして記述します。

“struct Car {
  var make: String
  var year: Int
  var color: Color
  var topSpeed: Int
 
  func startEngine() {...}
 
  func drive() {}
 
  func park() {}
 
  func steer(direction: Direction) {}
}
 
let firstCar = Car(make: "Honda", year: 2010, color: .blue, 
topSpeed: 120) 
let secondCar = Car(make: "Ford", year: 2013, color: .black, “topSpeed: 125)
 
firstCar.startEngine()
firstCar.drive() 

このコードの車は何をしましたか?firstCarとstreセカンドカーが同じ私道の外を向いていると想像すると、firstCarはこのコードを実行した後にのみ移動しましたが、stretCarはエンジンも始動していません。

イニシャライザー
すべてのSwift型には初期化子が付属しています。これは、型の新しいインスタンスを返す関数に似ています。デフォルトの初期化子はinit()です。

デフォルトの初期化子から作成されたインスタンスにはデフォルト値があります。デフォルトの文字列は" "、デフォルトのIntは0、デフォルトのブール値はfalseです。

var string = String.init() // ""
var integer = Int.init() // 0
var bool = Bool.init() // false 

新しいタイプを定義するときはいつでも、新しいインスタンスを作成する方法を考慮する必要があります。このレッスンでは、プロパティ値を初期化するためのさまざまなアプローチについて説明します。

p.138
デフォルト値

新しいインスタンスの初期化中、Swiftではすべてのインスタンスプロパティの値を設定する必要があります。

1つのアプローチは、型定義にデフォルトのプロパティ値を提供することです。各インスタンスはその値で初期化されます。これは、走行距離計の0読み取りなど、一貫したデフォルト状態を持つオブジェクトを定義する場合に便利です。

前のセクションでは、String、Int、Boolのデフォルト値を見ました。デフォルトのプロパティ値を使用して、カスタムタイプの新しいインスタンスごとにデフォルトの状態を作成できます。

struct Odometer {
  var count: Int = 0
}
 
let odometer = Odometer()
print(odometer.count)
Console Output:
0

プロパティを宣言するとき、カウントはデフォルト値0に設定されていることに注意してください。Odometerのすべての新しいインスタンスは、そのデフォルト値で作成されます。

メンバーワイズイニシャライザー
デフォルト値以外の新しいインスタンスを作成したい場合があります。たとえば、以前の旅行または以前の所有者からの走行距離で走行距離計を初期化する必要があるかもしれません。
新しい構造体を定義すると、Swiftは構造体のすべてのプロパティを含むメンバーワイズイニシャライザと呼ばれる特別な初期化子を作成します。Memberwise初期化子を使用すると、新しいインスタンスの各プロパティの初期値を設定できます。

let odometer = Odometer(count: 27000)
print(odometer.count) 
Console Output:
27000 

メンバーワイズイニシャライザは、タイプの新しいインスタンスのデフォルト状態がない場合の正しいアプローチです。

nameプロパティを持つPerson構造を考えてみましょう。名前のデフォルト値として何を割り当てますか?

struct Person {
  var name: String

一見すると、名前のデフォルト値は""である可能性があると言えるかもしれません。しかし、空の文字列は名前ではなく、空の名前で誤って人を初期化したくありません。

Memberwise初期化子を呼び出すには、タイプ名の後に各プロパティに一致するパラメータを含む括弧を使用します。実際、このレッスンでは、さまざまなコードスニペットでメンバーごとのイニシャライザを見てきました。

“struct Person {
  var name: String
 
  func sayHello() {
    print("Hello, there!")
  }
}
 
let person = Person(name: "Jasmine") // Memberwise initializer
 
struct Shirt {
  var size: Size
  var color: Color 
}
 
let myShirt = Shirt(size: .xl, color: .blue) // Memberwise 
Initializer 
 
struct Car {
  var make: String
  var year: Int
  var color: Color
  var topSpeed: Int
}
 
let firstCar = Car(make: "Honda", year: 2010, color: .blue,
topSpeed: 120) // Memberwise initializer
 
p.140 
Memberwise初期化子は、カスタム構造の新しいインスタンスを作成する最も一般的な方法です。しかし、すべてのプロパティを割り当てる前に、いくつかのカスタムロジックを完了するイニシャライザを定義したい場合があります。そのような場合、カスタムイニシャライザを定義できます。

カスタムイニシャライザ
独自の初期化子を定義することで、初期化プロセスをカスタマイズできます。カスタム初期化子は、デフォルトおよびメンバーごとの初期化子と同じ要件を持っています。初期化を完了する前に、すべてのプロパティを初期値に設定する必要があります。

摂氏特性を持つ温度構造体を考えてみましょう。摂氏温度にアクセスできる場合は、メンバーごとのイニシャライザを使用して初期化できます。

struct Temperature {
  var celsius: Double
}
 
let temperature = Temperature(celsius: 30.0)

p.141
しかし、華氏の温度にアクセスできる場合は、メンバーワイズイニシャライザを使用する前に、その値を摂氏に変換する必要があります。

let fahrenheitValue = 98.6
let celsiusValue = (fahrenheitValue - 32) / 1.8
 
let temperature = Temperature(celsius: celsiusValue)
しかし、メンバーごとの初期化子では、新しい温度オブジェクトを初期化する前に摂氏値を計算する必要がありました。
代わりに、華氏値をパラメータとして受け取り、計算を実行し、値を摂氏プロパティに割り当てるカスタム初期化子を作成できます。

struct Temperature {
  var celsius: Double
 
  init(celsius: Double) {
    self.celsius = celsius
  }
 
  init(fahrenheit: Double) {
    celsius = (fahrenheit - 32) / 1.8
  }
}
 
let currentTemperature = Temperature(celsius: 18.5)
let boiling = Temperature(fahrenheit: 212.0)
 
print(currentTemperature.celsius)
print(boiling.celsius)
Console Output:
18.5
100.0 

前の例では、メンバーごとの初期化子があることに気付くかもしれません。型定義にカスタムイニシャライザを追加するときは、独自のメンバーごとのイニシャライザを定義する必要があります。Swiftはもはやあなたにイニシャライザを提供しません。

複数のカスタムイニシャライザを追加できます。以下のコードは、温度を再定義してケルビンの初期化子を追加します。

struct Temperature {
  var celsius: Double
 
  init(celsius: Double) {
    self.celsius - celsius
  }
 
  init(fahrenheit: Double) {
    celsius = (fahrenheit - 32) / 1.8
  }
 
  init(kelvin: Double) {
    celsius = kelvin - 273.15
  }
}
 
let currentTemperature = Temperature(celsius: 18.5)
let boiling = Temperature(fahrenheit: 212.0)
let freezing = Temperature(kelvin: 273.15)
 
print(currentTemperature.celsius)
print(boiling.celsius)
print(freezing.celsius)
Console Output:
18.5
100.0
0

温度の各インスタンスは、異なる初期化子と異なる値を使用して作成されますが、それぞれが必要な摂氏プロパティを持つ温度オブジェクトとして終わります。

インスタンスメソッド
インスタンスメソッドは、型の特定のインスタンスで呼び出すことができる関数です。
構造体のプロパティにアクセスして変更する方法を提供し、インスタンスの目的に関連する機能を追加します。

先ほど学習したように、型定義内に関数を追加してインスタンスメソッドを追加します。その後、その型のインスタンスでその関数を呼び出すことができます。
以前にPersonまたはCarの構造でこの構文に気づいたかもしれません。

幅と高さを乗算して特定のインスタンスの面積を計算するインスタンスメソッドrea()を持つSize構造体を考えてみましょう。

“struct Size {
  var width: Double
  var height: Double
 
  func area() -> Double {
    return width * height
  }
}
 
let someSize = Size(width: 10.0, height: 5.5)
let area = someSize.area() // Area is assigned a value of 55.0
 

someSizeインスタンスはSizeタイプで、幅と高さはそのプロパティです。Area() は、Size タイプのすべてのインスタンスで呼び出すことができるインスタンスメソッドです。

突然変異法
場合によっては、インスタンスメソッド内の構造体のプロパティ値を更新したいと思うことがあります。
そのためには、関数の前にミューティングキーワードを追加する必要があります。

p.144 
次の例では、単純な構造は、特定の車のオブジェクトに関する走行距離データを格納します。コードを見る前に、マイレージカウンターが保存する必要があるデータと実行する必要があるアクションを検討してください。

・走行距離計に表示する走行距離を保管する

・走行距離カウントを増やして、車が運転するときに走行距離を更新する

・車が走行距離計に表示できるマイル数を超えて運転する場合、走行距離カウントをリセットする可能性があります。

“struct Odometer {
  var count: Int = 0 // Assigns a default value to the `count` 
  property. 
 
  mutating func increment() {
    count += 1
  }
 
  mutating func increment(by amount: Int) {
    count += amount
  }
 
  mutating func reset() {
    count = 0
  }
}
 
var odometer = Odometer() // odometer.count defaults to 0
odometer.increment() // odometer.count is incremented to 1
odometer.increment(by: 15) // odometer.count is incremented to
16 
odometer.reset() // odometer.count is reset to 0

走行距離計インスタンスは走行距離計タイプで、increment()とincrement(by:)はインスタンスにマイルを追加するインスタンスメソッドです。Reset()インスタンスメソッドは、マイレージカウントをゼロにリセットします。

計算されたプロパティ
Swiftには、プロパティが計算値を返すロジックを実行できるようにする機能があります。

温度の例を考えてみましょう。世界の大部分は温度に摂氏スケールの測定を使用していますが、一部の場所(米国など)は華氏を使用し、特定の職業はケルビンを使用しています。したがって、温度構造が3つの測定システムすべてをサポートするのに役立つかもしれません。

struct Temperature {
  var celsius: Double
  var fahrenheit: Double
  var kelvin: Double
}

この構造にメンバーワイズ初期化子を使用したと想像してみてください。

let temperature = Temperature(celsius: 0, fahrenheit: 32.0,
kelvin: 273.15)

温度オブジェクトを初期化するコードを書くときはいつでも、各温度を計算し、それらの値をすべてパラメータとして渡す必要があります。

代替案は、計算を処理する複数の初期化子を追加することです。

struct Temperature {
  var celsius: Double
  var fahrenheit: Double
  var kelvin: Double

init(celsius: Double) {
    self.celsius = celsius
    fahrenheit = celsius * 1.8 + 32
    kelvin = celsius + 273.15
  }
 
  init(fahrenheit: Double) {
    self.fahrenheit = fahrenheit
    celsius = (fahrenheit - 32) / 1.8
    kelvin = celsius + 273.15
  }
 
  init(kelvin: Double) {
    self.kelvin = kelvin
    celsius = kelvin - 273.15
    fahrenheit = celsius * 1.8 + 32
  }
}
 
let currentTemperature = Temperature(celsius: 18.5)
let boiling = Temperature(fahrenheit: 212.0)
let freezing = Temperature(kelvin: 273.15)

p.146 
上記のアプローチでは、複数の初期化子を使用して、多くの状態または情報を管理することが含まれます。温度が変化するたびに、3つのプロパティをすべて更新する必要があります。
このアプローチはエラーが発生しやすく、どのプログラマーにとってもイライラするだろう。

Swiftはより安全なアプローチを提供します。計算されたプロパティを使用すると、他のインスタンスプロパティやロジックに基づいてその値を計算できるプロパティを作成できます。

struct Temperature {
  var celsius: Double
 var fahrenheit: Double {

“return celsius * 1.8 + 32
  }
 
  var kelvin: Double {
    return celsius + 273.15
  }
}
 

p.147 
計算されたプロパティを追加するには、プロパティを変数として宣言します(その値は変更される可能性があるため)。また、型を明示的に宣言する必要があります。次に、オープン中括弧 ({) と閉じる中括弧 (}) を使用して、返す値を計算するロジックを定義します。

他のプロパティと同様に、ドット構文を使用して計算されたプロパティにアクセスできます。

let currentTemperature = Temperature(celsius: 0.0)
print(currentTemperature.fahrenheit)
print(currentTemperature.kelvin)
Console Output:
32.0
273.15
 

計算されたプロパティに含まれるロジックは、プロパティにアクセスするたびに実行されるため、戻り値は常に最新になります。

プロパティオブザーバー
Swiftを使用すると、任意のプロパティを観察し、プロパティの値の変化に対応できます。これらのプロパティオブザーバーは、新しい値がプロパティの現在の値と同じであっても、プロパティの値が設定されるたびに呼び出されます。任意のプロパティで定義できる2つのオブザーバクロージャ、またはコードブロックがあります: willSetとdidSet

p.148

次の例では、StepCounterはtotalStepsプロパティで定義されています。willSetとidSetの両方のオブザーバーが定義されています。totalStepsが変更されるたびに、llSetが最初に呼び出され、newValueという名前の定数のプロパティ値に設定される新しい値にアクセスできます。プロパティの値が更新されると、didSetが呼び出され、oldValueを使用して以前のプロパティ値にアクセスできます。

“struct StepCounter {
    var totalSteps: Int = 0 {
        willSet {
            print("About to set totalSteps to \(newValue)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}
 

新しいStepCounterのtotalStepsプロパティを変更する際の出力は次のとおりです。

var stepCounter = StepCounter()
stepCounter.totalSteps = 40
stepCounter.totalSteps = 100
Console Output:
About to set totalSteps to 40
Added 40 steps
About to set totalSteps to 100
Added 60 steps

p.149 
型のプロパティとメソッド
インスタンスプロパティは型の個々のインスタンスに関するデータであり、インスタンスメソッドは型の個々のインスタンスで呼び出すことができる関数であることを学習しました。

Swiftは、型自体でアクセスまたは呼び出すことができる型のプロパティとメソッドの追加もサポートしています。Staticキーワードを使用して、プロパティまたはメソッドを型に追加します。

Typeプロパティは、プロパティがtypeに関連しているが、インスタンス自体の特性ではない場合に便利です。

次のサンプルは、すべての温度インスタンスの定数値である沸騰点と呼ばれる静的特性を持つ温度構造体を定義しています。

struct Temperature {
  static var boilingPoint = 100

型名のドット構文を使用して型プロパティにアクセスします。

let boilingPoint = Temperature.boilingPoint

Typeメソッドはtypeプロパティに似ています。アクションが型に関連しているが、型の特定のインスタンスが実行すべきものではない場合は、typeメソッドを使用します。

Swift標準ライブラリで定義されているDouble構造体には、2つのパラメータのうち小さい方を返すminimumと呼ばれる静的メソッドが含まれています。

“let smallerNumber = Double.minimum(100.0, -1000.0)

p.150
コピー
変数に構造体を割り当てるか、インスタンスをパラメータとして関数に渡すと、値はコピーされます。したがって、別々の変数は値の別々のインスタンスであり、一方の値を変更してももう一方の値が変更されないことを意味します。

var someSize = Size(width: 250, height: 1000)
var anotherSize = someSize
 
someSize.width = 500
 
print(someSize.width)
print(anotherSize.width)
Console Output:
500
250

someSizeのwidthプロパティは500の値に変更されましたが、 anotherSizeのwidthプロパティは変更されませんでした。なぜなら、 anotherSizeを someSizeに等しく設定しましたが、これは someSizeのコピーを作成し、元の幅が変更されたときにコピーの幅は変更されなかったためです。

Self
このセクションでは、自己という言葉に気づいたかもしれません。Swiftでは、selfはオブジェクトの現在のインスタンスを指します。インスタンスメソッド内で、オブジェクトの現在のインスタンスを指すために使用できます。

struct Car {
  var color: Color
 
  var description: String {
    return "This is a \(self.color) car.

}
}

多くの言語では、型定義内のインスタンスプロパティまたはメソッドを参照するためにselfを使用する必要があります。Swiftコンパイラは、現在のオブジェクトにプロパティ名またはメソッド名が存在する場合を認識し、selfの使用をオプションにします。

この例は、前の例と機能的に同じです。

struct Car {
  var color: Color
 
  var description: String {
    return "This is a \(color) car.
  }

Selfの使用は、プロパティ名に一致するパラメータ名を持つイニシャライザ内で必要です。


struct Temperature {
  var celsius: Double
 
  init(celsius: Double) {
    self.celsius = celsius
  }
}

この概念はシャドウイングと呼ばれ、今後のレッスンで詳しく学びます。

p.152 

変数プロパティ
このレッスンのすべての構造例では、定数の代わりに変数プロパティを使用していることに気づきましたか?レッスンの冒頭で使用した車の定義を考えてみましょう。

struct Car {
  var make: String
  var year: Int
  var color: Color
  var topSpeed: Int
}
 

車の色は塗装作業で簡単に変更でき、所有者がエンジンをアップグレードすると最高速度が更新される可能性があります。しかし、車の構成と年は決して変わらないので、 letを使用してプロパティを定義してみませんか?

変数プロパティは、古いデータから新しいデータを作成する便利な方法を提供します。次のコードでは、2010年のフォードを青にすることは、2010年のホンダのコピーを作成し、メイクプロパティを変更するのと同じくらい簡単です。Makeプロパティが一定の場合、メンバーワイズイニシャライザを使用して2台目の車を作成する必要があります。

var firstCar = Car(make: "Honda", year: 2010, color: .blue, 
topSpeed: 120)
var secondCar = firstCar
secondCar.make = "Ford"

このレッスンの前半では、構造体のインスタンスが新しい変数に割り当てられるたびに、値がコピーされることを学びました。
前の例では、ファーストカーはまだホンダです。firstCarのプロパティが変更されるのを明示的に防ぎたい場合は、レットを使用してインスタンスを宣言するだけです。

p.153
 let firstCar = Car(make: "Honda", year: 2010, color: .blue, 
topSpeed: 120)
firstCar.color = .red //Compiler error!
 

プロパティがvarを使用して宣言されていても、定数の値は変更できません。原則として、可能な限りletを使用して構造体のインスタンスを定義し、インスタンスを変更する必要がある場合はvarを使用し、構造体のプロパティを定義するときにvarを使用する必要があります。


 

この記事が気に入ったらサポートをしてみませんか?