はじめに
今回はオブジェクト指向の考え方の1つである継承の使い方について紹介します。
継承がどういうものかはこちらで紹介しているので参照してください。
継承
定義の仕方
クラスの継承の構文以下となります。
class 子クラス: 親クラス {
}
具体的な実装方法を紹介します。
犬クラス、猫クラスを子クラスとし、親クラスとして動物クラスを継承しているとします。
この場合以下の実装となります。
class Animal {
}
class Dog: Animal {
}
class Cat: Animal {
}
継承していてもインスタンスの生成方法は変わりません。
let dog = Dog()
let cat = Cat()
メソッドを継承する
次にAnimalクラスにeat()メソッドを定義します。
class Animal {
func eat() {
print("もぐもぐ")
}
}
するとAnimalを継承したDog、Catはメソッドを定義しなくてもeat()を呼び出すことができます。
let dog = Dog()
let cat = Cat()
dog.eat()
cat.eat()
もぐもぐ
もぐもぐ
このように継承することで処理を共通化することができます。
またクラス内で呼び出す場合は自分自身のメソッドとして扱えるのでself.eat()で呼び出せます。
個別の処理を実装するにはそれぞれのクラスで実装します。
DogにsitDown()メソッドを定義します。
class Dog: Animal {
func sitDown() {
print("おすわり")
}
}
let dog = Dog()
dog.sitDown()
おすわり
これはDogクラスで定義されたメソッドなので当然Catでは呼び出せない処理になります。
let cat = Cat()
cat.sitDown() // error: Value of type 'Cat' has no member 'sitDown'
プロパティを継承する
プロパティもメソッド同様に継承されます。
class Animal {
var name: String
init(name: String) {
self.name = name
}
}
class Dog: Animal {
}
class Cat: Animal {
}
let dog = Dog(name: "ポチ")
let cat = Cat(name: "ミケ")
print(dog.name)
print(cat.name)
ポチ
ミケ
プロパティもメソッド同様それぞれの子クラスで定義したプロパティはそのクラスのオブジェクトしか持ちません。
オーバーライド
継承したメソッドは子クラスで上書きすることができます。
これをオーバーライドと言います。
オーバーライドの構文は以下となります。
override func 上書きするメソッド() {
}
メソッドの頭にoverrideを付けるだけです。
Animalのeat()メソッドをオーバーライドしてみます。
class Animal {
func eat() {
print("もぐもぐ")
}
}
class Dog: Animal {
override func eat() {
print("ドッグフードをモグモグ")
}
}
class Cat: Animal {
}
それぞれの子クラスからeat()を呼び出してみましょう。
let dog = Dog()
let cat = Cat()
dog.eat()
cat.eat()
ドッグフードをモグモグ
もぐもぐ
dog.eat()ではオーバライドした処理が、cat.eat()ではAnimalで定義した処理が実行されていることがわかります。
このようにメソッドを書き換えることができます。
ですがこれではあまり共通化した意味がないように思えます。
よくあるのはオーバーライドし、共通処理+クラスの独自処理というような実装です。
Animalを継承したHumanクラスを作成します。
Humanのeat()では食べる前に「いただきます」、食べ終わったら「ごちそうさまでした」というようにします。
class Human: Animal {
override func eat() {
print("いただきます")
super.eat()
print("ごちそうさまでした")
}
}
let human = Human()
human.eat()
いただきます
もぐもぐ
ごちそうさまでした
super.eat()で親クラスであるAnimalのeat()メソッドを呼び出しています。
自分自身を使うときはselfを使います。
一方でsuperは親クラスを表します。
このように共通処理に独自の処理を追加し、クラスを拡張していくことで効率的にコーディングしていきます。
イニシャライザ
継承した時のイニシャライザ(init)の定義の仕方は少し注意が必要です。
こういうものだと覚えてください。
継承していないときのイニシャライザは以下のように定義しますね。
class SuperClass {
var property: Int
init(property: Int) {
self.property = property
}
}
let object = SuperClass(property: 0)
SuperClassクラスを継承したSubClassをプロパティなしで定義します。
この場合SubClassにイニシャライザが不要なので特に意識することはありません。
class SubClass: SuperClass {
}
let subObject = SubClass(property: 0)
これは勝手にSuperClassのinit()が呼ばれます。
次はSubClassに新しくプロパティを追加します。
class SubClass: SuperClass {
var subProperty: Int
}
すると「Class 'SubClass' has no initializers」とイニシャライザがいないですよと怒られます。
これは継承していなくてもプロパティの初期値が決まっていないので発生するエラーです。
イニシャライザを追加してあげます。
class SubClass: SuperClass {
var subProperty: Int
init(subProperty: Int) {
self.subProperty = subProperty
}
}
これでいいかと思いきや「'super.init' isn't called on all paths before returning from initializer」とまだ怒られます。
イニシャライザの中でsuper.initが呼ばれていないと言われています。
親クラスのプロパティが初期化されていないため怒られています。
継承した場合、子クラスのイニシャライザの中で親クラスのイニシャライザも呼ばなければいけません。
子クラスのイニシャライズを以下のように修正します。
class SubClass: SuperClass {
var subProperty: Int
init(property: Int, subProperty: Int) {
self.subProperty = subProperty
super.init(property: property)
}
}
let subObject = SubClass(property: 0, subProperty: 0)
これでエラーが発生しなくなりました。
ここで注意が必要なのですが、super.initは子クラスのプロパティ全ての初期化が終わった後に呼び出してください。
先に親クラスをのイニシャライザを呼び出すとまた怒られます。
・プロパティは何かしらの値で初期化する必要がある
・子クラスのイニシャライザの最後に親クラスのイニシャライザを呼び出す必要がある
現状はこの2点を覚えておけば大丈夫です。
親クラスのselfでのメソッド呼び出し
以下のようにクラス定義します。
class SuperClass {
func testMethod1() {
print("SuperClass testMethod1 is called")
self.testMethod2()
}
func testMethod2() {
print("SuperClass testMethod2 is called")
}
}
このクラスのtestMethod1()を呼び出した結果はどうなるでしょう。
let object = SuperClass()
object.testMethod1()
SuperClass testMethod1 is called
SuperClass testMethod2 is called
testMethod1()の中でself.testMethod2()が呼ばれているのでそれぞれのメソッドが実行されていますね。
これは当然普段通りです。
では以下の場合はどうでしょうか。
class SuperClass {
func testMethod1() {
print("SuperClass testMethod1 is called")
self.testMethod2()
}
func testMethod2() {
print("SuperClass testMethod2 is called")
}
}
class SubClass: SuperClass {
override func testMethod1() {
super.testMethod1()
print("SubClass testMethod1 is called")
}
}
let subClass = SubClass()
subClass.testMethod1()
子クラスでtestMethod1()をオーバーライドし親クラスのtestMethod1()を呼んでいるので以下の結果となります。
SuperClass testMethod1 is called
SuperClass testMethod2 is called
SubClass testMethod1 is called
これもここまで説明してきた内容です。
では以下のようにtestMethod2()をオーバーライドするとどうなるでしょうか。
class SuperClass {
func testMethod1() {
print("SuperClass testMethod1 is called")
self.testMethod2()
}
func testMethod2() {
print("SuperClass testMethod2 is called")
}
}
class SubClass: SuperClass {
override func testMethod1() {
super.testMethod1()
print("SubClass testMethod1 is called")
}
override func testMethod2() {
print("SubClass testMethod2 is called")
}
}
let subClass = SubClass()
subClass.testMethod1()
これを実行すると以下のように出力されます。
SuperClass testMethod1 is called
SubClass testMethod2 is called
SubClass testMethod1 is called
親クラスで呼んでいるself.testMethod2()は子クラスのtestMethod2()を実行していることがわかります。
selfは自分自身のクラスではなくインスタンスを意味します。
つまりlet subClass = SubClass()で作成したsubClassを指します。
クラス定義としてはSuperClassとSubClassはそれぞれ別クラスですが、インスタンスはsubClassただ1つです。
親クラスでselfとしていてもそれはsubClassインスタンスを指します。
testMethod2()はオーバーライドされているので子クラスのtestMethod2()の処理を実行するため「SubClass testMethod2 is called」が表示されているというわけです。
以下のパターンも試してみます。
class SuperClass {
func testMethod1() {
print("SuperClass testMethod1 is called")
self.testMethod2()
}
func testMethod2() {
print("SuperClass testMethod2 is called")
}
}
class SubClass: SuperClass {
override func testMethod1() {
super.testMethod1()
print("SubClass testMethod1 is called")
}
override func testMethod2() {
super.testMethod2()
print("SubClass testMethod2 is called")
}
}
let subClass = SubClass()
subClass.testMethod1()
今度はSubClassのtestMethod2()でsuper.testMethod2()を呼んでいるので親クラスのtestMethod2()も呼ばれます。
SuperClass testMethod1 is called
SuperClass testMethod2 is called
SubClass testMethod2 is called
SubClass testMethod1 is called
このように意図しない挙動をする可能性ががあるのでメソッドがどのような順序で呼ばれるのかに注意した上で実装してください。
とはいえ最初のうちはそこまで気にすることはないと思うので頭に留めて置く程度でもいいかと思います。
最後に
今回はSwiftの継承の実装方法について紹介しました。
ですが少し難しい部分があったり、そもそもいつ継承を使うかなどはある程度自分で実装していき経験を積まなければ判断できないかと思います。
iOSアプリ開発では画面オブジェクトはUIViewControllerを継承し実装します。
そのため継承という考え方自体は押さえておく必要はありますが、UIViewControllerを親クラスとして使用するだけです。自分で親クラスを作るわけではありません。
このあたりはある程度パターン化されているので形として覚え、なんとなくこんなことをしてるのかなー程度がわかればいいかと思います。
今回の内容は以上です。
本記事とは別でプログラミング未経験からiOSアプリ開発が行えるようになることを目的とした記事を連載しています。
連載は以下にまとめていますのでそちらも是非もご覧ください。
http://naoyalog.com/