Better self-executing closures
Очень часто мы используем self-executing closure для настройки объектов в Swift. В основном именно во view слое, реже – в других частях код-базы. И хотя такие closure невероятно удобны, их можно улучшить: уменьшить размер, не ухудшив читаемость кода.
Сегодня хотел бы рассказать вам о функции-утилите, которую я повсеместно использую в своих проектах. Функция configure принимает любой объект или значение, используя ключевое слово inout, модифицирует его по ссылке и возвращает.
Звучит достаточно просто? Так и есть. Я добавил еще атрибут discardableResult, чтобы в случае необходимости игнорировать возвращаемое значение, и rethrows, чтобы не использовать try/catch для closure, которые не выбрасывают ошибки.
Функцию можно объявить глобально в проекте или использовать мой SDK: PimineUtilities, где эта функция уже реализована.
Source / PimineSDK
#utilities #closures #intermediate
Очень часто мы используем self-executing closure для настройки объектов в Swift. В основном именно во view слое, реже – в других частях код-базы. И хотя такие closure невероятно удобны, их можно улучшить: уменьшить размер, не ухудшив читаемость кода.
Сегодня хотел бы рассказать вам о функции-утилите, которую я повсеместно использую в своих проектах. Функция configure принимает любой объект или значение, используя ключевое слово inout, модифицирует его по ссылке и возвращает.
Звучит достаточно просто? Так и есть. Я добавил еще атрибут discardableResult, чтобы в случае необходимости игнорировать возвращаемое значение, и rethrows, чтобы не использовать try/catch для closure, которые не выбрасывают ошибки.
Функцию можно объявить глобально в проекте или использовать мой SDK: PimineUtilities, где эта функция уже реализована.
Source / PimineSDK
#utilities #closures #intermediate
Как легко cделать глубокую копию (deep-copy) объекта в Swift?
В Swift есть два основных типа данных: объекты (reference types) и структуры (value types). Одно из ключевых различий между value и reference типами сводится к копированию: две переменные могут указывать на один и тот же объект, поэтому если вы попытаетесь изменить одну переменную, изменения коснутся и второй. Если вы попытаетесь проделать этот же трюк со структурами, то обнаружите, что здесь изменения копии не влияют на оригинал.
Теперь вопрос: а как сделать так, чтобы изменение в одной части проекта не влияло на другие в reference типах? Для этого нам нужно сделать глубокую копию (deep-copy) объекта. Одним из вариантов решения является использование протокола NSCopying. И хотя этот способ имеет место быть, он создает много шаблонного (boilerplate) кода.
Сегодня я хочу показать вам, как для решения этой задачи можно использовать Codable. Идея достаточно простая: сначала мы кодируем объект в Data, а дальше из Data в нужный нам тип. Поскольку Data является value типом в Swift, при копировании выполняется deep-copy по умолчанию. На выходе мы получим новый объект с теми же данными, что и оригинал.
Ссылка
#tips #codable #intermediate
В Swift есть два основных типа данных: объекты (reference types) и структуры (value types). Одно из ключевых различий между value и reference типами сводится к копированию: две переменные могут указывать на один и тот же объект, поэтому если вы попытаетесь изменить одну переменную, изменения коснутся и второй. Если вы попытаетесь проделать этот же трюк со структурами, то обнаружите, что здесь изменения копии не влияют на оригинал.
Теперь вопрос: а как сделать так, чтобы изменение в одной части проекта не влияло на другие в reference типах? Для этого нам нужно сделать глубокую копию (deep-copy) объекта. Одним из вариантов решения является использование протокола NSCopying. И хотя этот способ имеет место быть, он создает много шаблонного (boilerplate) кода.
Сегодня я хочу показать вам, как для решения этой задачи можно использовать Codable. Идея достаточно простая: сначала мы кодируем объект в Data, а дальше из Data в нужный нам тип. Поскольку Data является value типом в Swift, при копировании выполняется deep-copy по умолчанию. На выходе мы получим новый объект с теми же данными, что и оригинал.
Ссылка
#tips #codable #intermediate
Используем String в качестве SectionIdentifierType для UITableViewDiffableDataSource
UITableViewDiffableDataSource предоставляет абсолютно новое API для работы с данными таблицы. Если вы уже имеете опыт использования, то знаете, что нам нужно предоставить два дженерика: SectionIdentifierType, ItemIdentifierType.
В качестве ItemIdentifierType обычно выступает модель, которую мы планируем отображать в таблице. SectionIdentifierType же, как следует из названия, описывает секции нашей таблицы. Классическим подходом будет использование отдельного enum для этих задач, если секций несколько. Тем не менее, если мы имеем дело только с одной, то таким образом создадим ненужный шаблонный (boilerplate) код.
Поскольку строки в Swift Hashable по умолчанию, можно использовать String в качестве SectionIdentifierType.
Source
#tips #tableView #intermediate
UITableViewDiffableDataSource предоставляет абсолютно новое API для работы с данными таблицы. Если вы уже имеете опыт использования, то знаете, что нам нужно предоставить два дженерика: SectionIdentifierType, ItemIdentifierType.
В качестве ItemIdentifierType обычно выступает модель, которую мы планируем отображать в таблице. SectionIdentifierType же, как следует из названия, описывает секции нашей таблицы. Классическим подходом будет использование отдельного enum для этих задач, если секций несколько. Тем не менее, если мы имеем дело только с одной, то таким образом создадим ненужный шаблонный (boilerplate) код.
Поскольку строки в Swift Hashable по умолчанию, можно использовать String в качестве SectionIdentifierType.
Source
#tips #tableView #intermediate
lazy теперь работает в локальном контексте (local context)
Используя ключевое слово lazy, мы можем реализовать свойство, начальное значение которого не вычисляется до первого использования. Начиная со Swift 5.5, можно использовать lazy внутри функций. На практике это позволит проводить оптимизацию, когда мы имеем какое-то условия if-else и не хотим проводить затратные вычисления на то, что нам не нужно.
Source
#languageGuide #lazy #intermediate
Используя ключевое слово lazy, мы можем реализовать свойство, начальное значение которого не вычисляется до первого использования. Начиная со Swift 5.5, можно использовать lazy внутри функций. На практике это позволит проводить оптимизацию, когда мы имеем какое-то условия if-else и не хотим проводить затратные вычисления на то, что нам не нужно.
Source
#languageGuide #lazy #intermediate
Typealiases с дженериками
Мало кто использует эту особенность Swift в своих проектах. Однако она может здорово улучшить некоторые участки кода и убрать ненужный шаблонный (boilerplate) код.
Typealiases могут использоваться в комбинации с generic типами.
Что самое интересное, мы можем явно указать конкретные типы для некоторых (или всех) generic параметров существующего типа. В обратную сторону, кстати, это не работает: новые параметры в typealiase вводить нельзя.
Source
#tips #generics #intermediate
Мало кто использует эту особенность Swift в своих проектах. Однако она может здорово улучшить некоторые участки кода и убрать ненужный шаблонный (boilerplate) код.
Typealiases могут использоваться в комбинации с generic типами.
Что самое интересное, мы можем явно указать конкретные типы для некоторых (или всех) generic параметров существующего типа. В обратную сторону, кстати, это не работает: новые параметры в typealiase вводить нельзя.
Source
#tips #generics #intermediate
Как сделать аббревиатуру для числа?
Когда нужно отобразить огромное число в пользовательском интерфейсе (скажем, количество лайков или подписчиков), зачастую показывать число полностью не имеет смысла. Гораздо удобнее использовать агрегированное представление в виде "12M" или "1.5K".
Данное расширение позволяет получить в виде аббревиатуры числа вплоть до квадриллиона. Если кратко, то логика такая:
1. Есть список аббревиатур "KMBTQ". Проходимся по нему с конца, потому что представление должно быть как можно компактнее.
2. Для каждой аббревиатуры определяем фактор. Для тысяч это ЧИСЛО/10^3 (10 в степени 3).
3. Если фактор меньше нуля, число слишком маленькое для этой аббревиатуры, поэтому двигаемся дальше.
4. Если фактор больше нуля, значит к фактору добавляем нужную аббревиатуру и получаем результат.
Source
#handyExtensions #int #intermediate
Когда нужно отобразить огромное число в пользовательском интерфейсе (скажем, количество лайков или подписчиков), зачастую показывать число полностью не имеет смысла. Гораздо удобнее использовать агрегированное представление в виде "12M" или "1.5K".
Данное расширение позволяет получить в виде аббревиатуры числа вплоть до квадриллиона. Если кратко, то логика такая:
1. Есть список аббревиатур "KMBTQ". Проходимся по нему с конца, потому что представление должно быть как можно компактнее.
2. Для каждой аббревиатуры определяем фактор. Для тысяч это ЧИСЛО/10^3 (10 в степени 3).
3. Если фактор меньше нуля, число слишком маленькое для этой аббревиатуры, поэтому двигаемся дальше.
4. Если фактор больше нуля, значит к фактору добавляем нужную аббревиатуру и получаем результат.
Source
#handyExtensions #int #intermediate
Опциональное декодирование элементов массива с помощью Decodable
Сегодня мы разберем случай, когда нужно отобразить массив данных с сервера, даже если некоторые элементы этого массива декодировать не удается. Предположим, в приложении есть секция (жанр музыки). В этой секции есть много разных элементов (песен). Если случится так, что какая-то песня придет с сервера с ошибкой (или с ней что-то будет не так), мы все равно сможем отобразить секцию без проблемного трека.
Для этого нам понадобится создать дженерик класс FailableDecodable с типом, который нужно будет десериализовать. Далее, используя singleValueContainer, получаем контейнер с декодера и пробуем получить нужную нам модель с помощью try? container.decode(Base.self).
Чтобы выполнить само декодирование элементов массива, вместо обычного типа данных, указываем тип в обертке с FailableDecodable и проходимся по нему с помощью compactMap, получая переменную base.
Таким образом, сначала мы получили массив с опциональными элементами, где могут встречаться nil, а дальше профильтровали этот массив, оставив только non-nil значения.
Source
#tips #decodable #intermediate
Сегодня мы разберем случай, когда нужно отобразить массив данных с сервера, даже если некоторые элементы этого массива декодировать не удается. Предположим, в приложении есть секция (жанр музыки). В этой секции есть много разных элементов (песен). Если случится так, что какая-то песня придет с сервера с ошибкой (или с ней что-то будет не так), мы все равно сможем отобразить секцию без проблемного трека.
Для этого нам понадобится создать дженерик класс FailableDecodable с типом, который нужно будет десериализовать. Далее, используя singleValueContainer, получаем контейнер с декодера и пробуем получить нужную нам модель с помощью try? container.decode(Base.self).
Чтобы выполнить само декодирование элементов массива, вместо обычного типа данных, указываем тип в обертке с FailableDecodable и проходимся по нему с помощью compactMap, получая переменную base.
Таким образом, сначала мы получили массив с опциональными элементами, где могут встречаться nil, а дальше профильтровали этот массив, оставив только non-nil значения.
Source
#tips #decodable #intermediate