#author("2016-05-21T23:54:37+00:00","default:nitta","nitta")
[[Swift]]

*Swift/クラス [#n73f083c]

**概要 [#deccc028]

クラスのインスタンスは参照型である。
すなわち、変数に代入したりメソッドの引数に渡す時に、コピーが作られるのではなく、同一のインスタンスへの参照が渡される。

 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 という修飾子をつけたが、
クラスの定義では必要ない。

クラスのインスタンスは、定数と変数のどちらに格納されていても、格納型プロパティの変更が可能である。

LEFT:
||クラス|構造体|列挙型|
|参照型/値型|参照型|値型|値型|
|変更に関する制約|なし|(*1)|(*2)|
|インスタンスプロパティ|格納型/計算型|格納型/計算型|-/計算型|
|タイププロパティ|格納型/計算型|格納型/計算型|格納型/計算型|
|継承|◯|-|-|
|プロトコル継承|◯|◯|◯|
|memberwise initializer|-|◯|-|

-(*1) --- インスタンスの格納型プロパティの値を変更するメソッドに mutating 指定が必要。また、定数に格納されたインスタンスはプロパティの値が変更できない。
-(*2) selfの値を変更するメソッドにmutating指定が必要。

*** クラスの継承 [#zce9016e]

ルートクラスは 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"と表示される

***継承とメソッド呼び出し [#me0bbc73]

継承関係にあるクラス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と表示される

**イニシャライザ [#u2c116b3]

-designated initializer --- ベースクラスでは、そのイニシャライザを使うだけでインスタンスの初期化がすべて完了できるもの。サブクラスでは、直接のスーパークラスのdesignated initializer以外の助けを借りずに初期化作業を行うもの。
-convenience initializer --- 他のイニシャライザを呼び出してその助けを借りる(Swiftではこれを delegate と呼ぶ)ことで初期化を行うもの。同じクラスのdesignated initializerを呼び出すのはよいが、スーパークラスのイニシャライザを呼び出してはいけない。定義のときに convenience というキーワードを付ける。

スーパークラスのdesignated initializerと同じイニシャライザをサブクラスで定義した場合、
override というキーワードをつける必要がある。

 designated initializer
   init([仮引数: 型]... ) { 文 ... }
 convenience initializer
   convenience init([仮引数: 型] ...) { 文 ... }

初期化の手順と守るべきルール
+サブクラスのイニシャライザが呼び出される。もしconvenience initializerの場合はインスタンスの変数や定数に値を設定することはまだできない。必要な前処理があれば行った上でdesignated initializerを呼び出す。
+そのサブクラスで追加された変数や定数の初期化を行う。初期値が宣言されていれば、それが格納される。代入文でも設定できる。
+サブクラスからスーパークラスのイニシャライザを呼び出す。
+ベースクラスのイニシャライザが、ベースクラスの変数と定数を初期化する。終了するとサブクラスのイニシャライザに制御が戻る。
+サブクラスのイニシャライザは、スーパークラスで初期化された変数について、必要ならば変更を加える。この時点で、メソッドを呼び出したり、各プロパティにアクセスしたりできる。

次の制約に気をつけること。
-designated initializerは、スーパークラスに初期化を任せる前に、そのクラスで追加された
変数と定数の初期化(値の代入、初期値の設定)を済ませている必要がある。
-サブクラスのイニシャライザは、スーパークラスのdesignated initializerに初期化を任せる前に、スーパークラスで定義されている変数と定数に値を代入してはいけない。
-convenience initializerは、同じクラスの他のイニシャライザに初期化を任せる前に、インスタンスのどの変数や定数にも値を代入してはいけない。
-イニシャライザは、ベースクラスで変数と定数の初期化が終わるまで、インスタンスメソッドを
呼び出したり、インスタンス変数を読み出したり、selfを値として扱ったりしてはいけない。
イニシャライザの中で何かの関数を使いたい場合は、グローバルな関数か、タイプメソッドとして
実装するとよい。

クラス継承の例題
 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()を入れておいて、スーパークラスのイニシャライザが
終了した段階で、本来の値を代入している。

***イニシャライザの継承 [#z021ecb5]

Swiftでは、スーパークラスのイニシャライザを、サブクラスでは基本的に継承しない。
しかし、次の2つの条件のどちらかに当てはまる場合は、継承できる。
-サブクラスでdesignated initializerをひとつも定義していない。
-サブクラスがスーパクラスのdesignated initializerを全て持っている。
すべてを定義して実装している場合と、ひとつも定義せずにスーパークラスから継承している場合がある。
スーパークラスのconvenience initializerをすべて継承できる。

***必須イニシャライザ [#xf2dae44]
イニシャライザの定義で 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)
 }

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS