Swift Language
ジェネリックス
サーチ…
備考
ジェネリックコードを使用すると、ユーザーが定義した要件に応じて、任意のタイプで動作可能な柔軟で再利用可能な関数とタイプを記述できます。重複を避け、その意図をはっきりと抽象的に表現するコードを書くことができます。
GenericsはSwiftの最も強力な機能の1つで、Swift標準ライブラリの多くは汎用コードで構築されています。たとえば、Swiftの
Array
型とDictionary
型は両方とも汎用コレクションです。Int
値を保持する配列、またはString
値を保持する配列、またはSwiftで作成可能なその他の型の配列を作成できます。同様に、指定された型の値を格納するための辞書を作成することができます。また、型の制限はありません。
汎用プレースホルダ型の制約
ジェネリッククラスの型パラメータを強制的に使用してプロトコルを実装することは可能です (たとえば、 Equatable
class MyGenericClass<Type: Equatable>{
var value: Type
init(value: Type){
self.value = value
}
func getValue() -> Type{
return self.value
}
func valueEquals(anotherValue: Type) -> Bool{
return self.value == anotherValue
}
}
新しいMyGenericClass
を作成するたびに、型パラメータはEquatable
プロトコルを実装するEquatable
ます(型パラメータを==
を使用して同じ型の別の変数と比較できるようにします)
let myFloatGeneric = MyGenericClass<Double>(value: 2.71828) // valid
let myStringGeneric = MyGenericClass<String>(value: "My String") // valid
// "Type [Int] does not conform to protocol 'Equatable'"
let myInvalidGeneric = MyGenericClass<[Int]>(value: [2])
let myIntGeneric = MyGenericClass<Int>(value: 72)
print(myIntGeneric.valueEquals(72)) // true
print(myIntGeneric.valueEquals(-274)) // false
// "Cannot convert value of type 'String' to expected argument type 'Int'"
print(myIntGeneric.valueEquals("My String"))
ジェネリックスの基礎
ジェネリックスは型のプレースホルダで、複数の型にまたがって適用できる柔軟なコードを書くことができます。 Any
に対してgenericsを使用する利点は、コンパイラーが強力な型安全性を強制できることです。
一般的なプレースホルダは、 <>
内に定義されています。
汎用関数
関数の場合 、このプレースホルダは関数名の後に置かれます。
/// Picks one of the inputs at random, and returns it
func pickRandom<T>(_ a:T, _ b:T) -> T {
return arc4random_uniform(2) == 0 ? a : b
}
この場合、一般的なプレースホルダはT
です。関数を呼び出すと、SwiftはあなたのためにT
の型を推論することができます(単純に実際の型のプレースホルダーとして機能します)。
let randomOutput = pickRandom(5, 7) // returns an Int (that's either 5 or 7)
ここでは、2つの整数を関数に渡しています。したがって、SwiftはT == Int
推論しているので、関数シグネチャは(Int, Int) -> Int
と推測されます。
ジェネリックが提供する強力な型の安全性のために、引数と関数の両方の戻り値は同じ型でなければなりません。したがって、以下はコンパイルされません:
struct Foo {}
let foo = Foo()
let randomOutput = pickRandom(foo, 5) // error: cannot convert value of type 'Int' to expected argument type 'Foo'
一般的なタイプ
クラス 、 構造体、または列挙型でジェネリックを使用するには、型名の後ろに汎用プレースホルダを定義できます。
class Bar<T> {
var baz : T
init(baz:T) {
self.baz = baz
}
}
この汎用プレースホルダには、クラスBar
を使用するときにタイプが必要です。この場合、初期化子init(baz:T)
から推論することができます。
let bar = Bar(baz: "a string") // bar's type is Bar<String>
ここでは、一般的なプレースホルダT
はString
型であると推測され、 Bar<String>
インスタンスを作成します。型を明示的に指定することもできます:
let bar = Bar<String>(baz: "a string")
型とともに使用すると、指定された汎用プレースホルダは、指定されたインスタンスの存続期間全体にわたってその型を保持し、初期化後に変更することはできません。したがって、プロパティbaz
にアクセスすると、このインスタンスのString
型になります。
let str = bar.baz // of type String
一般的な型を渡す
ジェネリック型を渡す場合、ほとんどの場合、期待するジェネリックプレースホルダーの型について明示的に指定する必要があります。例えば、関数入力として:
func takeABarInt(bar:Bar<Int>) {
...
}
この関数はBar<Int>
のみを受け入れます。一般的なプレースホルダ型がInt
はないBar
インスタンスを渡そうとすると、コンパイラエラーが発生します。
汎用プレースホルダの名前付け
一般的なプレースホルダの名前は単一文字に限られていません。指定されたプレースホルダが意味のある概念を表す場合は、わかりやすい名前を付ける必要があります。たとえば、SwiftのArray
は、 Element
という汎用的なプレースホルダがあります。これは、指定されたArray
インスタンスの要素タイプを定義します。
public struct Array<Element> : RandomAccessCollection, MutableCollection {
...
}
ジェネリッククラスの例
型パラメータを持つジェネリッククラスType
class MyGenericClass<Type>{
var value: Type
init(value: Type){
self.value = value
}
func getValue() -> Type{
return self.value
}
func setValue(value: Type){
self.value = value
}
}
タイプパラメータを使用して新しいオブジェクトを作成できるようになりました
let myStringGeneric = MyGenericClass<String>(value: "My String Value")
let myIntGeneric = MyGenericClass<Int>(value: 42)
print(myStringGeneric.getValue()) // "My String Value"
print(myIntGeneric.getValue()) // 42
myStringGeneric.setValue("Another String")
myIntGeneric.setValue(1024)
print(myStringGeneric.getValue()) // "Another String"
print(myIntGeneric.getValue()) // 1024
ジェネリックは複数のタイプのパラメータで作成することもできます
class AnotherGenericClass<TypeOne, TypeTwo, TypeThree>{
var value1: TypeOne
var value2: TypeTwo
var value3: TypeThree
init(value1: TypeOne, value2: TypeTwo, value3: TypeThree){
self.value1 = value1
self.value2 = value2
self.value3 = value3
}
func getValueOne() -> TypeOne{return self.value1}
func getValueTwo() -> TypeTwo{return self.value2}
func getValueThree() -> TypeThree{return self.value3}
}
同じように使用されます
let myGeneric = AnotherGenericClass<String, Int, Double>(value1: "Value of pi", value2: 3, value3: 3.14159)
print(myGeneric.getValueOne() is String) // true
print(myGeneric.getValueTwo() is Int) // true
print(myGeneric.getValueThree() is Double) // true
print(myGeneric.getValueTwo() is String) // false
print(myGeneric.getValueOne()) // "Value of pi"
print(myGeneric.getValueTwo()) // 3
print(myGeneric.getValueThree()) // 3.14159
汎用クラス継承
一般的なクラスは継承できます:
// Models
class MyFirstModel {
}
class MySecondModel: MyFirstModel {
}
// Generic classes
class MyFirstGenericClass<T: MyFirstModel> {
func doSomethingWithModel(model: T) {
// Do something here
}
}
class MySecondGenericClass<T: MySecondModel>: MyFirstGenericClass<T> {
override func doSomethingWithModel(model: T) {
super.doSomethingWithModel(model)
// Do more things here
}
}
Genericsを使用した配列関数の簡略化
オブジェクト指向remove関数を作成することによって、配列の機能を拡張する関数。
// Need to restrict the extension to elements that can be compared.
// The `Element` is the generics name defined by Array for its item types.
// This restriction also gives us access to `index(of:_)` which is also
// defined in an Array extension with `where Element: Equatable`.
public extension Array where Element: Equatable {
/// Removes the given object from the array.
mutating func remove(_ element: Element) {
if let index = self.index(of: element ) {
self.remove(at: index)
} else {
fatalError("Removal error, no such element:\"\(element)\" in array.\n")
}
}
}
使用法
var myArray = [1,2,3]
print(myArray)
// Prints [1,2,3]
この関数を使用して、索引を必要とせずに要素を削除します。削除するオブジェクトを渡すだけです。
myArray.remove(2)
print(myArray)
// Prints [1,3]
タイプセーフティを強化するためにジェネリックを使用する
ジェネリックを使わずにこの例を考えてみましょう
protocol JSONDecodable {
static func from(_ json: [String: Any]) -> Any?
}
あなたが実際にそれを使用しない限り、プロトコル宣言はうまくいくようです。
let myTestObject = TestObject.from(myJson) as? TestObject
なぜ結果をTestObject
にキャストする必要がありますか?プロトコル宣言のAny
型のためです。
ジェネリックを使用することで、ランタイムエラーを引き起こす可能性のあるこの問題を回避することができます。
protocol JSONDecodable {
associatedtype Element
static func from(_ json: [String: Any]) -> Element?
}
struct TestObject: JSONDecodable {
static func from(_ json: [String: Any]) -> TestObject? {
}
}
let testObject = TestObject.from(myJson) // testObject is now automatically `TestObject?`
高度な型の制約
where
句を使用してwhere
ジェネリックのいくつかの型制約を指定することができます。
func doSomething<T where T: Comparable, T: Hashable>(first: T, second: T) {
// Access hashable function
guard first.hashValue == second.hashValue else {
return
}
// Access comparable function
if first == second {
print("\(first) and \(second) are equal.")
}
}
引数リストの後ろにwhere
句を書くことも有効です:
func doSomething<T>(first: T, second: T) where T: Comparable, T: Hashable {
// Access hashable function
guard first.hashValue == second.hashValue else {
return
}
// Access comparable function
if first == second {
print("\(first) and \(second) are equal.")
}
}
拡張は、条件を満たすタイプに制限することができます。この関数は、型条件を満たすインスタンスでのみ使用できます。
// "Element" is the generics type defined by "Array". For this example, we
// want to add a function that requires that "Element" can be compared, that
// is: it needs to adhere to the Equatable protocol.
public extension Array where Element: Equatable {
/// Removes the given object from the array.
mutating func remove(_ element: Element) {
// We could also use "self.index(of: element)" here, as "index(of:_)"
// is also defined in an extension with "where Element: Equatable".
// For the sake of this example, explicitly make use of the Equatable.
if let index = self.index(where: { $0 == element }) {
self.remove(at: index)
} else {
fatalError("Removal error, no such element:\"\(element)\" in array.\n")
}
}
}