Sök…


Anmärkningar

Med generisk kod kan du skriva flexibla, återanvändbara funktioner och typer som kan fungera med alla typer, förutsatt att du definierar krav. Du kan skriva kod som undviker duplicering och uttrycker sin avsikt på ett tydligt, abstrakt sätt.

Generics är en av de kraftfullaste funktionerna i Swift, och mycket av Swift-standardbiblioteket är byggt med generisk kod. Till exempel är Swift's Array och Dictionary typer båda generiska samlingar. Du kan skapa en matris som innehåller Int värden, eller en matris som innehåller String , eller verkligen en matris för någon annan typ som kan skapas i Swift. På liknande sätt kan du skapa en ordlista för att lagra värden av vilken specifik typ som helst, och det finns inga begränsningar för vad den typen kan vara.

Källa: Apples snabba programmeringsspråk

Begränsa generiska platshållartyper

Det är möjligt att tvinga typparametrarna för en generisk klass att implementera ett protokoll , till exempel 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
    }
}

När vi skapar en ny MyGenericClass måste Equatable implementera Equatable protokollet (se till att typparametern kan jämföras med en annan variabel av samma typ med == )

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"))

Grunderna i generiker

Generics är platshållare för typer, så att du kan skriva flexibel kod som kan tillämpas på flera typer. Fördelen med att använda generika jämfört med Any är att de fortfarande tillåter kompilatorn att säkerställa stark typsäkerhet.

En generisk platshållare definieras inom vinkelfästen <> .

Generiska funktioner

För funktioner placeras denna platshållare efter funktionsnamnet:

/// 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
}

I detta fall är den generiska platshållaren T När du kommer att ringa funktionen kan Swift dra slutsatsen för typen av T för dig (eftersom den helt enkelt fungerar som en platshållare för en faktisk typ).

let randomOutput = pickRandom(5, 7) // returns an Int (that's either 5 or 7)

Här överför vi två heltal till funktionen. Därför drar Swift slutsatsen T == Int - alltså dras funktionssignaturen ut för att vara (Int, Int) -> Int .

På grund av den starka typsäkerheten som generics erbjuder - måste både argumenten och återlämnandet av funktionen vara av samma typ. Därför kommer följande inte att sammanställas:

struct Foo {}

let foo = Foo()

let randomOutput = pickRandom(foo, 5) // error: cannot convert value of type 'Int' to expected argument type 'Foo'

Generiska typer

För att använda generika med klasser , strukturer eller enums kan du definiera den generiska platshållaren efter typnamnet.

class Bar<T> {
    var baz : T
    
    init(baz:T) {
        self.baz = baz
    }
}

Denna generiska platshållare kräver en typ när du kommer att använda Bar . I detta fall kan man dra slutsatsen från initialiseraren init(baz:T) .

let bar = Bar(baz: "a string") // bar's type is Bar<String>

Här sluts den generiska platshållaren T att vara av typen String , vilket skapar en Bar<String> -instans. Du kan också specificera typen uttryckligen:

let bar = Bar<String>(baz: "a string")

När den används med en typ kommer den givna generiska platshållaren att behålla sin typ under hela den aktuella instansens livslängd och kan inte ändras efter initialisering. Därför när du baz fastigheten baz , kommer den alltid att vara av typen String för den här instansen.

let str = bar.baz // of type String

Passerar runt generiska typer

När du kommer fram till generiska typer måste du i de flesta fall vara uttrycklig för den generiska platshållartyp du förväntar dig. Till exempel som funktionsinmatning:

func takeABarInt(bar:Bar<Int>) {
    ...
}

Denna funktion accepterar endast en Bar<Int> . Att försöka passera i en Bar instans där den generiska platshållartypen inte är Int kommer att resultera i ett kompilatorfel.

Generisk platshållare-namngivning

Generiska platshållarnamn är inte bara begränsade till enstaka bokstäver. Om en given platshållare representerar ett meningsfullt begrepp, bör du ge det ett beskrivande namn. Till exempel har Swift's Array en generisk platshållare som heter Element , som definierar elementtypen för en given Array instans.

public struct Array<Element> : RandomAccessCollection, MutableCollection {
    ...
}

Exempel på generiska klass

En generisk klass med typparametern 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
    }
}

Vi kan nu skapa nya objekt med hjälp av en typparameter

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

Generics kan också skapas med flera typer av parametrar

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}
}

Och används på samma sätt

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

Generisk klassärvning

Generiska klasser kan ärvas:

// 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
    }
    
}

Använda Generics för att förenkla Array-funktioner

En funktion som utökar matrisens funktionalitet genom att skapa en objektorienterad borttagningsfunktion.

// 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")
        }
    }
}

Användande

var myArray = [1,2,3]
print(myArray)

// Prints [1,2,3]

Använd funktionen för att ta bort ett element utan behov av ett index. Passera bara objektet för att ta bort.

myArray.remove(2)
print(myArray)

// Prints [1,3]

Använd generika för att förbättra typsäkerheten

Låt oss ta detta exempel utan att använda generika

protocol JSONDecodable {
    static func from(_ json: [String: Any]) -> Any?
}

Protokolldeklarationen verkar bra om du inte använder den.

let myTestObject = TestObject.from(myJson) as? TestObject

Varför måste du kasta resultatet till TestObject ? På grund av vilken som Any returtyp i protokolldeklarationen.

Genom att använda generika kan du undvika detta problem som kan orsaka runtime-fel (och vi vill inte ha dem!)

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?`

Avancerade typbegränsningar

Det är möjligt att ange flera typbegränsningar för generiska med hjälp av where klausulen:

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.")
    }
}

Det är också giltigt att skriva where klausulen efter argumentlistan:

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.")
    }
}

Tillägg kan begränsas till typer som uppfyller villkoren. Funktionen är endast tillgänglig för fall som uppfyller typvillkoren:

// "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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow