Заполнители типа (type placeholders) — это новая языковая фича, представленная в Swift 5.6 (Xcode 13.3).
Сама концепция очень проста - вместо указания конкретного типа мы можем поставить _ (заполнитель типа), который предписывает компилятору самому определить заполняемый тип.
В следующем примере я использую заполнитель типа для name
, который впоследствии разрешается в String
.
let name: _ = "Sarun"
Когда я впервые увидел эту фичу, я не сразу понял, зачем вообще она может мне понадобиться, ведь в Swift уже есть вывод типов, и я могу написать предыдущий код вообще без указания типа (или какого-либо заполнителя).
let name = "Sarun"
Зачем нужны заполнители типа
Как оказалось, простой пример выше — не совсем то, для чего были введены заполнители типа. Заполнители типа предназначены для подстановки вместо типов с несколькими типами внутри.
Вот некоторые типы, которые содержат в себе несколько типов.
Тип функции:
(parameter type) -> return type
Тип словаря:
[key type: value type]
Универсальные типы (дженерики): любой тип с более чем одним универсальным типом, например Result.
public enum Result<Success, Failure> where Failure : Error {
case success(Success)
case failure(Failure)
}
Проблема с типами, содержащими в себе несколько других типов заключается в том, что выведение типов Swift не справляется с такими типами; от нас требуется, чтобы мы предоставили весь тип явно, в то время как на самом деле нужна только часть этого типа. Это становится проблематичным в тех случаях, когда речь идет о более сложных типах.
Думаю, это будет проще объяснить на примерах.
Примеры
Давайте рассмотрим несколько примеров предполагаемого использования заполнителей типов.
Функции
Первый пример взят прямиком из предложения SE-0315. Мы пытаемся создать конвертер строк из Double.init
.
let stringConverter = Double.init
Поскольку Double.init
имеет множество перегрузок, компилятор не может предположить тип этого выражения.
До Swift 5.6 вы должны были прописывать это явно, предоставляя больше контекста для ожидаемого нами типа.
// Аннотации типа переменной
let stringConverter: (String) -> Double? = Double.init
// Приведение типа через as
let stringConverter = Double.init as (String) -> Double?
Мы предоставили как аргументы, так и возвращаемые типы, но в этом конкретном случае нам нужно уточнить только тип аргумента, поскольку существует только одна перегрузка Double.init
, которая принимает String.
extension Double: LosslessStringConvertible {
public init?<S>(_ text: S) where S : StringProtocol
}
Заполнители типа позволяют нам указать только неоднозначную часть, оставив остальное на усмотрение процесса выведения типа.
let stringConverter = Double.init as (String) -> ?
// или
let stringConverter: (String) -> ? = Double.init
Несмотря на то, что этот пример указан в самом предложении, я бы не стал писать подобные вещи в своем коде. В этом конкретном случае я предпочитаю четко указать все типы.
В приведенных выше примерах мы используем ?
чтобы указать, что возвращаемое значение является необязательным, но вы можете убрать оттуда вопросительный знак (?
), и это не вызовет ошибки. Заполнители типов могут представлять как необязательные, так и обычные типы.
Словари
Заполнители типов можно использовать вместо ключей или значений в словарях.
Ниже мы рассмотрим пример со словарем, который хранит кэшированные изображения для каждой ячейки табличного представления, а ключами служат индексы строк.
var imageCache = [0: nil, 1: nil]
Компилятор может вывести ключ (как Int
) но не значение. Максимумом, на что он способен, будет Any?
.
Мы можем указать поставить на место значения заполнитель типа и оставить ключ пустым, поскольку Swift способен понять эту часть.
var imageCache: [: UIImage?] = [0: nil, 1: nil]
Опять же, я все-таки предпочитаю [Int: UIImage?]
этой форме.
Универсальные типы
Именно здесь я вижу наибольшую пользу от введения заполнителей типа. Поскольку функциональное программирование становится все более популярным, может наступить время, когда, используемые нами, типы приобретут форму месива со все более сложной вложенностью, и явное указание типа или оставление его пустым (_) будет в чем-то одинаковым.
Рассмотрим следующий пример кортежа:
let weirdTuple = (0, 1, Just(1).map(.description))
// (Int, Int, Publishers.MapKeyPath<Just<Int>, String>)
Если мы используем его с перечислением Result
из примера выше, как он написан сейчас, мы получим ошибку, что универсальный параметр 'Failure' не может быть выведен, и нам нужно предоставить весь тип.
let result = Result.success(weirdTuple)
// Универсальный параметр 'Failure' не может быть выведен
В этом случае, оставив тип пустым, мы сделаем код более читабельным.
let weirdTuple = (0, 1, Just(1).map(.description))
// Без заполнителя типа
let result = Result<(Int, Int, Publishers.MapKeyPath<Just<Int>, String>), Error>.success(weirdTuple)
// С заполнителем типа
let result = Result<, Error>.success(weirdTuple)
Я не могу придумать более наглядного примера, чем этот, но я надеюсь, что вы уловили суть, где это может быть очень полезно.
Опять же, заполнители типов не ограничены к применению лишь с типами, содержащими в себе другие типы. Просто лично я нахожу от них максимальную пользу именно здесь.
Вот еще несколько примеров типов, содержащих заполнители.
Array<> // массив с заполнителем типа элемента
[Int: ] // словарь с заполнителем типа значения
() -> Int // тип функции, принимающий один аргумент-заполнитель и возвращающий 'Int'
(_, Double) // кортеж с заполнителем типа и 'Double'
? // необязательная обертка заполнителя типа
Заключение
Заполнители типа — это способ упростить нам жизнь при необходимости выведения сложных типов (типов, которые содержат более одного типа в своем определении), где только часть неоднозначна.
Вы заполняете неоднозначную часть и все еще можете наслаждаться выводом типа с помощью .
Вы можете думать об этом как о нижнем подчеркивании в списке параметров замыкания, которое вы используете, чтобы игнорировать параметры, которые вам не нужны.
let dict = [1: "One"]
dict.map { _, value in
print(value)
}
Я думаю, что эту фичу все же следует использовать с осторожностью. Я считаю, что ошибиться будет намного сложнее, когда вы четко указываете тип. Если оставлять пользователям хоть какую-нибудь неопределенность, это может привести к потенциальному недопониманию и ошибкам.
В преддверии старта курса iOS Developer. Basic хочу пригласить всех желающих на бесплатный урок, в рамках которого рассмотрим, как можно создать несложный фоторедактор для iOS для простой обработки изображений, поработаем с фильтрами и цветовыми тонами. Интерфейс приложения будем создавать с использованием UIKit Autolayout.