6.

Swiftのクラスとメソッドの作り方|定義・init・呼び出し・継承

編集

Swiftでは、classキーワードでクラスを定義し、プロパティ・メソッド・イニシャライザ(init)をまとめ、let obj = クラス名()でインスタンスを生成してobj.メソッド名()の形でメソッドを呼び出します。クラスはデータ(プロパティ)とそれを操作する処理(メソッド)を1つにまとめるための仕組みで、設計図(クラス)から実体(インスタンス)を作って利用するのが基本的な流れです。

この記事の要点
  • クラスは class 名前 { } で定義し、中にプロパティ・メソッド・イニシャライザを書く。
  • プロパティは var(変更可)/ let(定数)で宣言し、インスタンスごとに値を持つ。
  • イニシャライザ init はインスタンス生成時に呼ばれ、プロパティの初期値を設定する。
  • メソッドは func で定義し、インスタンスを通じて obj.メソッド名(引数: 値) で呼び出す。
  • インスタンスは let obj = クラス名(引数: 値) で生成する。
  • メソッドやイニシャライザの内部では self で自分自身のインスタンスを参照できる。
  • クラスは継承でき、サブクラスは override でメソッドを上書きできる。
  • クラスは参照型、構造体(struct)は値型という重要な違いがある。

 

クラスを定義する

クラスは class キーワードに続けて名前を書き、波かっこ { } の中にメンバー(プロパティやメソッド)を記述します。クラス名は慣習として大文字始まり(アッパーキャメルケース)にします。次は、犬を表す Dog クラスを最小構成で定義した例です。

class Dog {  // クラスの定義

    // ここにプロパティやメソッドを書く

}

 

プロパティを持たせる

プロパティは、そのクラスのインスタンスが持つ「データ」です。クラスの内部で var(あとから変更可能)または let(変更不可の定数)として宣言します。プロパティに初期値を直接書いておくこともできますし、後述のイニシャライザで設定することもできます。

class Dog {

    var name = ""   // 文字列のプロパティ

    var age = 0      // 整数のプロパティ

}

 

イニシャライザ(init)で初期化する

イニシャライザはインスタンス生成時に自動で呼ばれる特別な処理で、init という名前で定義します。引数で受け取った値をプロパティに設定するのが典型的な使い方です。Swiftでは、宣言したすべての格納プロパティが初期化完了までに値を持つ必要があるため、初期値を直接書いていないプロパティは init 内で必ず設定します。引数名とプロパティ名が同じときは、self を付けて両者を区別します(後述)。

class Dog {

    var name: String

    var age: Int

 

    init(name: String, age: Int) {  // イニシャライザ

        self.name = name

        self.age = age

    }

}

 

メソッドを定義して呼び出す

メソッドは、クラスの内部で func キーワードを使って定義する関数です。引数や戻り値(-> の後ろに型)を持てます。メソッドの中からは、同じインスタンスのプロパティや別のメソッドにそのままアクセスできます。定義したメソッドは、インスタンス(実体)に対して インスタンス.メソッド名(引数ラベル: 値) の形で呼び出します。

class Dog {

    var name: String

 

    init(name: String) {

        self.name = name

    }

 

    func bark() -> String {  // メソッドの定義

        return name + "がワンと鳴いた"

    }

}

 

let dog = Dog(name: "ポチ")  // インスタンス生成

print(dog.bark())              // メソッド呼び出し

 

出力結果

ポチがワンと鳴いた

 

インスタンスを生成する

クラスはあくまで設計図であり、実際に使うにはインスタンス(実体)を作る必要があります。インスタンスは クラス名(引数ラベル: 値) という形で生成します。このかっこ呼び出しが、定義しておいたイニシャライザの呼び出しに対応します。生成したインスタンスは letvar の変数に入れて使います。

let dog1 = Dog(name: "ポチ")

let dog2 = Dog(name: "タロウ")  // 別のインスタンス

print(dog1.name)  // ポチ

print(dog2.name)  // タロウ

同じクラスから作ったインスタンスでも、それぞれが独立したプロパティの値を持ちます。dog1dog2 は別物として扱われ、一方の名前を変更してももう一方には影響しません。1つのクラス(設計図)から、状態の異なる実体をいくつでも作れるのがオブジェクト指向の基本的な利点です。なお、プロパティへのアクセスやメソッドの呼び出しは、いずれも インスタンス.メンバー名 という共通の書き方(ドット記法)で行います。

 

self とは

