Swift

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

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

クラス構造体列挙型
参照型/値型参照型値型値型
変更に関する制約なし(*1)(*2)
インスタンスプロパティ格納型/計算型格納型/計算型-/計算型
タイププロパティ格納型/計算型格納型/計算型格納型/計算型
継承--
プロトコル継承
memberwise initializer--
  • (*1) --- インスタンスの格納型プロパティの値を変更するメソッドに mutating 指定が必要。また、定数に格納されたインスタンスはプロパティの値が変更できない。
  • (*2) selfの値を変更するメソッドに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 --- ベースクラスでは、そのイニシャライザを使うだけでインスタンスの初期化がすべて完了できるもの。サブクラスでは、直接のスーパークラスのdesignated initializer以外の助けを借りずに初期化作業を行うもの。
  • convenience initializer --- 他のイニシャライザを呼び出してその助けを借りる(Swiftではこれを delegate と呼ぶ)ことで初期化を行うもの。同じクラスのdesignated initializerを呼び出すのはよいが、スーパークラスのイニシャライザを呼び出してはいけない。定義のときに convenience というキーワードを付ける。

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

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

初期化の手順と守るべきルール

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

次の制約に気をつけること。

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

イニシャライザの継承

Swiftでは、スーパークラスのイニシャライザを、サブクラスでは基本的に継承しない。 しかし、次の2つの条件のどちらかに当てはまる場合は、継承できる。

  • サブクラスでdesignated initializerをひとつも定義していない。
  • サブクラスがスーパクラスのdesignated initializerを全て持っている。 すべてを定義して実装している場合と、ひとつも定義せずにスーパークラスから継承している場合がある。 スーパークラスのconvenience initializerをすべて継承できる。

必須イニシャライザ

イニシャライザの定義で 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 する場合に次のルールを守ること。

  • designated initializerの場合
    すべてのプロパティに値が設定されるまで return nil してはいけない。 サブクラスのイニシャライザの場合は、スーパークラスのイニシャライザを呼び出してからでないと return nil してはいけない。
  • convenience 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             // 一旦値が決まったら、次からは呼び出されない

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-05-25 (水) 14:51:17 (1470d)