クラスはオブジェクト指向の根幹をなすもので、簡単に言うと変数と関数をまとめたものです。
Swiftでは、クラスは次の様に宣言します。
/* モンスタークラス */
class Monster {
var level = 1 // レベル
var name: String? // 名前
// 説明
func description() -> String {
if name == nil {
return "不明 Lv.\(level)"
} else {
return "\(name!) Lv.\(level)"
}
}
}
Swiftのクラス名は、次の様にアッパーキャメルケース(最初の文字とその後の区切りとなる単語の最初の文字は大文字)で記述することが推奨されています。また、プロパティやメソッドはローワーキャメルケース(最初の文字は小文字で、その後の区切りとなる単語の最初の文字は大文字)とすることが推奨されています。
/* プレイヤークラス */
class Player {
var hitPoint: Int
func attack() { ... }
:
}
/* 銀行口座クラス */
class BankAccount {
var amount: Int
func depositMoney(money: Int) { ... }
func withdrawMoney(money: Int) { ... }
:
}
インスタンスの生成
クラスはオブジェクトの内容を定義する設計図みたいなもので、実際に使用するには、クラスのインスタンス(オブジェクト)を生成する必要があります。
インスタンスを生成するには次のように、クラス名に()をつけて関数の様に呼び出します。
var monsterA = Monster()
インスタンスを生成したら、メンバ変数(クラスの変数をメンバ変数と呼びます。他にインスタンス変数と呼ぶこともあります。またプロパティと呼ぶこともあります。Objective-Cではインスタンス変数とプロパティは別の意味で扱われますが、Swiftでは区別しません。)やメソッド(クラスの関数をメソッドと呼びます。)にアクセスできます。
これらインスタンスの属性には、インスタンス名の後に.(ドット)をつけてアクセスします。
var monsterA = Monster()
print(monsterA.description()) // 不明 Lv.1
別のインスタンスを生成することもできます。
var slime = Monster()
slime.name = "スライム"
print(slime.description()) // スライム Lv.1
クラスのインスタンスは、変数に代入するとインスタンスそのものではなく、その参照が代人されます。その変数を別の変数に代入すると、1つのインスタンスを2つの変数で参照することになります。(参照カウントが2になる。)
そのため、一方の変数のプロパティを変更すると、別の変数でそのプロパティにアクセスした時も変更された後の値が参照されます。
var monsterA = Monster() // インスタンスの生成
monsterA.name = "スライム"
var monsterB = monsterA // 別の変数に代入
monsterB.name = "ドラゴン" // プロパティを変更
// どちらのモンスター名も同じ
print(monsterA.description()) // ドラゴン Lv.1
print(monsterB.description()) // ドラゴン Lv.1
クラスのインスタンスは上でみたように参照渡し(値そのものではなく、インスタンスへのアドレスが渡される)です。定数にインスタンスを代入した場合、参照先のインスタンスを変更することはできませんが、参照しているインスタンスのプロパティ自体は変更可能です。(値渡しの構造体とは動作が異なります。)
let monster = Monster()
slime.name = "スライム" // 定数のプロパティを変更してもエラーにならない
slime.name = "ドラゴン" // エラーにならない
クラスと異なり、構造体や列挙型は値渡しです。構造体のインスタンスを定数に代入した場合、プロパティは変更できません。
イニシャライザ
インスタンスの生成時に必ず実行されるメソッドを定義して、その中で変数の初期化を行うことができます。このメソッドをイニシャライザ、又はコンストラクタと呼びます。
イニシャライザは、initという名前のメソッドです。但しメソッド名の前にfuncを書く必要はありません。
/* モンスタークラス */
class Monster {
var level = 1 // レベル
let name: String // 名前
// イニシャライザ
init() {
self.name = "不明"
}
// 説明
func description() -> String {
return "\(name) Lv.\(level)"
}
}
let m = Monster()
print(m.description()) // 不明 Lv.1
selfは自分自身(インスタンス)を指し、そのインスタンスの属性であることを示すために使用しますが、上の場合はnameはメンバ変数であることが自明なので無くても構いません。
オプショナル型以外の変数は、上のように宣言時に値を設定するか、イニシャライザの中で初期値を与える必要があります。(初期値を与えないとコンパイルエラーになります。)
イニシャライザには次のように引数を与えることができます。引数はインスタンスの生成時に渡します。また、特に指定しない限りローカル引数名が外部名となります。
/* モンスタークラス */
class Monster {
var level: Int // レベル
let name: String // 名前
// イニシャライザ
init(name: String, level: Int = 1) {
self.name = name
self.level = level
}
// 説明
func description() -> String {
return "\(name) Lv.\(level)"
}
}
let slimeA = Monster(name: "スライム")
let slimeB = Monster(name: "スライム", level: 5)
print(slimeA.description()) // スライム Lv.1
print(slimeB.description()) // スライム Lv.5
上の場合、メンバ変数の名前とイニシャライザの引数の名前が同じなので、selfを使ってメンバ変数と引数を区別する必要があります。(selfをつけないと優先的に引数と認識されます。)
デイニシャライザ
生成されたインスタンスは、スコープを抜けたり、変数にnilや他のインスタンスが割り当てられるとメモリ上から破棄されます。この時、自動的に呼ばれるメソッドがあります。このメソッドのことをデイニシャライザ、又はデストラクタと呼びます。
デイニシャライザの中では必要に応じて、そのクラスで確保したリソースを破棄したり、開いたファイルを閉じる、通信を切断する等の後始末的な処理を記述します。
デイニシャライザは、deinitという名のメソッドです。deinitの後の()は不要です。引数を与えることはできません。
class FileHandler {
:
handle: Int
// イニシャライザ
init() {
:
self.handle = open(...)
}
// デイニシャライザ
deinit {
close(self.handle)
}
}
メンバ変数はインスタンスが破棄される時に自動的に破棄されるので、デイニシャライザの中で明示的に破棄する必要はありません。
ゲッターとセッター
メンバ変数としてなんらかの計算した値を返したり、メンバ変数を受け取る時になんらかの処理をすることができます。これらはそれぞれゲッター(getter)、セッター(setter)と呼ばれます。セッターを記述する時は、受け取る値はnewValueに設定されているのでこれを参照して計算等に使用することができます。記述例を次に示します。
/* モンスタークラス */
class Monster {
let name: String // 名前
var maxHitPoint: Int = 0 // 最大ヒットポイント
var hitPoint: Int = 0 // ヒットポイント
// レベル
var level: Int {
// 最大ヒットポイントはレベルに依存(下の計算は適当)
get { // ゲッター
return max(1, Int(sqrt(Float(maxHitPoint - 10))))
}
set { // セッター
maxHitPoint = 10 + newValue * newValue
}
}
// イニシャライザ
init(name: String, level: Int = 1) {
self.name = name
self.level = level
self.hitPoint = self.maxHitPoint
}
// 説明
func description() -> String {
return "\(name) Lv.\(level) HP(\(hitPoint)/\(maxHitPoint))"
}
}
let goblin = Monster(name: "ゴブリン", level: 10)
print(goblin.description()) // ゴブリン Lv.10 HP(110/110)
goblin.level = 20
print(goblin.description()) // ゴブリン Lv.20 HP(110/410)
newValueの代わりに、setの後に()で括って名前を渡して使うこともできます。
:
// レベル
var level: Int {
get {
return max(1, Int(sqrt(Float(maxHitPoint - 10))))
}
set(lv) {
maxHitPoint = 10 + lv * lv
}
}
:
同一インスタンスの判定
2つの変数が同じインスタンスを指しているかどうかは、=== 演算子を使って判定できます。ノットイコールを意味する!==もあります。
2つの変数が同じインスタンスを指しているかどうかは、==では判定できません。==は変数が指すインスタンス同士の内容が同じかどうかの判定に使用しますが、その実装内容はクラスの設計者に依存します。
var monsterA = Monster()
monsterA.name = "スライム"
var monsterB = monsterA
if monsterA === monsterB {
println("同じインスタンス")
}
if monsterA !== monsterB {
println("同じインスタンスではない")
}