Swift/クラス †概要 †クラスのインスタンスは参照型である。 すなわち、変数に代入したりメソッドの引数に渡す時に、コピーが作られるのではなく、同一のインスタンスへの参照が渡される。 class 型名 [: スーパークラス] { [変数/定数定義] [イニシャライザ定義] [メソッド定義] [その他の定義] } クラスでは memberwise initializer は利用できない。 内部の変数の名前を指定して初期値を与えるのは、オブジェクト指向の情報隠蔽 (encapsulation) を台無しにしてしまうので。 クラス定義にイニシャライザがない場合、「クラス名()」という記法で default initializer でインスタンスを生成できるが、すべてのインスタンスプロパティに初期値が設定されている必要がある。 class Time { var hour = 0, min = 0 init(hour:Int, min:Int) { self.hour = hour; self.min = min } func add(min:Int) { 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 } } } let t1 = Time(hour:13, min:20) let t2 = t1 print(t1.toString()) // 13:20 と表示される ti.inc() print(t2.toString()) // 13:21 と表示される 構造体の定義ではメンバ変数の値を変更するメソッドには mutating という修飾子をつけたが、 クラスの定義では必要ない。 クラスのインスタンスは、定数と変数のどちらに格納されていても、格納型プロパティの変更が可能である。
クラスの継承 †ルートクラスは NSObject。 継承において、スーパークラスのメソッドを上書き(override)することができるが、 メソッドの先頭部分に override という修飾語を記述する必要がある。 class クラス名 : スーパークラス名, プロトコル1, プロトコル2, ... class Time12 : Time, CustomStringConvertible { var pm: Bool // 午後ならtrue 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 -= 12 pm = !pm } } var description: String { return toString() + " " + (pm ? "PM" : "AM") } } クラスの継承を利用すると、スーパークラスに対して定義されたメソッドが、サブクラスに対しても同様に動作する。 実行時に動的に動作を決めることができる(dynamic binding)。 var array = [Time]() // Time型の配列 array.append(Time(hour:13, min:10)) array.append(Time12(hour:13, min:20)) array.append(Time12(hour:1, min:30, pm:true)) for t in array { if t is Time12 { // is は動的にインスタンスの型を調べる演算子 print(t) // Time12型ならそのまま表示 } else { print(">", t.toString()) // 先頭の">"の後に表示する。 } } if let u = array[2] as? Time12 { // オプショナル束縛構文 print(u.pm ? "PM" : "AM") } インスタンスをキャスト(cast)する演算子として as, as?, as! がある。 クラスメソッドや、クラスプロパティでは class という修飾語を記述する。 タイプメソッドやタイププロパエィは上書きできないが、 クラスメソッドやクラスプロパティはサブクラスで定義を上書きできる。 クラスプロパティは計算型プロパティに限定されている。 clas A : CustomStringConvertible { static var className : String { return "A" } // 計算型タイププロパティ static var total: Int = 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.toal): \(A.clasName), Level=\(A.level), \(A.point())pt, \(A.say())" } } class B : A { override init() { super.ini(); ++B.total } override var description : String { return "\(B.toal): \(B.className), level=\(B.level), \(B.point())pt, \(B.say())" } } let a = A() print(a) // "1: A, Level=1, 1000pt, ah."と表示される let b = B() print(b) // "3: A, Level=1, 1000pt, ah."と表示される クラスBの中で、クラスAのタイププロパティやクラスプロパティを使うことができる。 class B : A { // override static var className: String { return "B" } // 定義不可 // static var total:Int = 0 // 定義不可 override class var level : Int { return 2 } // override static func point() -> Int { return 2000 } // 定義不可 override class func day() -> String { return "Boo" } override init() { super.ini(); ++B.total } override var description : String { return "\(B.toal): \(B.className), level=\(B.level), \(B.point())pt, \(B.say())" } } let a = A() print(a) // "1: A, Level=1, 1000pt, ah."と表示される let b = B() print(b) // "3: A, Level=2, 1000pt, Boo"と表示される 継承とメソッド呼び出し †継承関係にあるクラスA, Bはそれぞれ show メソッドを持っている。 class A { func show() { print("A") } func who() { show() } } class B: A { override func show() { print("B") } } var a = A() var b = B() a.who() // Aと表示される b.who() // Bと表示される メソッドwhoにおけるshowの呼び出しは, self.show() と記述されているのと同じなので、 実際に実行されているインスタンスのメソッド定義が呼び出される。 クラスメソッドについても同様である。 class A { class func show() { print("A") } class func who() { show() } } class B: A { override class func show() { print("B") } } var a = A() var b = B() a.who() // Aと表示される b.who() // Bと表示される イニシャライザ †
スーパークラスのdesignated initializerと同じイニシャライザをサブクラスで定義した場合、 override というキーワードをつける必要がある。 designated initializer init([仮引数: 型]... ) { 文 ... } convenience initializer convenience init([仮引数: 型] ...) { 文 ... } 初期化の手順と守るべきルール
次の制約に気をつけること。
クラス継承の例題 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)+"/"+DayOfMonthTwoDigits(day) } class func twoDigits(n:Int) -> String { // クラスメソッド let i = n % 100 if (i < 10) { return "0\(i)" } return "\(i)" } 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 } 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) // スーパークラスを初期化する } } class DateW : Date { static let nameOfDay = { "Sun","Mon","Tue","Wed","Thu","Fri","Sat" } var dweek = String() // 注意!! override init(_ y:Int, _ m:Int, _ d:Int) { // override が必要 super.init(y,m,d) dweek = DateW.namesOfDay[dow] // 注意!! } convenience init(_ m:Int, _ d:Int, year:Int = 2016) { self.init(year,m, d) } override var day:Int { // プロパティの上書きなのでoverrideが必要 didSet { dow = dayOfWeek(month,day,year:year) dweek = DateW.nameOfDay[dow] } } override var description: String { // 計算型プロパティの上書き return "\(year)/" + super.description + "(\(dweek))" } } designated initializerはスーパークラスに初期化を任せる前に、そのクラスで 追加された変数や定数の初期化を済ませていなくてはいけない。 そのため、一旦dweekにString()を入れておいて、スーパークラスのイニシャライザが 終了した段階で、本来の値を代入している。 イニシャライザの継承 †Swiftでは、スーパークラスのイニシャライザを、サブクラスでは基本的に継承しない。 しかし、次の2つの条件のどちらかに当てはまる場合は、継承できる。
必須イニシャライザ †イニシャライザの定義で init の前に required という修飾語を記述すると 「必須イニシャライザ (required initializer)」となり、 そのクラスを継承したサブクラスはすべてそのイニシャライザを実装しなくてはいけなくなる。 (スーパークラスからイニシャライザを継承した場合は書かなくてもよい)。 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) { // 別のdesignated initializer idnumber = id; super.init(charge:money) } required init(charge:Int) { // 必須イニシャライザを定義する idnumber = ++Icocai.serial super.init(charge:money) } } class Suiica: ICCard { // イニシャライザは継承される var name = String() } 必須イニシャライザは、サブクラスでconvenience initializerとして定義してもよい。 required convenience init(charge:Int) { self.init(id:++icocai.Serial, money:charge) } 失敗のあるイニシャライザと継承 †クラス定義でも失敗のあるイニシャライザ (failable initializer) を定義できる。 ただし、イニシャライザの途中で return nil する場合に次のルールを守ること。
サブクラスのイニシャライザがスーパークラスの失敗のあるイニシャライザを呼び出す場合、 サブクラスのイニシャライザも失敗のあるイニシャライザとなる。 スーパークラスのイニシャライザが return nil した場合は、その時点で初期化の処理は終了するのでサブクラスのイニシャライザに制御が戻ることはない。 class NamePlate { let name, title: String init() { name ="inviter"; title = "" } init?(name:String,title:String) { // 失敗のあるイニシャライザ // if name == "" { return il} // ここでreturn nilしてはいけない self.name = name self.title = title if name == "" { return nil} // 全てのプロパティに値が設定できたので return nilしてもよい } } class SpeakerNamePlate : NamePlate { override init?(name:String,title:String) { // if title == "" { return nil } // ここで return nil してはいけない super.init(name:name, title:title) if title == "" { return nil } // スーパークラスの初期化が終わったのでreturn nilしてもよい } } 失敗のあるイニシャライザを失敗しないようにする。 †let plate = NamePlate(name:"Taro", title:"Main")! // unwrapする super.init(name:yourName,title:what)! // unwrapする。 イニシャライザが同一かどうかは、キーワードと引数の型で判断される。 同じキーワードと引数の型を持つイニシャライザは、失敗するかどうかによらず、 同じイニシャライザと見なされる。 class GuestNamePlate : NamePlate { override init(name:String, title:String) { // 失敗のないイニシャライザで上書き if (name == "") { super.init() // 失敗のない定義を呼ぶ } else { super.init(name:name,title:title)! //失敗のないこと明記する } } } 継承とサブクラス †スーパークラスのプロパティを継承できる。 スーパークラスでread only であったプロパティを read and write可能に変更することはできるが、スーパークラスでread and write可能であったプロパティをread onlyにすることはできない。 継承では、サブクラスがスーパークラスの機能を包含している必要があるため。 class Prop { var attr = 0 } class PropA : Prop { // 0か1を返す(奇数であれば1が返る) override var attr : Int { get { return super.attr & 1 } set { super.attr = newValue } // 省略できない } } class PropB : Prop { // 1の位を切り捨てて値を保存する override var attr : Int { get { return super.attr } // 省略できない set { super.attr = newValue / 10 * 10 } } } 添字付けの継承 class Recipe { let amount = [ 6.6, 8.9, 7.5, 9.1 ] subscript(idx: Int) -> Double { // read only return idx < 4 ? amount[idx] : 0.0 } } class Arrange : Recipe { var custom: [Double] = [] override init() { super.init() custom = amount } override subscript(idx:Int) -> Double { // read and write get { return idx < 4 ? custom[idx] : 0.0 } set { if idx < 4 { custom[idx] = newValue } } } func recall(idx: Int) { // 指定した添字について元の値に戻す self[idx] = super[idx] // idxが範囲外でも問題ない } } 継承とプロパティオブザーバ †プロパティオブザーバは、サブクラスでoverrideされても順番に呼び出される。 つまり、動作がメソッドやプロパティとは異る。 class PropA { var attr = 0 } class PropB : PropA { override var attr : Int { willSet { print("B: will set") } didSet { print("B: did set") } } } class PropC : PropB { override var attr : Int { willSet { print("C: will set") } didSet { print("C: did set") } } } var x = propC() x.attr = 1 次のように表示される。 C: will set B: will set B: did set C: did set 継承させない指定 †finalという修飾語をつかって、継承させない指定を行う。 キャスト演算子とパターンマッチ †let s = [ "John", "Smith" ] as Set<String> // s = Set<String>([ "John", "Smith" ]) // と同じ // s:Set<String> = [ "John", "Smith" ] // と同じ let c = "字" as Character // s = Character("字") // と同じ case let 定数名 as 型 switch t { // tはTime型またはTime12型(サブクラス)のインスタンス case let u as Time12: print("Time12:", u) default: print("TIme:", t.toStirng()) } 次の例では条件が成立した場合定数vはTime12型として処理の中で利用できる。 if let v = obj as? Time12 { ... } if case let v as Time12 = obj { ... } 継承と型推論 †クラスTime12がクラスTimeを継承しているものとする。 これらのインスタンスを同時に含む配列を作ると、共通のスーパークラスであるTime型の配列になる。 var array = [ Time12(hour:13, min:20), Time(hour:18, min:45) ] 継承関係がないインスタンスの場合は AnyObject? 型となる。 var all:[AnyObject] = [ Time12(hour:0, min:15), SOS() ] print( all[0] as! Time12 ) // 0:15 AM と表示される AnyObject?はクラスのインスタンスだけを扱う。全ての型をまとめて扱うのは Any 型。 var every: [Any] = [ Time12(hour:16, min:30), "駅前", 5 ] print( all[0] as! Time12 ) // 4:30 PM と表示される クラスのメタタイプ †インスタンスに dynamicType という問い合せを行うと、実際に属するクラスのオブジェクトが値として得られる。 クラスAのクラスオブジェクトの型は A.Type と記述し、これをメタタイプ(meta type)と呼ぶ。 クラスAのクラスオブジェクトの値は A.self と記述する。 クラスオブジェクトに対して、クラスメソッドを呼び出したり、イニシャライザを使ってインスタンスを生成したりできる。 class Avatar { class func say() { print("Avatar") } // class method required init() { } } class Player : Avatar { override class func say() { print("Player") } // class method let name : String init(name: String) { self.name = name; super.init() } required convenience init() { self.init(name: "none") } } var meta: Avatar.Type = Player.self // メタタイプの変数を定義 meta.say() // Player と表示される let p:Avatar = meta.init() // インスタンスを生成できる if p.dynamicType === Player.self { // === 演算子で比較できる print( (p as! Player).name) // none と表示される } let q = Avatar() meta = q.dynamicType // インスタンスからメタタイプを得る meta.say() // Avatar と表示される 開放時処理 †デイニシャライザ †クラスのインスタンスは参照型であり、インスタンスはリファレンスカウンタで管理している。 デイニシャライザはクラスのインスタンスが開放される直前に呼び出され、一度だけ実行される。 デイニシャライザが実行される時、そのインスタンスのプロパティなどにはすべてアクセスできる。 deinit { 文 ... } クラスに継承関係がある場合は、サブクラスのデイニシャライザが実行された後、 スーパークラスのデイニシャライザが実行される。 開放されるクラスにデイニシャライザが存在しなくても、スーパークラスのデイニシャライザは 必ず実行される。 class ReadWord { var fp: UnsafeMutablePointer<FILE> = nil init?(open:String) { // 失敗のあるイニシャライザ fp = fopen(open,"r") // C言語のFILE*型 if fp == nil { return nil } } dinit { pirnt("deinit"); self.close() } func close() { if fp != nil { fclose(fp); fp = nil } } func nextWord() -> String? { // オプショナル型を返す var ch:Int repeat { ch = Int( fgetc(fp) ) // 1byte読み込んで if ch < 0 { return nil } // EOFならばnilを返す } while ch <= 0x20 // 空白や改行を読み飛ばす var s = String(UnicodeScalar(ch)) // 1文字だけの文字列を生成する while true { // 非空白文字の列を作る ch = Int( fgetc( fp ) ) // 1byte読み込んで if ch <= 0x20 { break } // 空白や改行ならば中断 s.append(UnicodeScalar(ch)) } return s } } func readIt() { if let reader = ReadWord(open:"text.txt") { // オープンできれば while let w = reader.nextWord() { // オプショナル束縛構文 if w == "Q!" { return } // 処理を中断 print(w,terminator:" ") } print("EOF") reader.close() } } 遅延格納型プロパティ †遅延格納型プロパティ †「初期化のときには値を決めず、必要になったときに初めて値を決定する」ようなプロパティを定義できる。 これを遅延格納型プロパティ (lazy stored property) と呼び、定義の先頭に 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) // statを呼び出す print("getFileSize") // 動作確認のための表示 return Int(buffer.st_size) // 得られた値をInt型に変換する } } または import Foundation class FileAttr { let filename: String lazy var size: Int = { // クロージャの記述開始 var buffer = stat() // 構造体の初期化 stat(filename,&buffer) // statを呼び出す print("getFileSize") // 動作確認のための表示 return Int(buffer.st_size) // 得られた値をInt型に変換する }() // クロージャの呼び出しなので、ここに () は必須。 self.getFileSize() // 遅延評価で値を決定する init(file:String) { filename = file } } 実行 let d = FileAttr(file: "text.txt") print(d.filename) print(d.size) print(d.size) 表示 text.txt getFileSize // ここで遅延評価が実行されている 332 332 // 一旦値が決まったら、次からは呼び出されない |