Swift

Swift/拡張

ソースコードを書き換えたり継承を使ったりすることなく、 既に存在する肩に新しいメソッドやプロパティを付け加える機能が Swiftの「拡張 (extension)」である。

extension 型名 : [ プロトコル列 ] {
  [ イニシャライザ定義 ]
  [ 計算型プロパティ定義 ]
  [ メソッド定義 ]
  [ その他(型定義、添字付け定義、など)の定義 ]
}

拡張に記述できる定義は以下の通り。

  • 計算型のインスタンスプロパティ、計算型のタイププロパティ
  • インスタンスメソッド、タイプメソッド
  • イニシャライザ
  • 添字付け
  • (拡張の内部で使用する)ネスト型定義

拡張機能を使って、クラスや構造体をそれまで適合していなかったプロトコルに適合させることができる。

拡張に記述できないのは次の通り。

  • 格納型のインスタンスプロパティ、格納型のタイププロパティ
  • プロパティオブザーバ
  • すでに定義されている内容の上書き

システムの既存の型に対する拡張定義の例

extension String {
  var length: Int {
    return self.characters.count    // インスタンス自体はselfで表す
  }
}
let s = "オーハ\u{3099}ーロート\u{3099}"
print(s, s.length)      // オーバーロード 7 と表示される

CGRectに対する拡張定義の例

import Cocoa
extensions CGRect {
  init(points p1:CGPoint, _ p2:CGPoint) {
    var ox,oy, w, h: CGfloat
    (ox, w) = p1.x < p2.x ? (p1.x, p2.x-p1.x) : (p2.x, p1.x-p2.x)
    (oy, h) = p1.y < p2.y ? (p1.y, p2.y-p1.y) : (p2.y, p1.y-p2.y)
    self.origin = CGPoint(x:ox, y:oy)
    self.size = CGSize(width:w, height: h)
  }
  subscript (i:Int) -> CGPoint {
    var p = self.origin
    if i == 1 || i == 2 {
      p.x += self.width
    }
    if i == 2 || i == 3 {
      p.y += self.height
    }
    return p
  }
}

let p1 = CGPoint(x:10.0, y:105.0)
let p2 = CGPoint(x:130.0, y:15.0)
let rect = CGRect(points: p1, p2)
print(rect)     // (10.0, 15.0, 120.0, 90.0) と表示される
for i in 0...2 {
  print(rect[i], terminator:", ")
}
print(rect[3])  // (10.0, 15.0), (130.0, 15.0), (130.0, 105.0), (10.0, 105.0)

拡張定義とイニシャライザ

  • 値型の定義の場合。
    イニシャライザの定義がなく、default initializer または memberwise initializer を使っているとする。 拡張定義でイニシャライザを追加した後も、既存の default initializer や memberwise initializer を利用し続けることができる。
  • クラスの場合
    拡張定義にdesignated initializer を記述することはできないが、 convenience initializerの記述はできる。 デイニシャライザを拡張定義に含めることはできない。

拡張定義と継承

あるクラスに対して拡張定義を追加した場合、その定義はサブクラスにも継承される。 ただし、拡張定義中の記述は final がついているものとして扱われるので、 サブクラスで上書きすることはできない。

拡張定義とプロトコルへの適合

クラス、構造体、列挙型に対する拡張定義にはプロトコルを指定することができるが、 そのプロトコルに適合するように拡張機能で実装しなくてはならない。

プロトコル FloatLiteralConvertible? の宣言

protocol FloatLiteralConvertible {
  typealias FloatLiteralType
  init(floatLiteral value: Self.FloatLiteralType)
}

Ounce+Ext.swift というファイルの内容

extension Ounce : FloatLiteralConvertible {
  init(floatLiteral value: Double) { self.init(ounce: value) }
}

次のmain.swift を Ounce+Ext.swift と一緒にコンパイルして実行する

var a: Ounce
a = 10.0
print("\(a.mL)mL")   // 295.735mL と表示される

ただし、代入できるようになったのは実数のリテラルだけなので、以下の記述はエラーとなる。

var a: Ounce
let v = 10.0
a = v    // エラー

プロトコルに適合するための定義がすでに存在する場合

