Основной инструмент для гибкого кода

Поддержка дженериков в Swift позволяет вам писать код, который можно использовать с широким спектром типов, сохраняя при этом безопасность типов. Используя универсальные шаблоны, вы можете создавать функции и типы, которые являются гибкими и пригодными для повторного использования, то есть их можно легко адаптировать для работы с различными типами без необходимости написания отдельного кода для каждого типа. Обобщения также помогают избежать дублирования, позволяя писать абстрактный код, который можно применять к нескольким типам. Это может сделать ваш код более кратким и легким для чтения, поскольку он четко выражает цель кода без необходимости дублирования.

Эта статья является частью моей серии Учебник по программированию на Swift.

Вы можете использовать дженерики, добавив заполнитель для нужного типа, записанный как <GenericName>, после имени функции, класса или другого типа. Вот пример синтаксиса:

func printDataType<T>(data: T) {}

Примечание. Буква "T" часто используется в качестве заполнителя при определении универсальных типов, но ее можно заменить любым другим допустимым идентификатором.

Использование обобщений в функциях

Начиная с очень простого примера с использованием функции, вот как создаются дженерики.

func printDataType<T>(data: T) {
    print(data, "is", type(of: data))
}

printDataType(data: "Hello World")
printDataType(data: 45.0000087)

Если вам интересно, вот вывод:

Hello World is String
45.0000087 is Double

Использование обобщений с ограничениями типа

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

func showLowerNumber(firstNum: Int, secondNum: Int) {
    if firstNum < secondNum {
        print("First number is lower")
    } else {
        print("Second number is lower")
    }
}

showLowerNumber(firstNum: 34, secondNum: 89)

Функция, показанная выше, служит своей цели показать, какое число меньше. Однако он принимает только целочисленные типы данных. Если мы хотим использовать другие числовые типы данных, такие как double или float, мы можем использовать дженерики, чтобы изменить функцию для обработки этих типов данных. Отредактируйте приведенный выше код следующим образом:

func showLowerNumber<AnyNum: Comparable>(firstNum: AnyNum, secondNum: AnyNum) {
    if firstNum < secondNum {
        print("First number is lower")
    } else {
        print("Second number is lower")
    }
}

showLowerNumber(firstNum: 0.067, secondNum: 0.0001)

Вот результат:

Second number is lower

Comparable — это протокол, который позволяет сравнивать экземпляры определенного типа с помощью реляционных операторов.

Использование дженериков в классах

Обобщения также можно использовать в классе, поскольку они позволяют создавать повторно используемые объекты. Вот пример:

class ShowDataType<T> {
    
    var data: T
    
    init(data: T) {
        self.data = data
    }
    
    func printDataType() {
        print(data, "is", type(of: data))
    }
    
}

var showAny = ShowDataType(data: [1, "Hi"])
showAny.printDataType()

var showString = ShowDataType<String>(data: "Hello World")
showString.printDataType()

var showInt = ShowDataType<Int>(data: 45)
showInt.printDataType()

Вот результат:

[1, "Hi"] is Array<Any>
Hello World is String
45 is Int

В представленном примере тип данных не был указан для переменной showAny , но он был указан для переменных showString и showInt .

Использование дженериков в структурах, таких как SwiftUI

Поскольку SwiftUI использует структуру, вы в основном будете использовать дженерики для хранения представления в качестве свойства для повторно используемых представлений SwiftUI. Вот пример:

struct BlueCirclePlaceholder<Content: View>: View {
    
    var content: () -> Content
    
    var body: some View {
        content()
            .frame(maxWidth: .infinity)
            .frame(height: 50)
            .background(Color.blue)
            .clipShape(Circle())
    }
    
}

В приведенном выше коде мы объявляем тип Content универсальным и указываем, что он соответствует протоколу View в качестве ограничения типа.

Теперь мы можем использовать новое представление следующим образом:

struct ContentView: View {

    var body: some View {
        VStack {
            BlueCirclePlaceholder {
                Text("AS")
                    .font(.system(size: 30))
                    .padding()
            }
        }
        .background(Color.white)
    }

}

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

Да пребудет с вами код,

-Дуга