Swift Language
Merkloos product
Zoeken…
Opmerkingen
Met generieke code kunt u flexibele, herbruikbare functies en typen schrijven die met elk type kunnen werken, afhankelijk van de vereisten die u definieert. U kunt code schrijven die duplicatie vermijdt en de bedoeling ervan op een duidelijke, abstracte manier uitdrukt.
Generics zijn een van de krachtigste functies van Swift, en veel van de standaard Swift-bibliotheek is gebouwd met generieke code. De typen
Array
enDictionary
Swift zijn bijvoorbeeld beide generieke collecties. U kunt een array maken metInt
waarden, of een array metString
, of een array voor elk ander type dat in Swift kan worden gemaakt. Op dezelfde manier kunt u een woordenboek maken om waarden van elk gespecificeerd type op te slaan, en er zijn geen beperkingen aan wat dat type kan zijn.
Generieke plaatsaanduidingstypen beperken
Het is mogelijk om de Equatable
van een generieke klasse te dwingen een protocol te implementeren , bijvoorbeeld 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
}
}
Wanneer we een nieuwe MyGenericClass
, moet de parameter type het Equatable
protocol implementeren (ervoor zorgen dat de parameter type kan worden vergeleken met een andere variabele van hetzelfde type met ==
)
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"))
De basis van generieken
Generics zijn tijdelijke aanduidingen voor typen, zodat u flexibele code kunt schrijven die op meerdere typen kan worden toegepast. Het voordeel van het gebruik van generieke geneesmiddelen ten opzichte van Any
is dat hiermee de compiler nog steeds een sterke typeveiligheid kan afdwingen.
Een generieke tijdelijke aanduiding wordt gedefinieerd tussen punthaken <>
.
Algemene functies
Voor functies wordt deze tijdelijke aanduiding achter de functienaam geplaatst:
/// 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
}
In dit geval is de generieke tijdelijke aanduiding T
Wanneer u de functie komt oproepen, kan Swift het type T
voor u afleiden (omdat het eenvoudigweg fungeert als tijdelijke aanduiding voor een feitelijk type).
let randomOutput = pickRandom(5, 7) // returns an Int (that's either 5 or 7)
Hier geven we twee gehele getallen door aan de functie. Daarom leidt Swift T == Int
- dus wordt de functiesignatuur afgeleid als (Int, Int) -> Int
.
Vanwege de sterke typeveiligheid die generieke geneesmiddelen bieden, moeten zowel de argumenten als het rendement van de functie van hetzelfde type zijn. Daarom wordt het volgende niet gecompileerd:
struct Foo {}
let foo = Foo()
let randomOutput = pickRandom(foo, 5) // error: cannot convert value of type 'Int' to expected argument type 'Foo'
Generieke typen
Om generieken met klassen , structuren of enums te gebruiken , kunt u de generieke tijdelijke aanduiding achter de typenaam definiëren.
class Bar<T> {
var baz : T
init(baz:T) {
self.baz = baz
}
}
Deze generieke placeholder zal een type nodig als je naar de klas te gebruiken Bar
. In dit geval kan het worden afgeleid uit de initialisator init(baz:T)
.
let bar = Bar(baz: "a string") // bar's type is Bar<String>
Hier wordt afgeleid dat de generieke tijdelijke aanduiding T
van het type String
, waardoor een Bar<String>
-instantie wordt gemaakt. U kunt het type ook expliciet opgeven:
let bar = Bar<String>(baz: "a string")
Bij gebruik met een type, behoudt de gegeven generieke tijdelijke aanduiding zijn type voor de gehele levensduur van het gegeven exemplaar en kan niet worden gewijzigd na initialisatie. Wanneer u daarom toegang krijgt tot de eigenschap baz
, is deze altijd van het type String
voor deze gegeven instantie.
let str = bar.baz // of type String
Generieke typen doorgeven
Wanneer u generieke typen doorgeeft, moet u in de meeste gevallen expliciet zijn over het type generieke tijdelijke aanduiding dat u verwacht. Bijvoorbeeld, als een functie-invoer:
func takeABarInt(bar:Bar<Int>) {
...
}
Deze functie accepteert alleen een Bar<Int>
. Als u probeert te slagen in een Bar
exemplaar waarbij het generieke type tijdelijke aanduiding niet Int
is, resulteert dit in een compilerfout.
Generieke plaatsaanduiding
Algemene plaatsnamen zijn niet alleen beperkt tot enkele letters. Als een bepaalde tijdelijke aanduiding een betekenisvol concept vertegenwoordigt, moet u het een beschrijvende naam geven. Swift's Array
heeft bijvoorbeeld een generieke tijdelijke aanduiding Element
, die het elementtype van een bepaalde Array
instantie definieert.
public struct Array<Element> : RandomAccessCollection, MutableCollection {
...
}
Algemene klassenvoorbeelden
Een generieke klasse met de parameter 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
}
}
We kunnen nu nieuwe objecten maken met behulp van een parameter type
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 kunnen ook worden gemaakt met meerdere typeparameters
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}
}
En op dezelfde manier gebruikt
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
Overerving van generieke klasse
Algemene klassen kunnen worden overgenomen:
// 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 gebruiken om arrayfuncties te vereenvoudigen
Een functie die de functionaliteit van de array uitbreidt door een objectgeoriënteerde verwijderfunctie te maken.
// 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")
}
}
}
Gebruik
var myArray = [1,2,3]
print(myArray)
// Prints [1,2,3]
Gebruik de functie om een element te verwijderen zonder dat een index nodig is. Geef het te verwijderen object gewoon door.
myArray.remove(2)
print(myArray)
// Prints [1,3]
Gebruik generieke geneesmiddelen om de typeveiligheid te verbeteren
Laten we dit voorbeeld nemen zonder generieke geneesmiddelen te gebruiken
protocol JSONDecodable {
static func from(_ json: [String: Any]) -> Any?
}
De protocolverklaring lijkt prima, tenzij u deze daadwerkelijk gebruikt.
let myTestObject = TestObject.from(myJson) as? TestObject
Waarom moet je het resultaat naar TestObject
? Vanwege het Any
return-type in de protocolverklaring.
Door generieke geneesmiddelen te gebruiken, kunt u dit probleem voorkomen dat runtime-fouten kan veroorzaken (en we willen ze niet hebben!)
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?`
Geavanceerde typebeperkingen
Het is mogelijk om verschillende typebeperkingen voor generieken op te geven met behulp van de where
clausule:
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.")
}
}
Het is ook geldig om de where
clausule na de lijst met argumenten te schrijven:
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.")
}
}
Uitbreidingen kunnen worden beperkt tot typen die aan voorwaarden voldoen. De functie is alleen beschikbaar voor instanties die voldoen aan de typevoorwaarden:
// "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")
}
}
}