詳解Swift#5(Chapter8 クラス)
前回に続き、詳解Swiftの写経を続ける。
- 作者: 荻原剛志
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2015/12/25
- メディア: 単行本
- この商品を含むブログを見る
chapter8 クラス
クラス定義
構造体とは異なり、クラスでは 全項目イニシャライザを使えない(カプセル化の思想と相性悪)。
class Time { var hour = 0, min = 0 init(hour:Int, min:Int) { self.hour = hour; self.min = min } func add(min:Int) { // mutatingは不要 let m = self.min + min if m >= 60 { self.min = m % 60 let t = self.hour + m / 60 self.hour = t % 24 }else{ self.min = m } } func inc() { // mutatingは不要 self.add(1); } func toString() -> String { let h = hour < 10 ? " \(hour)":"\(hour)" let m = min < 10 ? "0\(min)":"\(min)" return h + ":" + m } } let t1 = Time(hour:13, min:20) let t2 = t1 print(t1.toString()) // 13:20 を出力 t1.inc() print(t2.toString()) // 13:21 を出力
- クラスの継承
class クラス名: スーパークラス名 { }
で書ける。
// Timeを継承、プロトコルを採用 class Time12 : Time, CustomStringConvertible { var pm:Bool // 新しいインスタンス変数。午後なら真 init(hour:Int, min:Int, pm:Bool) {// 新しいイニシャライザ self.pm = pm super.init(hour:hour, min:min) } // スーパークラスのイニシャライザを呼び出して使う override convenience init(hour:Int, min:Int) { // 24時制 let isPm = hour >= 12 self.init(hour:isPm ? hour - 12 : hour, min:min, pm:isPm) } // スーパークラスのイニシャライザを上書きする override func add(min:Int) { // メソッドを上書き super.add(min) // スーパークラスのメソッド while hour >= 12 { // hourはスーパークラスの定義 hour -= 12 pm = !pm } } var description : String {// スーパークラスのメソッドを利用 return toString() + " " + (pm ? "PM" : "AM") } }
convenience
は他のイニシャライザ(この例だと直上のイニシャライザ)に処理を委譲する場合に使う。 CustomStringConvertible
はプロトコル。 description
をつけて print(Time12型のインスタンス)
で標準出力を可能にする。
これを除くと書いてることは結構シンプル。
動的結合とキャスト
クラスメソッド / クラスプロパティ
class A : CustomStringConvertible { static var className : String { return "A" } // 計算型タイププロパティ static var total = 0 // 格納型タイププロパティ class var level : Int { return 1 } // 計算型クラスプロパティ static func point() -> Int { return 1000 } // タイプメソッド class func say() -> String { return "Ah." } // クラスメソッド init() { ++A.total } var description: String { return "\(A.total): \(A.className), " + "Level=\(A.level), \(A.point())pt, \(A.say())" } } class B : A { // override static var className : String { return "B" } // 定義不可. error: class var overrides a 'final' class var // static var total = 0 // 定義不可. error: cannot override with a stored property 'total' override class var level : Int { return 2 } // override static func point() -> Int { return 2000 } // 定義不可. error: class method overrides a 'final' class method override class func say() -> String { return "Boo" } override init() { super.init(); ++B.total } override var description: String { return "\(B.total): \(B.className), " + "Level=\(B.level), \(B.point())pt, \(B.say())" } } let a = A() print(a) let b = B() print(b)
こんな感じ。
クラスのイニシャライザ
Swiftでは、ベースクラスを継承して作られているクラスのインスタンスを正しくつくるために、イニシャライザにはいくつかのルールがある。
用語の話
- 指定イニシャライザ
- そのイニシャライザだけで、インスタンスの初期化が完了する
- 簡易イニシャライザ
- ほかのイニシャライザも使ってインスタンスの初期化をする
- ほかのイニシャライザを使って初期化を任せることをデリゲートと呼ぶ
convenience
というキーワードを付ける
どのクラスも指定イニシャライザを最低1つは備えている必要がある。
- インスタンスのイニシャライズの概念
これは↓の画像を見るのが一番理解が早い。
- 簡易イニシャライザ
- 指定イニシャライザ
- スーパークラスの指定イニシャライザ
- ...
という順序でイニシャライズされる。
イニシャライズ時の制約も結構ある。
- 指定イニシャライザはクラスで追加されている変数、定数の初期化をしなければいけない
- サブクラスのイニシャライザはスーパークラスのイニシャライズが終わるまではスーパークラスで定義されている変数、定数を扱うことはできない
簡易イニシャライザは指定イニシャライザの初期化処理が終わるまで変数、定数を扱うことはできない
継承しているクラスのイニシャライザのサンプル
// Zellers congruence func dayOfWeek(var m:Int, let _ d:Int, var year y:Int) -> Int { if m < 3 { m += 12; --y } let leap = y + y / 4 - y / 100 + y / 400 return (leap + (13 * m + 8) / 5 + d) % 7 } // base class class DayOfMonth : CustomStringConvertible { var month, day: Int init(month:Int, day:Int) { self.month = month; self.day = day } var description: String { return DayOfMonth.twoDigits(month) + "/" + DayOfMonth.twoDigits(day) } class func twoDigits(n:Int) -> String { let i = n % 100 if (i < 10) { return "0\(i)" } return "\(i)" } } // sub class1 class Date: DayOfMonth { var year: Int var dow: Int init(_ y:Int, _ m:Int, _ d:Int) { year = y dow = dayOfWeek(m, d, year: y) super.init(month: m, day: d) } } // sub class2 class DateW : Date { static let namesOfDay = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ] var dweek = String() // 指定イニシャライザ(スーパークラスのイニシャライザと引数が同じなのでoverrideする) override init(_ y:Int, _ m:Int, _ d:Int) { super.init(y, m, d) dweek = DateW.namesOfDay[dow] } // 簡易イニシャライザ convenience init(_ m: Int, _ d:Int, year:Int = 2016) { self.init(year, m, d) } // プロパティオブザーバ(didSetなので、dayの値が変更した直後に発動する) override var day:Int { didSet { dow = dayOfWeek(month, day, year:year) dweek = DateW.namesOfDay[dow] } } // printで呼ばれるプロパティの上書き(本質ではない) override var description: String { return "\(year)/" + super.description + " (\(dweek))" } } var d = DateW(1991, 5, 8) print(d, terminator:"") // 1991/05/08(Wed) d.day++ print(d, terminator:"") // 1991/05/09(Thu)
ここまで書いたところで、初期化についてのまとめ。
- パターン共通(すべてのケースに当てはまる)
- プロパティの値がすべて設定されるまで、処理はできない
- プロパティに初期値がある場合はイニシャライザで初期化しなくてもよい
- パターン1: スーパークラスがない場合
- イニシャライザで各プロパティの値を設定すればOK。シンプル
- パターン2: 同じクラスの別のイニシャライザを使う(デリゲートする)場合
- デリゲートするイニシャライザの呼出し後に変数の設定をし直せる
- パターン3: スーパークラスがある場合
- スーパークラスのイニシャライザを呼び出す前に自身のクラスで定義している変数・定数の初期化が済んでいる必要がある
少し端折ってるけど、こんな感じだと思います。
イニシャライザの継承
サブクラスで指定イニシャライザを定義していない
- サブクラスがスーパークラスの指定イニシャライザをすべて持っている
この2つの条件のどちらかを満たす場合にサブクラスでスーパークラスのイニシャライザを継承できるらしい。「2.」の意味がよくわからない。。
- 必須イニシャライザ
class ICCard { static let Deposit = 500 var money = 0 required init(charge:Int) { money = charge - ICCard.Deposit } } class Icocai : ICCard { static var serial = 0 let idnumber: Int init(id:Int, money:Int) { idnumber = id super.init(charge:money) } required init(charge:Int) { idnumber = ++Icocai.serial super.init(charge:charge) } } class Suiica : ICCard { var name = String() } var card = Suiica(charge:2000) print(card.money) // 1500 var mycard = Icocai(charge:2000) print(mycard.money) // 1500
required
なイニシャライザを持つクラスを継承したサブクラスは、同じイニシャライザの実装が必須となる。
- 失敗のあるイニシャライザ
指定イニシャライザの場合、すべての変数・定数の値が設定されるまで return nil
できないが、簡易イニシャライザの場合はいつでも return nil
できる。
通常、失敗のあるイニシャライザを使ってクラスのインスタンスを作るとオプショナル型になるが、構造体と同じく、 !
で開示すると、オプショナル型ではなくなる。
この辺りは基本的に構造体と同じ設計思想。
継承とサブクラスの定義
- プロパティの継承
スーパークラスのプロパティ(格納型、計算型どちらも)を継承して上書きできる。参照するときは super.プロパティ名
と書く。
- 継承とプロパティオブザーバ
スーパークラスのプロパティを監視できる。計算型、格納型の制限はない。
class Propα { var attr = 0 } class Propβ : Propα { override var attr : Int { willSet{ print("β: will set") } didSet{ print("β: did set") } } } class Propγ : Propβ { override var attr : Int { willSet{ print("γ: will set") } didSet{ print("γ: did set") } } } var g = Propγ() g.attr = 1
- 継承をさせない
サブクラスでの継承をさせたくないメソッド、プロパティは final
修飾子を定義の先頭に付ける。final
修飾子がついたメソッド、プロパティを継承/overrideしようとするとコンパイル時にエラーとなる。
- 継承と型推論 / クラスのメタタイプ
クラスAと、クラスAを継承したクラスBのそれぞれのインスタンスを含む配列をつくると、その配列の型はスーパークラスであるクラスAとなる。
class Hoge { var text: String init() { text = "hoge" } } class Fuga: Hoge { init(msg: String) { super.init() super.text = msg } } var h1 = Hoge() var f1 = Fuga(msg: "fuga") var array = [h1, f1] // [Hoge] array.dynamicType // Array<Hoge>.Type
xxxx.dynamicType
で実際に属するクラスを表すオブジェクトを返す。コレ欲しかったやつだ。
解放時処理
- Swiftではインスタンスは自動的に消去される
- クラスのインスタンスは参照型なので、Swiftが内部に持っているリファレンスカウンタで管理している
- いつ消去されるかは実行時までわからない
- 明示的に消すことで、ファイルのクローズやメモリの解放を行うことができる
解放処理を デイニシャライザ とよぶ。
deinit { }
で解放処理を書く。
遅延格納型プロパティ
初期化の際には値を決めずに、必要になったタイミングで初めて値を決めるプロパティのこと。
ex. 画像情報の読み込み - 画像は使われるかもしれないし、使われないかもしれない - パスだけは初期化時に指定しておく - 画像が必要になったタイミングで遅延読み込みを行うことで、画面初期化時の処理を減らせる
こんなイメージ。 lazy
キーワードをプロパティの宣言の先頭に書く。
import Foundation class FileAttr { let filename: String // 遅延格納型プロパティ lazy var size:Int = self.getFileSize() init(file: String) { filename = file } func getFileSize() -> Int { var buffer = stat() stat(filename, &buffer) print("[getFileSize]") return Int(buffer.st_size) } } let d = FileAttr(file:"/Users/kentana20/Desktop/363.png") print(d.filename) print(d.size) // getFileSize() が呼ばれる print(d.size)
- いくつかの制限がある
- クラス / 構造体定義の内部でのみ使用可能
- プロパティは定数ではなく変数とする
- 構造体の場合で、遅延評価を引き起こす可能性のあるメソッドには
mutating
が必要となる - プロパティオブザーバは指定できない
所感・雑感
クラス
イニシャライザ
- ひととおりの把握はしたものの、奥が深すぎて実戦経験積まないと手に馴染まなそう
- イニシャライザの継承は正直あまり使わずに(というか継承自体そんなに使わずに)
super.init
を基本形とするべきだと思った - イニシャライザはそんなにバンバン増やすものではないのでは。なるべく増やさないほうがインスタンスの見通しが明るくなりそうだと感じた。
継承
- オブジェクト指向言語の継承そのままといったイメージ
- クラス自体、プロパティ、メソッドの継承や
final
での制限など、すんなり使えそう - 継承の文脈ではないが、 Any, AnyObject 型はどれくらい使うのだろう。型に厳密なSwiftのよさをスポイルしてしまいそう。
解放処理
- ファイルI/O、メモリ大食いなどの処理を行うクラスについては
deinit
処理を明示的に書いていったほうがよい - ネイティブアプリのメモリ管理はシビアそうなので、チューニングやコーナーケースのデバッグなどではこの対応を行うケースがありそう
- ファイルI/O、メモリ大食いなどの処理を行うクラスについては
遅延格納型プロパティ
- 使えそう
- ネイティブアプリにおいては遅延評価をするとパフォーマンスを意識したプログラミングができそう
ほか
- ここまで読んで
xxxx.dynamicType
の存在を知った。ほっとした。
- ここまで読んで