self は「メソッドやイニシャライザを実行している、そのインスタンス自身」を指すキーワードです。よく使うのは、引数名とプロパティ名が同じときに両者を区別する場面です。self.name はプロパティ、name は引数というように、self. を付けることでプロパティ側を明示できます。名前が重複していなければ self. は省略でき、name と書くだけでプロパティを参照できます。メソッドの内部から同じインスタンスの別のメソッドを呼ぶときも、self.別のメソッド() あるいは self. を省略してそのまま呼び出せます。最初のうちは、プロパティを設定する init の中では self. を付けておくと意図が読み取りやすくなります。

 

継承とオーバーライド

クラスは、既存のクラスを土台にして新しいクラスを作る「継承」ができます。class サブクラス名: 親クラス名 { } と書くと、親クラスのプロパティやメソッドを引き継ぎます。親のメソッドを作り直したいときは、override キーワードを付けて同名のメソッドを定義します。親側の実装を呼びたいときは super.メソッド名() を使います。

class Animal {

    func sound() -> String {

        return "…"

    }

}

 

class Cat: Animal {  // Animal を継承

    override func sound() -> String {  // 上書き

        return "ニャー"

    }

}

 

print(Cat().sound())  // ニャー

 

struct(値型)と class(参照型)の違い

Swiftにはクラスとよく似た「構造体(struct)」もあり、プロパティやメソッドを持てる点は同じです。最大の違いは、クラスが参照型、構造体が値型であることです。値型は変数へ代入したり関数へ渡したりするとコピーが作られ、コピー先を変更しても元には影響しません。参照型は同じインスタンスを指す参照が共有されるため、片方を通じた変更がもう片方にも及びます。Swiftでは、データの入れ物としてはまず構造体を検討し、継承や同一インスタンスの共有が必要な場合にクラスを選ぶ、という使い分けがよく推奨されます。

struct PointS { var x = 0 }  // 値型

class  PointC { var x = 0 }  // 参照型

 

var s1 = PointS(); var s2 = s1  // s2 はコピー

s2.x = 9

print(s1.x, s2.x)  // 0 9(元は変わらない)

 

let c1 = PointC(); let c2 = c1  // c2 は同じ実体への参照

c2.x = 9

print(c1.x, c2.x)  // 9 9(両方変わる)

 

つまずきやすい落とし穴

注意したいポイント
  • 参照型の共有による意図しない変更:クラスのインスタンスを別の変数や関数に渡しても複製されません。複数の箇所が同じ実体を参照するため、一方の変更が他方にも反映されます。コピーが欲しい場面では構造体の利用や明示的な複製を検討します。
  • プロパティの初期化漏れ:初期値を持たない格納プロパティは、すべてイニシャライザ内で値を設定しないとコンパイルエラーになります。「初期値を直接書く」か「init で設定する」のどちらかを必ず行います。
  • 構造体のメソッドで mutating を付け忘れる:構造体(struct)は値型のため、自身のプロパティを書き換えるメソッドには mutating を付ける必要があります(クラスでは不要)。付け忘れると変更できずエラーになります。
  • 呼び出し時の引数ラベル:Swiftのメソッド呼び出しでは、原則として引数ラベルを明示します(例:dog.move(to: 3))。定義時のラベルと一致しないと呼び出せません。

 

よくある質問(FAQ)

Q1. メソッドとプロパティの違いは何ですか?
プロパティはインスタンスが保持する「データ(値)」で、varlet で宣言します。メソッドはそのデータを使った「処理(ふるまい)」で、func で定義します。たとえば「名前」はプロパティ、「鳴く」はメソッド、というイメージです。

Q2. インスタンスを let で作っても、プロパティを書き換えられるのはなぜですか?
クラスは参照型のため、let が固定するのは「どのインスタンスを指すか(参照)」であり、インスタンス内部の var プロパティの値は変更できます。一方、構造体(値型)を let で宣言した場合は、内部のプロパティも変更できません。

Q3. クラスと構造体、どちらを使えばよいですか?
一般的には、まず構造体(struct)の利用を検討し、継承が必要な場合や、複数の箇所で同じインスタンスを共有・変更したい場合にクラスを選ぶ、という基準がよく用いられます。要件に応じて適切な方を選ぶとよいでしょう。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. print でデバッグ出力
  2. 変数の宣言
  3. 定数の宣言
  4. データ型
  5. 配列の宣言/追加/削除
  6. クラスとメソッドの作り方と呼び出し方
  7. 繰り返し制御
  8. if文による条件分岐
  9. エラー一覧

最近更新/作成されたページ