Поиск…


замечания

Общий код позволяет писать гибкие, многократно используемые функции и типы, которые могут работать с любым типом, в зависимости от требований, которые вы определяете. Вы можете написать код, который избегает дублирования и выражает свое намерение четким, абстрактным образом.

Generics - одна из самых мощных функций Swift, и большая часть стандартной библиотеки Swift построена с общим кодом. Например, типы Array Swift и Dictionary являются общими коллекциями. Вы можете создать массив, который содержит значения Int , или массив, который содержит значения String , или действительно массив для любого другого типа, который может быть создан в Swift. Аналогичным образом, вы можете создать словарь для хранения значений любого указанного типа, и нет ограничений на то, что может быть этим типом.

Источник: Быстрый язык программирования Apple

Ограничение общих типов закладок

Можно заставить параметры типа универсального класса реализовать протокол , например 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 , параметр type должен реализовывать 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"))

Основы дженериков

Generics - это заполнители для типов, позволяющие писать гибкий код, который можно применять для нескольких типов. Преимущество использования дженериков над Any заключается в том, что они все еще позволяют компилятору обеспечить сильную безопасность типов.

Общий заполнитель определяется в угловых скобках <> .

Общие функции

Для функций этот заполнитель помещается после имени функции:

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

Здесь мы передаем две целые функции. Поэтому 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> . Попытка передать экземпляр Bar где общий тип заполнителя не является Int приведет к ошибке компилятора.

Именование общего места

Общие имена заполнителей не ограничиваются только отдельными буквами. Если данный заполнитель представляет собой содержательную концепцию, вы должны дать ему описательное имя. Например, в Array Swift имеется общий заполнитель, называемый 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
    }
    
}

Использование генераторов для упрощения функций массива

Функция, расширяющая функциональность массива путем создания объектно-ориентированной функции удаления.

// 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 результат в 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 :

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