enum Grade {
  case 秀, 優, 良, 可, 不可, 放棄
  var boolValue: Bool {
    switch self {
    case 秀, 優, 良, 可: return true
    default: return false
  }
}
public protocol BooleanType {
  public var boolValue: Bool { get }
}

列挙型 Grade は既に BooleanType? プロトコルに適合しているが、そのことが宣言されていない。 Gradeのソースプログラムを変更できない場合には拡張定義の記述をすることでプロトコルへの 適合を宣言できる。

extension Grade : BooleanType { }   // プロトコルに適合していることだけを宣言する

プロトコルが演算子の定義を含む場合

前章で扱ったプロトコル Equatable は演算子 == の定義だけを含んでいる。 拡張定義でプロトコル Equatable に適合させるには、トップレベルで演算子の定義を記述し、 さらに空の拡張定義が必要となる。

extension DayOfMonth : Equatable {}
func ==(lhs: DayOfMonth, rhs: DayOfMonth) -> Bool {
  return lhs.month == rhs.month && lhs.day == rhs.day
}

プロトコル拡張

protocol Datable {
  var year: Int { get }
  var month: Int { get }
  var day: Int { get }
}

プロトコル Datable に対する拡張定義

func Zeller(var m:Int, _ d:Int, var _ y:Int) -> Int { /* 略 */ }

extension Datable {
  var dayOfWeek: Int {
    return Zeller(month, day, year)
  }
}

struct Date : Daytable {
  var year, month, day: Int
}

let d1 = Date(year: 2016, month: 6, day: 8)
print(d1)    // Date(year: 2016, month: 6, day: 8)
print(d1.dayOfWeek)  // 3 と表示される(水曜日を表す数字)

データ型が採用したプロトコルにプロトコル拡張( protocol extension )が存在していると、 そのデータ型ではプロトコル拡張の定義を利用できるし、必要に応じて上書きすることもできる。 つまり、プロトコル拡張では、 メソッドやプロパティのデフォルトの実装 (default implementation) を記述できるといえる。

protocol Datable {
  var year: Int { get }
  var month: Int { get }
  var day: Int { get }
  var isLeap: Bool { get }
}
extension Datable {
  var isLeap: Bool {
    return (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0)
  }
  private static func Zeller(var m:Int, _ d:Int, var _ y:Int) -> { /* 略 */ }
  var dayOfweek: Int {
    return Self.Zeller(month, day, year)
  }
}

default implementation は、それを継承したプロトコルやデータ型で上書きすることができる。 その場合、構造体、列挙型、クラス定義でも override を記述する必要はない。

プロトコル拡張による多重継承

protocol DateType {
  var year: Int { get }
  var month: Int { get }
  var day: Int { get }
  var area: String? { get }    // Timezone
}
extension DateType {
  func toString() -> String {
    return "\(year). \(month). \(day)"
  }
}
protocol TimeType {
  var hour: Int { get }
  var minute: Int { get }
  var area: String? { get }    // Timezone
}
extension TypeType {
  func toString() -> String {
    var s = (hour < 10 ? " " : "") + "\(hour)"
    s += ":" + (minute < 10 ? "0" : "") + "\(minute)"
    if let a = area { s += " (\(a))" }
    return s
  }
}
struct Date : DateType, TimeType {  // 2つのプロトコル拡張を利用する構造体
  let year, month, day, hour, minute: Int
  let area: String?
  init(_ y:Int, _ m:Int, _ d:Int, _ t:(Int, Int), area: String? = nil) {
    year = y; month = m; day = d;
    (hour, minute) = t; self.area = area
  }
}

次のようなプログラムは、どちらの toString()関数を使ってよいかわからないのでエラーとなる

let d = Date(2010, 7, 23, (12, 56), area: "JS")
print(d.toString())     // エラーとなる

次の例ではダウンキャストしているので、どちらの toString() 関数を使うか明白であり、エラーは起こらない。

let dd: DateType = Date(2010, 7, 23, (12, 56), area: "JS")
let dt: TimeType = Date(2010, 7, 23, (12, 56), area: "JS")
print(dd.toString())    // 2010. 7. 28 と表示される
print(dt.toString())    // 12:56 (JST) と表示される

プロトコル拡張のどちらの関数を使用するか指定すればエラーは起きない。

extension Date: CustomStringConvertible {
  var description: String {
    return (self as DateType).toString() + ", " + (self as TimeType).toString()
  }
}
let d: DateType = Date(2010, 7, 23, (12, 56), area: "JS")
print(d))    // 2010. 7. 28, 12:56 (JST) と表示される

default implementation の利用には注意が必要である。 もしも次のような定義をした場合は、両方のプロトコル拡張に同じ toString() という名前の関数がないと、無限にプロトコル拡張のtoString()を呼び出してしまい異常終了となる。

extension Date {
  func toString() -> String {
    return (self as DateType).toString() + ", " + (self as TimeType).toString()
  }
}

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-06-08 (水) 18:20:24 (1460d)