Swiftでプログラミング-Extensions
extensionは、既存のクラス、構造、列挙型、またはプロトコルタイプに新しい機能を追加します。 これには、元のソースコードにアクセスできない型を拡張する機能があります(遡及モデリングと呼ばれます)。extensionは、Objective-Cのカテゴリに似ています。 (Objective-Cカテゴリとは異なり、Swiftのextensionには名前がありません。)
Swiftのextensionは次のことができます。
・計算インスタンスプロパティと計算タイププロパティを追加します
・インスタンスメソッドとタイプメソッドを定義する
・新しいイニシャライザを提供する
・subscript(添字)を定義する
・新しいネストされた型を定義して使用する
・既存の型をプロトコルに準拠させる
Swiftでは、プロトコルを拡張してその要件の実装を提供したり、適合タイプが利用できる機能を追加したりすることもできます。(Protocol Extensions)
extensionはタイプに新しい機能を追加できますが、既存の機能をオーバーライドすることはできません。
Extension Syntax
拡張キーワードを使用して拡張を宣言します。
extension SomeType {
// new functionality to add to SomeType goes here
}
extensionは、既存のタイプを拡張して、1つ以上のプロトコルを採用させることができます。 プロトコル適合性を追加するには、クラスまたは構造体にプロトコル名を記述するのと同じ方法でプロトコル名を記述します。
extension SomeType: SomeProtocol, AnotherProtocol {
// implementation of protocol requirements goes here
}
extensionを定義して既存のタイプに新しい機能を追加すると、extensionが定義される前に作成された場合でも、そのタイプの既存のすべてのインスタンスで新しい機能を使用できます。
Computed Properties
extensionは、計算インスタンスプロパティと計算タイププロパティを既存のタイプに追加できます。 この例では、5つの計算されたインスタンスプロパティをSwiftの組み込みのDoubleタイプに追加して、距離単位を操作するための基本的なサポートを提供します。
extension Double {
var km: Double { return self * 1_000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters")
// Prints "One inch is 0.0254 meters"
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// Prints "Three feet is 0.914399970739201 meters"
これらの計算プロパティは、Double値を特定の長さの単位と見なす必要があることを表しています。これらは計算プロパティとして実装されますが、これらのプロパティの名前は、そのリテラル値を使用して距離変換を実行する方法として、ドット構文を使用して浮動小数点リテラル値に追加できます。
この例では、Double値1.0は「1メートル」を表すと見なされます。これが、mで計算プロパティがselfを返す理由です。式1.mは、1.0のDouble値を計算すると見なされます。
他の単位では、メートル単位で測定された値として表現するために、ある程度の変換が必要です。 1キロメートルは1,000メートルと同じであるため、kmで計算されたプロパティは、値に1_000.00を掛けて、メートルで表される数値に変換します。同様に、メートルには3.28084フィートがあるため、ftで計算されたプロパティは、基になるDouble値を3.28084で除算して、フィートからメートルに変換します。
これらのプロパティは読み取り専用の計算プロパティであるため、簡潔にするためにgetキーワードなしで表現されています。それらの戻り値はDouble型であり、Doubleが受け入れられる場所であればどこでも数学計算内で使用できます。
let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// Prints "A marathon is 42195.0 meters long"
extensionは、新しい計算プロパティを追加できますが、保存されているプロパティを追加したり、既存のプロパティにプロパティオブザーバーを追加したりすることはできません。
Initializers
extensionは、既存の型に新しい初期化子を追加できます。これにより、他の型を拡張して、独自のカスタムタイプを初期化パラメーターとして受け入れるか、型の元の実装の一部として含まれていなかった追加の初期化オプションを提供できます。
extensionは、新しい便利な初期化子をクラスに追加できますが、新しい指定された初期化子または非初期化子をクラスに追加することはできません。指定された初期化子と非初期化子は、常に元のクラス実装によって提供される必要があります。
extensionを使用して、格納されているすべてのプロパティのデフォルト値を提供し、カスタム初期化子を定義しない値型に初期化子を追加する場合、extensionの初期化子内からその値型のデフォルトの初期化子とメンバーごとの初期化子を呼び出すことができます。これは、値型の初期化子委任で説明されているように、値型の元の実装の一部として初期化子を記述した場合には当てはまりません。
extensionを使用して、別のモジュールで宣言された構造に初期化子を追加する場合、新しい初期化子は、定義モジュールから初期化子を呼び出すまで、自分自身にアクセスできません。
以下の例では、幾何学的な長方形を表すカスタムRect構造を定義しています。この例では、サイズとポイントと呼ばれる2つのサポート構造も定義しています。どちらも、すべてのプロパティにデフォルト値0.0を提供します。
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
}
構造体Rectは、そのすべてのプロパティにデフォルト値を提供するため、デフォルトの初期化子で説明されているように、デフォルトの初期化子とメンバーごとの初期化子を自動的に受け取ります。 これらの初期化子を使用して、新しいRectインスタンスを作成できます。
let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
構造体Rectを拡張して、特定の中心点とサイズをとる追加の初期化子を提供できます。
extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
この新しい初期化子は、提供された中心点とサイズ値に基づいて適切な原点を計算することから始まります。 次に、初期化子は構造体の自動メンバーワイズ初期化子init(origin:size :)を呼び出します。これにより、新しい起点とサイズの値が適切なプロパティに格納されます。
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
新しいイニシャライザに拡張子を付けた場合でも、イニシャライザが完了したら、各インスタンスが完全に初期化されていることを確認する必要があります。
Methods
extensionは、既存の型に新しいインスタンスメソッドと型メソッドを追加できます。 次の例では、繰り返しと呼ばれる新しいインスタンスメソッドをInt型に追加します。
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
繰り返し(タスク:)メソッドは、タイプ()-> Voidの単一の引数を取ります。これは、パラメーターがなく、値を返さない関数を示します。
このextensionを定義した後、任意の整数でreperations(task :)メソッドを呼び出して、次の回数だけタスクを実行できます。
3.repetitions {
print("Hello!")
}
// Hello!
// Hello!
// Hello!
Mutating Instance Methods
extensionを追加したインスタンスメソッドは、インスタンス自体を変更(または変更)することもできます。 selfまたはそのプロパティを変更する構造体メソッドと列挙メソッドは、元の実装のメソッドを変更するのと同じように、インスタンスメソッドを変更としてマークする必要があります。
以下の例では、squareと呼ばれる新しいミューテーションメソッドをSwiftのInt型に追加します。これは、元の値を2乗します。
extension Int {
mutating func square() {
self = self * self
}
}
var someInt = 3
someInt.square()
// someInt is now 9
Subscripts
extensionは、既存のタイプに新しい添え字を追加できます。 この例では、Swiftの組み込みInt型に整数の添え字を追加します。 この添え字[n]は、数値の右からn桁の10進数を返します。
123456789 [0]は9を返します
123456789 [1]は8を返します
…等々:
extension Int {
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex {
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
746381295[0]
// returns 5
746381295[1]
// returns 9
746381295[2]
// returns 2
746381295[8]
// returns 7
Int値に要求されたインデックスに十分な桁がない場合、添え字の実装は、数値の左側にゼロが埋め込まれているかのように0を返します。
746381295[9]
// returns 0, as if you had requested:
0746381295[9]
Nested Types
extensionは、既存のクラス、構造、および列挙に新しいネストされた型を追加できます。
extension Int {
enum Kind {
case negative, zero, positive
}
var kind: Kind {
switch self {
case 0:
return .zero
case let x where x > 0:
return .positive
default:
return .negative
}
}
}
この例では、新しいネストされた列挙型をIntに追加します。 Kindと呼ばれるこの列挙は、特定の整数が表す数値の種類を表します。 具体的には、数値が負、ゼロ、または正のいずれであるかを表します。
この例では、kindと呼ばれる新しい計算インスタンスプロパティもIntに追加します。これは、その整数に適切なKind列挙ケースを返します。
ネストされた列挙は、任意のInt値で使用できるようになりました。
func printIntegerKinds(_ numbers: [Int]) {
for number in numbers {
switch number.kind {
case .negative:
print("- ", terminator: "")
case .zero:
print("0 ", terminator: "")
case .positive:
print("+ ", terminator: "")
}
}
print("")
}
printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
// Prints "+ + - 0 - 0 + "
この関数printIntegerKinds(_ :)は、Int値の入力配列を受け取り、それらの値を順番に繰り返し処理します。 配列内の整数ごとに、関数はその整数の種類計算プロパティを考慮し、適切な説明を出力します。
number.kindは、Int.Kind型であることがすでに知られています。 このため、すべてのInt.Kindケース値は、Int.Kind.negativeではなく.negativeなど、switchステートメント内に省略形で記述できます。
この記事が気に入ったらサポートをしてみませんか?