サーチ…


備考

ジェネリックコードを使用すると、ユーザーが定義した要件に応じて、任意のタイプで動作可能な柔軟で再利用可能な関数とタイプを記述できます。重複を避け、その意図をはっきりと抽象的に表現するコードを書くことができます。

GenericsはSwiftの最も強力な機能の1つで、Swift標準ライブラリの多くは汎用コードで構築されています。たとえば、SwiftのArray型とDictionary型は両方とも汎用コレクションです。 Int値を保持する配列、またはString値を保持する配列、またはSwiftで作成可能なその他の型の配列を作成できます。同様に、指定された型の値を格納するための辞書を作成することができます。また、型の制限はありません。

出典: Appleの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>

ここでは、一般的なプレースホルダTString型であると推測され、 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")
        }
    }
}


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow