├── .github
└── FUNDING.yml
├── roadmap
├── README.md
├── Git-cheat-sheet.md
├── courses
│ └── Courses list.md
├── memory management
│ ├── Copy on write.md
│ ├── Shallow and deep copying.md
│ ├── ARC
│ │ └── Side table and object reletionship.md
│ └── Delayed deallocation.md
├── swift
│ ├── Push notifications.md
│ ├── uikit
│ │ ├── Frame and bounds.md
│ │ └── App and view controller lifecycle.md
│ ├── Method dispatch.md
│ ├── Low level virtual machine.md
│ ├── Auto layout.md
│ ├── AnyObject, Any и any.md
│ ├── Test Review Tips.md
│ ├── Hashable.md
│ ├── The SwiftUI render loop.md
│ ├── Optional Chaining.md
│ ├── Closures.md
│ └── Optional.md
├── Articles.md
├── multithreading and concurrency
│ ├── Run loops.md
│ └── Actors.md
├── design principles
│ ├── Protocol Oriented Programming.md
│ └── Solid.md
├── books
│ └── Book list.md
└── data structures
│ └── Arrays.md
├── README.md
├── other
├── Job resources.md
└── Interview Questions.md
└── algorithms
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: ["https://boosty.to/somestay"]
2 |
--------------------------------------------------------------------------------
/roadmap/README.md:
--------------------------------------------------------------------------------
1 | # 👨💻 Карта развития для iOS разработчика
2 |
3 | Скоро здесь будет актуальное описание, для этого блока : - )
4 |
--------------------------------------------------------------------------------
/roadmap/Git-cheat-sheet.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 | 
5 |
6 | 
7 | 
8 |
9 | 
10 |
11 | 
12 |
13 | 
14 |
15 | 
16 |
17 | 
18 |
--------------------------------------------------------------------------------
/roadmap/courses/Courses list.md:
--------------------------------------------------------------------------------
1 | # 📺 Курсы:
2 | Если вам необходимо изучить [computer science](https://ru.wikipedia.org/wiki/Информатика), то курс от Cronis - это то, что вам нужно. Актуальный материал, хорошая подача и изложение, очень достойный материал!
3 | - 🧡 middle
4 | - `дополнительные знания`
5 | - [Ссылка](https://cronis.by)
6 | > Преподаватель: [Юрий Петранков](https://cronis.by/yp/), [Илья Яскевич](https://cronis.by/iy/)
7 | #
8 | Этот курс поможет подготовиться к собеседованию по `системному дизайну` на должность iOS, проведя вас через гипотетический архитектурный дизайн приложения электронной коммерции, приложения для обмена сообщениями, ну и наконец сам Instagram и многое другое.
9 | - 🤎 middle+
10 | - `дополнительные знания`
11 | - [Cсылка](https://iosinterviewguide.com/system-design-interview)
12 | > Автор: - [Alex Bush](https://www.linkedin.com/in/alexvbush/)
13 | #
14 | Два замечательных видео, по system design на iOS.
15 | - 🤎 middle+
16 | - `основные знания`
17 | - [Собеседование Senior iOS-разработчика](https://www.youtube.com/watch?v=CadPMJJsl0E)
18 | - [iOS System Design Instagram Example](https://www.youtube.com/watch?v=irUTptJWc9o)
19 |
20 | P.S. [`Pointfree`](https://www.pointfree.co) и (`Swift Talk`](https://talk.objc.io) будут добавлены позже ибо надо распарсить 200 и 300 воркшопов :)
21 |
--------------------------------------------------------------------------------
/roadmap/memory management/Copy on write.md:
--------------------------------------------------------------------------------
1 | # **Copy On Write**
2 |
3 | - Копирование при записи - это метод оптимизации, который помогает повысить производительность при копировании типов значений.
4 | #
5 |
6 | - Допустим, мы копируем одну строку или Int или, возможно, любой другой тип значения? В этом случае мы не столкнемся с какими-либо решающими проблемами производительности.
7 | Но как быть, когда мы копируем массив из тысяч элементов? Это все равно не создаст проблем с производительностью?
8 | Что, если мы просто скопируем его и не внесем никаких изменений в этот экземпляр?
9 | Разве это не лишняя память, которую мы использовали просто пустая трата в этом случае?
10 | #
11 | - На сцену выходит понятие **Copy in Write** - при копировании каждая ссылка указывает на один и тот же адрес памяти. Только когда одна из ссылок изменяет базовые данные, именно в этот момент, момент модификации, Swift фактически копирует исходный экземпляр и вносит изменения.
12 | То есть, будь то **deep copy** или **shallow copy**, новая копия не будет создана, пока мы не внесем изменения в один из объектов.
13 |
14 | #
15 | Пример:
16 | 
17 |
18 | #
19 | Реализация COW своими руками:
20 | 
21 |
22 | # **Полезные ссылки**
23 | - [Apple docs](https://github.com/apple/swift/blob/main/docs/OptimizationTips.rst#id28)
24 |
--------------------------------------------------------------------------------
/roadmap/swift/Push notifications.md:
--------------------------------------------------------------------------------
1 | # Анатомия пуш нотификаций
2 |
3 | Push-уведомление — это короткое сообщение, состоящее из токена девайса, полезной нагрузки (payload) и ещё некоторой информации.
4 | Полезная нагрузка — это актуальные данные, которые будут отправляться на девайс.
5 |
6 | 
7 |
8 | - iOS запрашивает у сервера `Apple Push Notification Service (APNS)` токен девайса. Приложение получает токен девайса.
9 | Можно считать, что токен – это адрес для отправки push-уведомлений. Приложение отправляет токен девайса на ваш сервер.
10 | Когда произойдёт какое-либо событие для вашего приложения, сервер отправит push-уведомление в APNS. APNS отправит push-уведомление на девайс пользователя.
11 |
12 | - Когда пользователь получит push-уведомление, появится сообщение, и/или будет воспроизведён звуковой сигнал, и/или обновится бейдж на иконке приложения.
13 | Пользователь может открыть приложение из уведомления. Приложение получит контент push-уведомления и сможет обработать его.
14 |
15 | Ваш сервер должен преобразовать полезную нагрузку в JSON-словарь. Полезная нагрузка для простого push-сообщения выглядит следующим образом:
16 |
17 | ```{ "aps": { "alert": "Hello, world!", "sound": "default" } }```
18 |
19 | > Полезная нагрузка — это словарь, который состоит из, по крайней мере, одной пары «ключ-значение» «aps», значение которой само по себе является словарём.
20 | > В примере выше «aps» содержит два поля: «alert» и «sound». Когда на девайс придёт push-уведомление, отобразится всплывающее сообщение
21 | > с текстом «Hello, world!» и будет воспроизведён стандартный звуковой сигнал.
22 |
23 | 
24 |
25 | # Полезное:
26 | - [Когда почта доставляет: боремся с потерями push-уведомлений в iOS / Ася Свириденко](https://www.youtube.com/watch?v=SVCMbPIuy8w&t=1471s)
27 | - [Тестирование push-уведомлений в мобильных приложениях](https://habr.com/ru/company/youla/blog/553762/)
28 | - [The Push Notifications primer](https://www.wwdcnotes.com/notes/wwdc20/10095/)
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🤓 Для кого это?
2 |
3 | Эти материалы будут полезны для:
4 | * Для любого, кто захочет стать iOS разработчиком.
5 | * Для iOS разработчиков, которые готовятся к собеседованию.
6 | * Для iOS разработчиков, которым нужно составить вопросы для собеседования.
7 | * Для iOS разработчиков, которые находятся на уровне trainee|junior|middle и хотят вырасти.
8 |
9 | # 👨🎓 Материалы:
10 | - [Изучение iOS. Подготовка к собеседованиям.](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/Articles.md)
11 | - [Книги для изучения iOS и общего программирования](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/books/Book%20list.md)
12 | - [Курсы для изучения iOS и общего программирования](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/courses/Courses%20list.md)
13 | - [Изучение алгоритмов](https://github.com/SomeStay07/iOS-Developer-Roadmap/tree/main/algorithms)
14 | - [Ресурсы для поиска работы](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/other/Job%20resources.md)
15 | - [Собеседование - вопросы и ответы](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/other/Interview%20Questions.md)
16 |
17 | # 👨💻 Карта развития для iOS разработчика
18 |
19 | 📘 Главное меню:
20 |
21 | Перед использованием roadmap’a ***рекомендую изучить*** работу с материалом
22 | 1. То с чего нужно начать - первые шаги
23 | - Изучение ЯП Swift, документации
24 | - Изучение GIT
25 | - Изучить Xcode и его тулзы
26 | - Общие советы, которые могут помочь в изучении
27 | 2. Продолжение первых шагов - переход к основам
28 | - Базовые понятия
29 | - Изучение UIKit фреймворка
30 | - Изучение платформы
31 | - DI
32 | - Изучение паттернов проектирования
33 | - Архитектурные паттерны
34 | - Сетевой слой
35 |
36 |
37 | # 👨🏫 Для тех кто хочет больше контента
38 | Всем привет, сорри, что забросил, так как пока-что занимаюсь продолжением этого детища на [закрытом бусти](https://boosty.to/somestay), там кстате намного больше и качественнее, [можете сами убедиться](https://www.notion.so/fb0f0752c9ad47c0a3c289557f56a92f?pvs=4), но не переживайте, раз в квартал буду сюда из бусти закидывать некоторые вещи и тем самым обновлять данных хаб, поэтому до скорого :)
39 |
--------------------------------------------------------------------------------
/other/Job resources.md:
--------------------------------------------------------------------------------
1 | # Начните с резюме
2 |
3 | Воспользовался услугой бесплатного ревью резюме на **https://www.topcv.com/**. Получил крутой фидбек, крайне рекомендую. По результатам постарался переписать опыт работы, убрав воду и сделав акцент на измеримых результатах (достижениях). Ещё посоветовали убрать значения годов обучения в универе дабы исключить эйджизм.
4 |
5 | # Источники поиска вакансий
6 |
7 | Те, которые не сработали совсем и, на мой взгляд, не стоили потраченного времени:
8 |
9 | - **https://dice.com/**, **[https://indeed.com](https://indeed.com/)** -- очень много вакансий в USA, много удаленки, но в пределах страны.
10 |
11 | - **[https://reed.co.uk](https://reed.co.uk/)** - то же самое, что и выше, но для UK. Изредка встречаются вакансии на удаленку извне страны, но как правило для резидентов еврозоны.
12 |
13 | - **[https://weworkremotely.com](https://weworkremotely.com/)** -- для размещения iOS вакансий ресурс непопулярен.
14 |
15 | Те, которые показались немного сомнительными, но могу их рекомендовать:
16 |
17 | - **https://t.me/evacuatejobs** -- не так много iOS вакансий, но парочку интересных накодил, поэтому советую посматривать.
18 |
19 | - **https://getmatch.ru/** -- хорошая подборка вакансий в интересных компаниях. Среди работодателей преимущественно российские, если это не проблема, то однозначно рекомендую.
20 |
21 | - **https://t.me/Remotework_RW** - вкусные вакансии по вкусным ценам - хз правда или нет, но попробовать стоит.
22 |
23 | Наконец те, что давали на выходе вакансии для отклика. Общий очевидный совет -- обязательно подписаться на email рассылки/уведомления по своему поисковому запросу
24 |
25 | - **https://geekjob.ru** -- дает возможность анонимного и пассивного поиска, большинство ВУ.
26 |
27 | - **https://relocate.me/** -- нашел парочку очень интересных вакансий.
28 |
29 | - **https://t.me/mobile_jobs** - ну тут само название говорит за себя, поэтому однозначно советую.
30 |
31 | - **[https://hh.ru](https://hh.ru/)** -- думаю описывать смысла нет, но скажу так, что парочка моих друзей нашли тут валютную удаленку из США, Израиль, Саудовская Араваия. Если вы ищите только ВУ - не поленитесь и просмотрите вакансии на хх тоже.
32 |
33 | - **[https://linked.in](https://linked.in/)** -- с одной стороны бесконечный пул самых разных вакансий. С другой, искать удаленку без релокации очень сложно: ты можешь указать remote в поиске, но в выборку попадут и вакансии с удаленкой в рамках страны и их будет абсолютное большинство. Фильтрация по локации (в случае релокейта) тоже крайне неудобна. В итоге поиск вакансий на линкедине сродни поиску золотого песка на дачном участке у бабушки в Калужской области. Общий совет -- игнорировать вакансии без "100% REMOTE" в названии. В описании оставшихся вакансий сразу искать либо в самом начале, либо в самом конце инфу о требованиях к локации соискателя. У меня получилось 11 откликов за 3 недели, ответ и дальнейшее общение с работодателем было только по одному из них.
34 |
--------------------------------------------------------------------------------
/roadmap/swift/uikit/Frame and bounds.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Frame
4 | 🖼 Расположение и размер view с использованием системы координат родительского представления (важно для размещения представления в superview).
5 | Проще говоря про фрейм вспоминают стену и рамку картину. Стена — это супервьюха, а рамка — это вью.
6 |
7 | # Bounds
8 | 🟩 Bounds — местоположение и размер представления с использованием его собственной системы координат (важно для размещения содержимого View или subview внутри него)
9 |
10 | # Когда использовать frame и когда использовать bounds
11 | Поскольку frame связывает местоположение **view** в **superview**, используйте его при внесении внешних изменений: ширины или вычисления расстояния между **view** и вершиной его **родительского view**.
12 |
13 | Используйте bounds для внутренних изменений: рисования или организации **subview** в пределах **view**. Также используйте **bounds** для получения размера **view**, если вы сделали какие-то преобразование для него.
14 |
15 | # Что будет при повороте элемента?
16 |
17 | До сих пор ширина и высота frame и bounds оставались одинаковыми. Хотя это не всегда так.
18 | Посмотрим что случится если мы повернем view на 20 градусов по часовой стрелке.
19 | (Вращение выполняется с помощью преобразования, описанного в документации, view и layer примерах.)
20 |
21 | 
22 |
23 | ```
24 | Frame
25 | origin = (x: 20, y: 52) // Это примерные значения.
26 | width = 118
27 | height = 187
28 |
29 | Bounds
30 | origin = (x: 0, y: 0)
31 | width = 80
32 | height = 130
33 | ```
34 |
35 | Видно, что bounds остался прежним. Он до сих пор не знает что произошло. Однако, все значения frame изменились.
36 |
37 | Более подробно в этой [статье](https://vmityuklyaev.medium.com/различие-frame-и-bounds-в-ios-frame-vs-bounds-in-ios-4e5aee5ed477)
38 |
39 | # Frame vs Bounds: Как работает UIScrollView?
40 | 
41 |
42 | Частый вопрос на собеседовании: "А когда у нас может поменяться bounds?".
43 | Ответ — в `ScrollView`. На изменении своих координат строится вся модель скроллинга.
44 |
45 | Что будет с сабвьюхами, если мы изменим `frame` или `bounds` для их супервью?
46 |
47 | 1. Изменяя **frame** также меняются его сабвьюхи.
48 |
49 | 2. Изменяя **bounds** у супервью наша позиция остается неизменной, но сабвьюхи внутри будто смещаются.
50 |
51 | Это происходит потому, что супервью имеет свой фрейм относительно своего супервью. И фактически меняя **bounds** мы меняем начало координат по отношению к его собственным.
52 |
53 | Этот эффект похож на эффект пленки и фиксированной камеры.
54 | 📸 Камера — это вью. Она неподвижна и стоит на месте.
55 | 🎞 Пленка — это контент внутри, который позиционируется относительно системы координат.
56 |
57 | Более подробно в [этой статье](https://oleb.net/blog/2014/04/understanding-uiscrollview/)
58 |
--------------------------------------------------------------------------------
/roadmap/swift/Method dispatch.md:
--------------------------------------------------------------------------------
1 | `Диспетчеризация` - Каждый раз при вызове метода в Swift используется функция Method Dispatch.
2 | Он сообщает приложению, где найти метод в памяти, прежде чем он будет выполнен в CPU.
3 | Особенно при написании Swift-кода очень важно понимать концепцию, лежащую в основе метода Dispatch, поскольку это может привести к проблемам с производительностью.
4 |
5 | 1️⃣ `Static Dispatch` - также известный как Direct Dispatch, является самым быстрым и эффективным типом отправки метода.
6 | Статически отправляемые методы могут выполняться немедленно во время выполнения, поскольку компилятор знает точный адрес памяти во время компиляции.
7 | Это также означает, что компилятор может выполнять различные виды оптимизации, такие как `inlining`, что приводит к еще более быстрому времени выполнения во время выполнения.
8 |
9 |
10 | 2️⃣ `Dynamic Dispatch` - делится на две категории: Table Dispatch и Message Dispatch:
11 |
12 | 👉 Table Dispatch - во время компиляции для каждого класса строится так называемая `Virtual Table`, которая содержит массив указателей функций,
13 | соответствующих реализации в каждом классе. Во время диспетчеризации вызова метода он выполняет только одну арифметическую операцию - вычисление
14 | фактического адреса функции на основе смещения функции в таблице следящих элементов базового класса и расположения таблицы следящих элементов класса
15 | объектов. Это относительно дешевая операция по сравнению с Obj-C. Это объясняет, почему "чистый" Swift приближается к производительности C++.
16 |
17 | 👉 Message Dispatch - это самый динамичный, но и самый медленный способ отправки.
18 | Чтобы найти метод, расположенный за сообщением диспетчеризации, среди выполнения необходимо выполнить обход всей иерархии классов,
19 | чтобы определить, какой метод следует вызвать. Однако это также означает, что можно изменить поведение программы во время выполнения,
20 | что позволяет работать таким методам, как `Swizzling`.
21 | Objective-C в значительной степени зависит от отправки сообщений, а также предоставляет эту функциональность Swift через среду выполнения Objective-C.
22 |
23 | `Важное дополнение!` - message dispatch не всегда является самым долгим, т.к можно закешировать и после первого обращения так и происходит, поэтому `важно` понимать это, что не всегда самое долгое и не забыть!
24 |
25 | `inlining` - способ оптимизации, при котором вызов функции заменяется непосредственно её телом.
26 | По сути компилятор помещает новую копию функции в каждое место, где она вызывается.
27 | Код со встроенными функциями работает немного быстрее обычного, поскольку пропадают дополнительные действия по вызову функций,
28 | однако же при этом увеличивается расход памяти. Если функция встроена 10 раз, то в код будет вставлено 10 копий функции.
29 | Поэтому встраивание лучше всего подходит для небольших часто используемых функций.
30 |
31 | `Swizzling` - позволяет подменить метод вашим прямо в runtime, притом оставляя оригинальную имплементацию доступной. [Вот тут прям более детально](https://habr.com/ru/post/274545/)
32 |
33 | 
34 |
--------------------------------------------------------------------------------
/algorithms/README.md:
--------------------------------------------------------------------------------
1 | # Изучаем алгоритмы
2 |
3 | - [Swift Algorithm Club | Github](https://github.com/raywenderlich/swift-algorithm-club)
4 | - Цель этого проекта - объяснить, как работают алгоритмы. Основное внимание уделяется четкости и удобочитаемости кода.
5 | - [Порешать задачки можно тут | Leetcode](https://leetcode.com/tag/prefix-sum/)
6 | - Обязательно набить руку на `easy`.
7 | - Желательно попробовать `medium` - комбинация простых алгоритмов/структур данных.
8 | - `Hard` - необязательно, самые хардовые алгоритмы.
9 | - [Алгосики для самых маленьких | Youtube](https://www.youtube.com/c/ViktorKarpovCodes)
10 | - Простым и понятным языком, автор канала погружает в мир алгоритмов.
11 | - [Грокаем алгоритмы](https://www.ozon.ru/product/grokaem-algoritmy-illyustrirovannoe-posobie-dlya-programmistov-i-lyubopytstvuyushchih-139296295/?sh=nqY4OyAURg)
12 | - Эта книга написана для тех, кому нужен быстрый старт в основы алгоритмизации и программирования. Данная книга подойдёт как для тех, кто только начинает программировать, так и для тех, кто уже является опытным разработчиком, так как материал в книге всегда является актуальным и востребованным.
13 | - [Introduction to Algorithms](https://www.amazon.de/-/en/Thomas-H-Cormen/dp/0262033844)
14 | - Данный труд удачно совмещает в себе описание разнообразных алгоритмов и отличное качество изложения, делающее книгу доступной для читателей разного уровня подготовки. Все алгоритмы описаны простым языком и сопровождены примерами псевдокода.
15 | - [Открытое алгоритмическое собеседование | Youtube](https://www.youtube.com/watch?v=aYuAd-IDigc)
16 | - Краткий конспект по видео:
17 | - Кандидату нужно:
18 | - Уметь оценивать решение / код по затратам на время и память (асимптотитка).
19 | - Уметь использовать базовые техники программирования:
20 | - Рекурсия,
21 | - Базовые структуры данных (динамический массив, список, стэк, ассоциативный массив)
22 | - Редко, но бывают: графы, динамическое программирование, деревья поиска.
23 | - Проверяется:
24 | - Умение кандидата уточнить задачу при её постановке.
25 | - Умение кандидата грамотно прояснить решение задачки.
26 | - Умение составлять тест-кейсы на написанный им же код к задачке.
27 | - Умение написать правильный код (без синтаксических ошибок).
28 | - Умение проверить свой код осмысленным прочтением вслух (с комментариями).
29 | - Умение исправить баг, а также проанализировать код на наличие скрытых брешей, которые могут проявиться в будущем и потребуют дописывания «костылей» программистом: например, дополнительных if-конструкций на обработку таких промахов, что, по сути своей, будет являться следствием недостаточного анализа предварительного решения при написании кода изначально.
30 | - Дополнительно:
31 | - Необходимо помнить про сложности, связанные с операциями со структурами данных, ассоциативным массивом, ассоциативным массивом на хэш-таблице, ассоциативным массивом на дереве поиска, операциями на линейных структурах данных, сортировку.
32 | - Перед непосредственным написанием кода кандидат должен озвучить вариант своего решения (своей идеи) интервьюеру!
33 |
34 | - [Тренировка алгоритмов от Яндекса, 8 лекций с домашними заданиями и 4 разбора заданий.]
35 |
--------------------------------------------------------------------------------
/roadmap/memory management/Shallow and deep copying.md:
--------------------------------------------------------------------------------
1 |
2 | # **Shallow and Deep copy**
3 |
4 | - **Shallow copies** - Копирование объектов, таких как объекты коллекции, которые могут содержать другие объекты, также должно выполняться с осторожностью. Как и следовало ожидать, использование оператора = для копирования этих объектов приводит к дублированию ссылки на объект.
5 |
6 | В случае этих объектов `shallow copies` - означает, что создается новый объект коллекции, но содержимое исходной коллекции не дублируется - в новый контейнер копирует только ссылки на объекты. Этот тип копирования полезен, если, например, у вас есть неизменяемый массив, и вы хотите изменить его порядок. В этом случае вы не хотите дублировать все содержащиеся объекты, потому что их не нужно изменять - а зачем тратить лишнюю память? Вы просто хотите изменить набор включенных объектов. Здесь действуют те же риски, что и при копировании ссылок на объекты с простыми типами.
7 |
8 | 
9 |
10 | Пример:
11 | - human1 - объект типа **Human**, который является классом и тем самым имеет **reference type**.
12 | - let human2 = human1 — создаётся **shallow copy**
13 | - Это видно тем, что на изображении объект **human1** и **human2** имеют одинаковый адресс в памяти.
14 |
15 | #
16 | - **Deep copy** - если вы хотите создать совершенно новый объект, вы должны выполнить **deep copy**. **Deep copy** дублирует объект, а также содержимое всех содержащихся в нем объектов. Текущий **Core Foundation** включает функцию, которая выполняет глубокое копирование списка свойств(см. CFPropertyListCreateDeepCopy). Если вы хотите создавать глубокие копии других структур, вы можете выполнить глубокое копирование самостоятельно, рекурсивно спускаясь в объект и копируя все его содержимое одно за другим. Позаботьтесь о реализации этой функции, поскольку составные объекты могут быть рекурсивными - они могут прямо или косвенно содержать ссылку на себя - что может вызвать рекурсивный цикл.Т.е:
17 | - При использовании глубокой копии любой объект, на который указывает источник, копируется, а копия - место назначения. Так будут созданы два совершенно отдельных объекта.
18 | - Коллекции - глубокая копия коллекции представляет собой две коллекции с дублированием всех элементов исходной коллекции.
19 | - Менее подвержен **race conditions** и хорошо работает в многопоточном окружении - т.е изменения в одном объекте не окажут влияния на другой объект.
20 | - **Value type** копируются **deeply**.
21 | - Создает неглубокую копию, если объект, который назначается, содержит вложенные ссылочные типы.
22 | - Serializing/De-serializing — создает истинную глубокую копию — use Codable/Archive-Unarchive
23 |
24 | 
25 |
26 | Пример:
27 | - Класс **Human** реализует протокол **NSCopying** и имплементирует функцию **copy()**. **human1** - назначается **human2** с помощью метода copy(). Это создаст глубокую копию **human1**, а затем назначит эту копию **human2**, то есть будет создан совершенно новый объект.
28 | - Как видно на рисунке, **human1** и **human2** имеют разный адресс в памяти.
29 |
30 | # **Подытожив**
31 | 
32 |
33 | - **Дополнительные материалы**
34 | - [Apple documentations](https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/CopyFunctions.html)
35 |
--------------------------------------------------------------------------------
/roadmap/swift/Low level virtual machine.md:
--------------------------------------------------------------------------------
1 | # Компилятор
2 |
3 | 
4 |
5 | Компилятор в широком понимании — программа, преобразующая код из одного языка в другой. Но чаще компиляцией называют преобразование исходного кода именно в машинный (или в другое низкоуровневое представление), который можно потом использовать для создания исполняемого файла.
6 |
7 | Делится на три части:
8 | - `Frontend` - задачей front end’а является преобразование языка высокого уровня (например, Swift’а) в intermediate representation (IR).
9 | - `Middle` - ответственен за оптимизацию этого промежуточного представления.
10 | - `Backend` - преобразует это представление в машинный код под конкретную архитектуру.
11 |
12 | # Front end
13 | На вход компилятору поступает код, написанный на языке высокого уровня. В данном случае это будет Swift.
14 |
15 | Первый этап называется Parsing. Парсер ответственен за генерацию абстрактного синтаксического дерева — AST (Abstract Syntax Tree) без какой-либо семантической информации о типах.
16 | - Затем наступает очередь семантического анализа (Semantic analysis). В результате чего формируется уже семантически корректное типизированное синтаксическое дерево. На этом этапе определяется наличие семантических ошибок.
17 | - На следующем шаге происходит импорт кода, написанного на Objective-C и C, в Swift’овое представление. За это отвечает ClangImporter.
18 | - И на последнем этапе происходит генерация Swift Intermediate Language в его первой форме представления.
19 |
20 | 
21 |
22 | # Middle
23 | - Первая форма SIL поступает на вход оптимизатору. Который выполняет анализ, оптимизацию и трансформацию этой формы в каноническую.
24 | - Сначала на шаге трансформации выполняется диагностика потока данных и гарантированные преобразования, повышающие производительность программы (определяется использование неинициализированных переменных, проверяется достижимость всех узлов графа потока данных).
25 | - Дальше SILOptimizer выполняет высокоуровневые доменно-специфичные оптимизации для базовых типов контейнеров. Также он ответственен за девиртуализацию функций, оптимизацию ARC и спецификацию дженериков.
26 | - После этого уже генерируется промежуточное представление **LLVM**, которое поступает на вход **Back-end’у**.
27 |
28 | 
29 |
30 | # Backend
31 | - Почему-то многие, когда говорят о back-end’е LLVM, имеют ввиду сам LLVM. Хотя на деле этим back-end’ом является LLC (LLVM Static Compiler).
32 | - LLC выполняет уже специфичные для конкретных архитектур оптимизации — использование меньшего места на диске, повышение скорости работы, понижение энергозатрат.
33 | Также он генерирует машинный код под конкретную архитектуру (x86, ARM, MIPS, и т.д.). Собирает объектные файлы в конечное приложение, библиотеку или фрэймворк.
34 |
35 | 
36 |
37 | 
38 |
39 | # Подробные статьи
40 | - [Статья](https://habr.com/ru/company/e-legion/blog/438204/) номер один
41 | - [Статья](https://habr.com/ru/company/e-legion/blog/438664/) номер два
42 | - [Статья](https://habr.com/ru/company/e-legion/blog/438696/) номер три
43 | - [Статья](https://habr.com/ru/company/e-legion/blog/440078/) номер четыре
44 |
--------------------------------------------------------------------------------
/other/Interview Questions.md:
--------------------------------------------------------------------------------
1 | # Вопросы по общему программированию.
2 | - Рассказать про принципы SOLID, YAGNI, KISS. [Вот можно посмотреть](https://www.youtube.com/watch?v=TxZwqVTaCmA) так же [последняя глава](https://www.chitai-gorod.ru/catalog/book/830585/) в этой книги отлично раскрывает эти принципы.
3 | - Рассказать про принципы [ООП](https://www.youtube.com/watch?v=-6DWwR_R4Xk): наследование, инкапсуляция, полиморфизм и абстракция.
4 | - В чем разница между композицией и наследованием? Хорошая статья для изучения [тут](https://habr.com/ru/post/325478/), [пример с кодом тут](https://www.avanderlee.com/swift/composition-inheritance-code-architecture/) и еще вот [тут.](https://betterprogramming.pub/swift-favor-composition-over-inheritance-the-baseviewcontroller-case-f598064bda6)
5 | - Разница между `mock` и `stub` для тестирования, изучить можно [вот тут](https://stackoverflow.com/questions/3459287/whats-the-difference-between-a-mock-stub).
6 | - Проблемы ООП и зачем нам нужно POP, [почитать тут](https://habr.com/ru/post/473798/)
7 | - DI, IoC и DIP, спасибо Jonfir'u [за чтиво](https://jonfir.github.io/posts/ioc-ios/#ioc)
8 |
9 | # Устройство памяти.
10 | - Рассказать про виды памяти.
11 | - Разница между `heap` и `stack`.
12 | - Разница между `value` и `reference` типами.
13 | - Когда использовать `класс` для работы? Когда использовать `структуру` для работы?
14 | - Как положить `value` типы в хип?
15 | - Как положить `reference` типы на стек?
16 | - Что такое `Экзистенциальный контейнер`?
17 | - Что такое `Inout аргумент`?
18 | - [Вот здесь](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/memory%20management/Data%20type.md) можно про всё, что выше, почитать.
19 | - Что такое [Copy on Write](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/memory%20management/Copy%20on%20write.md)?
20 | - Что такое [`indirect enum`](https://stackoverflow.com/a/37533442)?
21 | - Разница между [`== vs ===`](https://stackoverflow.com/a/50645846)?
22 | - Вопрос про отложенную деаллокацию, [вот почитать](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/memory%20management/Delayed%20deallocation.md)
23 |
24 | # ARC
25 | - Что такое [`ARC`](https://youtu.be/SnvZwVgoUrI?t=159)?
26 | - Что такое [`MRC`](https://youtu.be/SnvZwVgoUrI?t=25)?
27 | - Разница между `ARC и MRC`?
28 | - Разница между `ARC и GC`?
29 | - Что такое [`retain cycle`](https://youtu.be/SnvZwVgoUrI?t=225)?
30 | - На каком этапе [`расставляются ссылки` в ARC](https://youtu.be/SnvZwVgoUrI?t=174)?
31 | - Разница между [`weak` и `unowned`]()?
32 | - Что такое [`side table`](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/memory%20management/ARC/Side%20table%20and%20object%20reletionship.md#side-table)?
33 | - Как устроена и когда появляется [`side table`](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/memory%20management/ARC/Side%20table%20and%20object%20reletionship.md#как-устроена-side-table)?
34 | - Счетчик ссылок и жизненный цикл объекта, [ссылка](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/memory%20management/ARC/Side%20table%20and%20object%20reletionship.md#счетчик-ссылок)
35 | - Что такое [`autoreleasepool`](https://swiftrocks.com/autoreleasepool-in-swift)?
36 |
37 | # Многопоточность
38 | - В чем разница sync и async?
39 | - [Потокобезопасность со static](https://stackoverflow.com/a/58038780)
40 |
41 | # Others
42 | - Что такое `generics` и для чего они нужны?
43 | - Отличие [`отличие static методов от class`](https://habr.com/ru/sandbox/146984/) функций.
44 | - Что такое [`responder chain`](https://habr.com/ru/post/464463/)?
45 | - Рассказать про [замыкания](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/Closures.md)
46 |
--------------------------------------------------------------------------------
/roadmap/swift/uikit/App and view controller lifecycle.md:
--------------------------------------------------------------------------------
1 | # Жизненный цикл прилoжения.
2 |
3 | 1. `Not running` - (не запущенное) — приложение не было запущено или его работа была прекращена.
4 | 2. `Inactive` - (неактивное) — приложение работает, но не принимает события (например, когда пользователь заблокировал телефон при запущенном приложении).
5 | 3. `Active` - (активное) — нормальное состояние приложения при его работе.
6 | 4. `Background` - (фоновое) — приложение больше не на дисплее, но оно все еще выполняет код.
7 | 5. `Suspended` - (приостановленное) — приложение занимает память, но не выполняет код.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | # Жизненный цикл UIViewController
18 |
19 | 1. `load view` — создает вью, которой управляет контроллер. Вызывается при создании контроллера. Вы можете переопределить этот метод, чтобы создать свои вью вручную, здесь присваивается корневое view для иерархии представлений, так же тут мы не вызываем `super`.
20 | 2. `viewDidLoad` — вью создано и загружено в память, но нет bounds. Хорошее место для инициализации и настройки объектов, используемых во вью контроллере.
21 | 3. `viewWillAppear` — вью будет добавлено в иерархию, определены bounds, но ориентация экрана не определена. Вызывается каждый раз, когда появляется вью.
22 | 4. `viewWillLayoutSubviews` — вызывается каждый раз, когда frame изменился, например, при смене ориентации. Если вы не используете autoresizing masks или constaints, вы, вероятно, хотите обновить сабвью здесь.
23 | 5. `viewDidLayoutSubviews` — вызывается уведомить контроллер, что его вью только что залэйаутил сабвью.
24 | 6. `viewDidAppear` — вью добавлено в иерахию и появилось на экране. Хорошее место для выполнения задач, связанных с анимацией вью. Метод вызывается после того, как анимация загрузки вью закончена.
25 | Иногда хорошим кейсом в этом методе будет вытаскивать данные из кордаты и отображать на вью или запрашивать данные с сервера.
26 | Так же, в этом жизненном цикле, определены bounds.
27 | 7. `viewWillDissapear` — вью уходит с экрана. Вызывается как при закрытии вью контроллера, так и при переходе дальше по иерархии, например, при пуше нового контроллера в NavigationController.
28 | 8. `viewDidDissapear` — вью ушло с экрана. Вызывается как при закрытии вью контроллера, так и при переходе дальше по иерархии.
29 | 9. `didReceiveMemoryWarning` - iOS устройства имеют ограниченный объем памяти и мощности. Когда память начинает заполняться, iOS не использует ограниченное место на жестком диске для перемещения данных из памяти, как это делает компьютер. По этой причине вы несете ответственность за снижение объема памяти вашего приложения. Если приложение начнет использовать слишком много памяти, iOS сообщит об этом.
30 |
31 | Поскольку вьюконтроллеры выполняют управление ресурсами, эти уведомления доставляются им с помощью этого метода. Таким образом можно предпринять действия, чтобы освободить некоторую память. Помните, что если вы игнорируете предупреждения памяти и память, используемая вашим приложением, превышает определенный порог, iOS завершит ваше приложение.
32 |
33 |
34 | 10. `deinit` - как и любой другой объект, перед удалением вьюконтроллера из памяти он деинициализируется. Обычно мы переопределяем `deinit()` для очистки ресурсов, выделенных вьюконтроллером и не освобожденных при помощи `ARC`. Также можно остановить задачи, которые не были остановлены в предыдущем методе, так как их нужно было сохранить в фоновом режиме.
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/roadmap/swift/Auto layout.md:
--------------------------------------------------------------------------------
1 | `Auto Layout` занимается динамическим вычислением позиции и размера всех view в view иерархии,
2 | на основе `constraints` — правил заданных для того или иного view.
3 | Самый большой и очевидный плюс для разработчика в использовании Auto Layout в том,
4 | что исчезает необходимость в подгонке размеров приложения под определенные устройства — Auto Layout делает это за вас,
5 | динамически изменяя интерфейс в зависимости от внешних или внутренних изменений.
6 |
7 | Внутренний размер содержимого может быть получен через свойства UIView `Intrinsic Content Size`.
8 | Autolayout учитывает Intrinsic Content Size, создавая для каждого представления внутренние констрейнты для ширины и высоты.
9 | В отличие от других констрейнтов эти констрейнты имеют два приоритета: **Content Hugging Priority** и **Content Compression Resistance Priority**.
10 |
11 | # Для чего нужны **Content Hugging Priority** / **Content Compression Resistance Priority.**
12 |
13 | `Content hugging priorities` - устанавливает приоритет, с которым View будет препятствовать увеличению своего размера относительно размеров своего контента.
14 | Грубо говоря, этот параметр показывает, насколько сильно View не хочет увеличиваться.
15 | Устанавливая большее значение этому приоритету, мы указываем, что не хотим, чтобы размер View увеличивался больше, чем размер своего контента, и наоборот.
16 |
17 | `Content Compression Resistance Priority` - устанавливает приоритет, с которым View будет препятствовать уменьшению своего размера относительно размера
18 | своего контента, то есть насколько сильно View не хочет уменьшаться. Чем выше значение этого параметра, тем сильнее View препятствует своему сжатию и
19 | в конечном счете обрезке своего контента.
20 |
21 | `Hugging => контент не хочет расти.`
22 | `Compression Resistance => контент не хочет сжиматься`
23 |
24 | Например у нас есть кнопка и мы прикрепили края кнопки к большой супервью с приоритетом = 500: `[_____Click Me_____]`
25 |
26 | Если `Hugging priority > 500` то кнопка будет выглядить вот так: `[Click Me]`
27 |
28 | Если `Hugging priority < 500` то кнопка будет выглядить вот так: `[_____Click Me_____]`
29 |
30 | Если супервью сжимается, то `Compression Resistance priority > 500`, и кнопка будет выглядеть так: `[Click Me]`
31 |
32 | В противном случае, если `Compression Resistance priority < 500`, кнопка будет выглядеть так: `[Cli..]`
33 |
34 | 
35 |
36 | # Render loop
37 |
38 | 1. `updateConstraints` - Позволяет внести изменения для ограничений. Используйте для наилучшего перфоманса, когда нужно `добавить`, `изменить` или `удалить` множество ограничений за один проход `layout'a.`
39 | 2. `setNeedsUpdateConstraints` - Вызывает изменения ограничений в следующем цикле рендеринга.
40 | 3. `layoutSubviews` - Вы должны переопределить этот метод только в том случае, если автоматическое изменение размера и поведение на основе ограничений вложенных представлений не предлагают желаемого поведения. Вы можете использовать свою реализацию для непосредственной установки прямоугольников фреймов ваших подпредставлений. Вы не должны вызывать этот метод напрямую. Если вы хотите принудительно обновить макет, вызовите метод `setNeedsLayout`, чтобы сделать это до следующего обновления отрисовки. Если вы хотите немедленно обновить лейаут ваших представлений, вызовите метод `layoutIfNeeded`.
41 | 4. `setNeedsDisplay` - Вызывает перерисовку вью в следующем цикле рендеринга.
42 |
43 | 
44 |
45 | # Полезные ссылки
46 | - [Математические основы Auto Layout / Антон Сергеев (Яндекс)](https://www.youtube.com/watch?v=-eGciYD9i3I&t=14s)
47 | - [Auto layout magic: content sizing priorities](https://krakendev.io/blog/autolayout-magic-like-harry-potter-but-real)
48 | - [High Performance Auto Layout](https://developer.apple.com/videos/play/wwdc2018/220/)
49 |
--------------------------------------------------------------------------------
/roadmap/Articles.md:
--------------------------------------------------------------------------------
1 | 1. 🎋 Swift:
2 | - 💚 junior | [App & View Controller жизненный цикл.](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/uikit/App%20and%20view%20controller%20lifecycle.md)
3 | - 💚 junior | [Замыкания](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/Closures.md)
4 | - 💚 junior | [Frame и bounds](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/uikit/Frame%20and%20bounds.md)
5 | - 💚 junior | [Что такое optional, для чего он нужен и как его использовать](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/Optional.md)
6 | - 💚 junior | [Optional Chaining](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/Optional%20Chaining.md)
7 | - 💛 junior+| [AnyObject, Any и any](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/AnyObject%2C%20Any%20и%20any.md)
8 | - 💛 junior+| [Delayed deallocation](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/memory%20management/Delayed%20deallocation.md)
9 | - 💛 junior+| [Диспетчеризация](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/Method%20dispatch.md)
10 | - 💛 junior+| [Анатомия пуш нотификаций](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/Push%20notifications.md)
11 | - 💛 junior+| [Auto Layout](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/Auto%20layout.md)
12 | - 🤎 middle+| [Устройство компилятора(Low level virtual machine)](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/Low%20level%20virtual%20machine.md)
13 | 2. 📝 Управление памятью:
14 | - 💚 junior | [Типы данных](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/memory%20management/Data%20type.md)
15 | - 💚 junior | [ARC]()
16 | - 💛 junior+| [Copy on write](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/memory%20management/Copy%20on%20write.md)
17 | - 💛 junior+| [Типы ссылок, side table, счётчик ссылок](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/memory%20management/ARC/Side%20table%20and%20object%20reletionship.md)
18 | - 🧡 middle | [Hashable](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/Hashable.md)
19 | - 🧡 middle | [Autorelease pool]()
20 | - 🤎 middle+| [Shallow and deep copying](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/memory%20management/Shallow%20and%20deep%20copying.md)
21 | - 🤎 middle+| [MRC]()
22 | 3. 🦦 Многопоточность:
23 | - 💛 junior+| [Введение в многопоточность](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/multithreading%20and%20concurrency/Multhithreading.md)
24 | - 🧡 middle | [Run Loop](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/multithreading%20and%20concurrency/Run%20loops.md)
25 | - 🤎 middle+| [Акторы](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/multithreading%20and%20concurrency/Actors.md)
26 | 4. 🌶 Структуры данных:
27 | - 💚 junior | [Массивы](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/data%20structures/Arrays.md)
28 | - 💚 junior | [Множества]()
29 | - 💚 junior | [Словари]()
30 | 5. 🐼 Принципы разработки:
31 | - 💚 junior | [SOLID](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/design%20principles/Solid.md)
32 | - 💛 junior+| [Protocol Oriented Programming](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/design%20principles/Protocol%20Oriented%20Programming.md)
33 | 6. 🥋 SwiftUI:
34 | - 🧡 middle | [Цикл отрисовки для SwiftUI](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/The%20SwiftUI%20render%20loop.md)
35 | 7. 🥷 Дополнительно:
36 | - 🤎 middle+| [Компьютерные сети](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/Computer%20networks.md)
37 | 8. 👾 Дополнительные материалы:
38 | - [Подсказки для работы с гитом](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/Git-cheat-sheet.md)
39 | - [Cоветы для улучшения тестового задания](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/swift/Test%20Review%20Tips.md)
40 |
--------------------------------------------------------------------------------
/roadmap/multithreading and concurrency/Run loops.md:
--------------------------------------------------------------------------------
1 | # Что такое Run loop?
2 |
3 | 
4 |
5 | `Run loop` - цикл обработки событий, который используется для планирования работы и координации получения входящих событий.
6 | Цель выполнения состоит в том, чтобы поддерживать поток занятым, когда есть работа, и переводить поток в спящий режим, когда его нет.
7 | 1. `Run loop` запускается.
8 | 2. Источники ввода доставляют события соответствующим обработчикам событий в цикле выполнения - это может привести к вызову IBAction (созданного вами метода), к примеру.
9 | 3. Обработка действий и вызванных событий.
10 | 4. Этот пункт относится к `MRC - Manual reference counting` К концу цикла возникают задачи подсчета ссылок (объекты освобождаются и т.д.).
11 | 5. `Run loop` заканчивает работу.
12 | 6. `Run loop` может повторить свои действия.
13 |
14 | В OSX/iOS у нас уже есть реализация этого механизма в виде NSRunLoop и CFRunLoopRef.
15 | `CFRunLoopRef` является частью фреймворка CoreFoundation. Он предоставляет API для функций на чистом C, которое является потокобезопасным.
16 |
17 | `NSRunLoop` — это обертка, берущая за основу CFRunLoopRef, которая предоставляет объектно-ориентированное API, но это API не является потокобезопасным.
18 |
19 | - Метод CFRunLoop - можно вызвать из любого потока.
20 | - Метод NSRunLoop и RunLoop - можно вызвать только из того же потока, в котором запущен RunLoop.
21 |
22 | В системе по умолчанию предусмотрено пять режимов:
23 | 1. `kCFRunLoopDefaultMode`: дефолтный для вашего приложения режим, обычно в этом режиме выполняется основной (main) поток.
24 | 2. `UITrackingRunLoopMode`: режим отслеживания интерфейса, используемый ScrollView для отслеживания касаний и слайдов, чтобы гарантировать, что интерфейс не зависит от других режимов при слайдинге.
25 | 3. `UIInitializationRunLoopMode`: первый режим, в который приложение входит при запуске, он не будет больше использоваться после завершения запуска.
26 | 4. `GSEventReceiveRunLoopMode`: внутренний режим для приема системных событий, обычно не используется.
27 | 5. `kCFRunLoopCommonModes`: это режим-заполнитель, не имеющий практического значения.
28 |
29 | 
30 |
31 | В Cocoa для каждого потока, системой, обычно создается свой **Run Loop** — цикл, который обрабатывает таймеры и события, а так же усыпляет поток, если ему нечего делать в текущий момент.
32 | Run Loop поддерживает 2 типа событий:
33 | - `Input sources` — асинхронные события. Обычно это сообщения от других потоков, приложений или системных вызовов. Основанные на портах или созданные пользователем, или источники, выполняющие селекторы.
34 | - `Timer sources` — синхронные события. Таймеры. Вызываются синхронно с известным интервалом.Каждый Run Loop определяет режим, в котором он работает: от режима зависит, какие события будут обработаны и кто будет об этом оповещен.
35 |
36 | > Runloop и поток связаны вместе. Каждый поток (включая главный поток) имеет соответствующий объект Runloop. Невозможно создать объект Runloop самостоятельно, но можно получить объект Runloop, предоставленный системой.
37 |
38 | **Четыре функции цикла исполнения:**
39 | - Принимать вводимые пользователем данные, не прерывая выполнения программы.
40 | - Решать, когда события должны обрабатываться программой.
41 | - Разделять вызовы.
42 | - Экономить время процессора
43 |
44 |
45 | При запуске Run Loop можно явно указать, какие источники событий ему стоит отслеживать на текущей итерации.
46 | Для этого существуют несколько режимов, называемых `Run Loop Modes`. Внимания заслуживают:
47 | - .default (основной режим),
48 | - .tracking (режим отслеживания нажатий)
49 | - .common (комбинация предыдущих двух режимов).
50 |
51 | Когда пользователь начинает взаимодействие с UI, основной Run Loop переходит в режим .tracking и временно откладывает обработку всех остальных событий, чтобы обеспечить плавность интерфейса.
52 |
53 | # Дополнительные материалы:
54 | 1. [Документация от Apple](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html)
55 | 2. [RunLoop.main vs DispatchQueue.main: The differences explained](https://www.avanderlee.com/combine/runloop-main-vs-dispatchqueue-main/)
56 | 3. [RunLoop на главном потоке / Антон Сергеев](https://www.youtube.com/watch?v=s8B6t5XnB7M)
57 | 4. [Пример использования autoreleasepool в Swift](https://proswift.ru/pamyat-i-autoreleasepool-dlya-ciklov/)
58 |
--------------------------------------------------------------------------------
/roadmap/swift/AnyObject, Any и any.md:
--------------------------------------------------------------------------------
1 | # AnyObject и Any получили новый вариант any
2 | Как было представлено в [SE-355](https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md), что затрудняет нам, разработчикам, понять различия.
3 | Каждый параметр имеет свои сценарии использования и подводные камни относительно того, когда не использовать их.
4 |
5 | `Any` и `AnyObject` являются специальными типами в Swift, используемыми для стирания типов, и не имеют прямой связи ни с одним из них.
6 |
7 | # Когда использовать `AnyObject?`
8 | `AnyObject` - это протокол, которому неявно соответствуют все классы. Фактически стандартная библиотека содержит псевдоним типа `AnyClass`, представляющий `AnyObject.Type`.
9 |
10 | - ```print(AnyObject.self) // Prints: AnyObject```
11 | - ```print(AnyClass.self) // Prints: AnyObject.Type```
12 |
13 | Все классы, типы классов или протоколы только для классов, могут использовать `AnyObject` в качестве конкретного типа. Для демонстрации можно создать массив различных типов:
14 |
15 | ```
16 | let imageView = UIImageView(image: nil)
17 | let viewController = UIViewController(nibName: nil, bundle: nil)
18 |
19 | let mixedArray: [AnyObject] = [
20 | // We can add both `UIImageView` and `UIViewController` to the same array
21 | // since they both cast to `AnyObject`.
22 | imageView,
23 | viewController,
24 |
25 | // The `UIViewController` type conforms implicitly to `AnyObject` and can be added as well.
26 | UIViewController.self
27 | ]
28 | ```
29 |
30 | Только классы соответствуют `AnyObject`, что означает, что их можно использовать для ограничения реализации протоколов только ссылочными типами:
31 |
32 | `protocol MyProtocol: AnyObject { }`
33 |
34 | `AnyObject` можно использовать, если требуется гибкость нетипизированного объекта.
35 |
36 | ```
37 | func configureImage(_ image: UIImage, in imageDestinations: [AnyObject]) {
38 | for imageDestination in imageDestinations {
39 | switch imageDestination {
40 | case let button as UIButton:
41 | button.setImage(image, for: .normal)
42 | case let imageView as UIImageView:
43 | imageView.image = image
44 | default:
45 | print("Unsupported image destination")
46 | break
47 | }
48 | }
49 | }
50 | ```
51 |
52 | Используя `AnyObject` в качестве целевого объекта, мы всегда должны приводить и помнить о фейле при кастинге, с использованием реализации по умолчанию.
53 | Пример с использованием конкретных протоколов:
54 | ```
55 | // Create a protocol to act as an image destination.
56 | protocol ImageContainer {
57 | func configureImage(_ image: UIImage)
58 | }
59 |
60 | // Make both `UIButton` and `UIImageView` conform to the protocol.
61 | extension UIButton: ImageContainer {
62 | func configureImage(_ image: UIImage) {
63 | setImage(image, for: .normal)
64 | }
65 | }
66 |
67 | extension UIImageView: ImageContainer {
68 | func configureImage(_ image: UIImage) {
69 | self.image = image
70 | }
71 | }
72 |
73 | // Create a new method using the protocol as a destination.
74 | func configureImage(_ image: UIImage, into destinations: [ImageContainer]) {
75 | for destination in destinations {
76 | destination.configureImage(image)
77 | }
78 | }
79 | ```
80 |
81 | Полученный код является более чистым, читаемым и больше не требует обработки неподдерживаемых контейнеров.
82 | Экземпляры должны соответствовать протоколу ImageContainer для получения настроенного образа.
83 |
84 | # Когда использовать `Any`?
85 | `Any` может представлять экземпляр любого типа, включая типы функций:
86 | ```
87 | let arrayOfAny: [Any] = [
88 | 0,
89 | "string",
90 | { (message: String) -> Void in print(message) }
91 | ]
92 | ```
93 |
94 | Те же правила применяются к `Any` по сравнению с `AnyObject`, что означает, что вы всегда должны стремиться использовать конкретные типы.
95 | `Any` является более гибким, позволяя отливать экземпляры любого типа, что затрудняет прогнозирование кода по сравнению с использованием конкретных типов.
96 |
97 | # Когда использовать `any`?
98 | `any` выглядит аналогично `Any` и `AnyObject`, но имеет другую цель, поскольку вы используете его для обозначения использования экзистенциального.
99 | Следующий пример кода демонстрирует конфигуратор изображения, использующий пример кода из предыдущих примеров в этой статье:
100 | ```
101 | struct ImageConfigurator {
102 | var imageContainer: any ImageContainer
103 |
104 | func configureImage(using url: URL) {
105 | // Note: This is not the way to efficiently download images
106 | // and is just used as a quick example.
107 | let image = UIImage(data: try! Data(contentsOf: url))!
108 | imageContainer.configureImage(image)
109 | }
110 | }
111 |
112 | let iconImageView = UIImageView()
113 | var configurator = ImageConfigurator(imageContainer: iconImageView)
114 | configurator.configureImage(using: URL(string: "https://picsum.photos/200/300")!)
115 | let image = iconImageView.image
116 | ```
117 | Как вы видите, мы указали использование экзистенциального `ImageContainer`, пометив наше свойство `imageContainer` ключевым словом `any`.
118 | Маркировка протокола с использованием `any` протокола будет применяться начиная с 6 Swift'a, поскольку она будет указывать влияние на производительность использования протокола таким образом.
119 |
120 | Экзистенциальные типы имеют существенные ограничения и последствия для производительности и являются более дорогими, чем использование конкретных типов, поскольку их можно изменять динамически. Примером такого изменения является следующий код:
121 | ```
122 | let button = UIButton()
123 | configurator.imageContainer = button
124 | ```
125 | Наше свойство `imageContainer` может представлять любое значение, соответствующее нашему протоколу `ImageContainer`, и позволяет нам изменить его с изображения на кнопку.
126 | Для этого требуется динамическая память, что лишает компилятор возможности оптимизировать этот фрагмент кода.
127 | Вплоть до введения `any` ключевого слова не было явного указания разработчикам на эту стоимость производительности.
128 |
129 | # Уход от `any`
130 | Мы можем переписать приведенный выше пример кода с помощью дженериков и избавиться от потребности в динамической памяти:
131 | ```
132 | struct ImageConfigurator {
133 | var imageContainer: Destination
134 | }
135 | ```
136 | Осознание последствий для производительности и знание того, как переписать код, является необходимым навыком для работы в качестве разработчика Swift.
137 |
138 | # Вывод
139 |
140 | `Any`, `any` и `AnyObject` выглядят аналогично, но имеют важные различия. На мой взгляд, лучше переписать свой код и отнять необходимость использовать любое из этих ключевых слов. Это часто приводит к более удобочитаемому и предсказуемому коду.
141 |
142 | # Материал
143 |
144 | [Ссылка на оригинал](https://www.avanderlee.com/swift/anyobject-any/)
145 |
--------------------------------------------------------------------------------
/roadmap/swift/Test Review Tips.md:
--------------------------------------------------------------------------------
1 | # Полезные советы, которые помогут сделать твоё тестовое задание лучше!
2 |
3 | 1) Все тестовые храни на **Github/Gitlab**
4 |
5 | Подтверждается автоматически навык владения VCS - это плюс для каждого джуна. Тестовое не потеряется через год.
6 | Бонус к шансу найти работу. Друга пригласили на собеседование, увидев его тестовое на гитхабе. На проекте был схож стэк, если - что [вот здесь](https://learngitbranching.js.org/?locale=ru_RU) можно выучить интерактивно гит.
7 |
8 | 
9 |
10 | 2) Добавь к тестовому ТЗ и скриншоты.
11 |
12 | Синьоры-ревьюверы очень ленивые. Я бы забил уже вот здесь, не увидев задания.
13 | Опиши ТЗ, приложи запись финальной версии приложения, напиши, какие фреймворки использовал. Это как красивая обложка, без нее книгу не откроют даже.
14 |
15 |
16 |
17 | 3) Gitignore
18 |
19 | Уже на этом месте, я знаю, что внутри я найду файлы .DS_Store и папку xcuserdata, которых в гите быть не должно.
20 | Эти файлы есть в базовом gitignore от [Github](https://github.com/github/gitignore/blob/main/Swift.gitignore), просто добавь его в проект - это позволит подчеркнуть твою аккуратность.
21 |
22 | 4) Линтер
23 |
24 | Увидев .swiftlint.yml в корне, я думаю:
25 | - кандидат почитал про хорошие практики индустрии, заинтересован
26 | - он не любит руками форматировать код, экономит время, хорошо
27 | - внутри я точно не увижу try!, as!
28 |
29 | 5) Удаляй весь неиспользуемый код
30 |
31 | Сразу иду в SceneDelegate.swift и AppDelegate.swift, по классике там пустые методы. Либо кандидату было пофиг, либо он не знал, зачем это, и решил на всякий оставить.
32 |
33 | Так же с оставленным кускам закоменченного (//) кода, убери все ненужное
34 |
35 |
36 |
37 | 6) Любая структура проекта
38 |
39 | Тут четких ожиданий нет. Единственный 🚩: все файлы просто лежат в папке приложения без разделения.
40 | Можете разбивать по экранам, по слоям, как-нибудь.
41 |
42 |
43 |
44 | 7) Не перебарщивай с фреймворками
45 |
46 | Если в ТЗ не сказано про фреймворки, то лучше уточни "можно ли". Если "на ваше усмотрение", то не больше трех:
47 |
48 | - Kingfisher для картинок
49 | - Realm или SQLite для бд
50 | - SwiftLint
51 |
52 | Есть исключения, например, если компания пишет на реактивном стэке и просит его. Но это должно быть сказано явно.
53 | Если не сказано, и тебе оченль сильно нравится SnapKit, Moya, TCA, то не нужно все это пихать в проект.
54 | Каждый новый фреймворк потенциальный 🚩 для ревьювера. К нативному никто не доебется
55 |
56 | Тесты пишем только нативно
57 |
58 |
59 |
60 | 8) Нет "красных файлов" в структуре проекта
61 |
62 | Красное название файла означает, что ты не добавил какой-то файл в гит, а это означает, что проект не запустится, а ты неаккуратный.
63 |
64 | Легко тестируется: скачай проект в отдельную папку и запусти с нуля
65 |
66 |
67 |
68 | 9) // MARK: - и MARK:
69 |
70 | Вот один и тот же файл с разметкой и без. Она добавляет читабельности. Плевать, что это тестовое и файл длинной сто строчек.
71 | Ты показываешь, что понимаешь назначение своих инструментов и умеешь их использовать.
72 |
73 |
74 |
75 | 10) Оформляем модели правильно
76 |
77 | Слева – совсем неправильно, справа – идеально:
78 | - используем Decodable, а не Codable, если нет нужды в Encodable
79 | - для данных всегда struct
80 | - все свойства let
81 | - внутренний структуры (Main, Wind) убраны в extension, чтобы не засорять global scope.
82 |
83 |
84 |
85 | 11) Не пишите вы CodingKeys
86 |
87 | Поля в JSON называются в snake_case, а в проекте мы юзаем camelCase. Решение найдено казалось бы, но!
88 |
89 | Если программист может не писать код, он должен его не писать. Такой код означаете, что вы не изучили API, просто забили...
90 |
91 |
92 |
93 | 12) Обработка ошибок
94 |
95 | Уровни крутости по нарастанию:
96 | - игнор и возврат nil
97 | - print(error.localizedDescription) и возврат nil
98 | - юзаем Logger https://developer.apple.com/documentation/os/logger…
99 | - юзаем Result и прокидываем ошибки на UI слой, где показываем алерт с текстом https://developer.apple.com/documentation/swift/result
100 |
101 | 13) Не надо писать весь код во viewDidLoad
102 |
103 | Используй приватные функции с названием типа configurePicker() или setupLayout() и помести их куда-нибудь вниз класса вьюконтроллера.
104 | Эти функции будут вызываться из viewDidLoad и сильно увеличивать читаемость куда
105 |
106 |
107 |
108 | 14) Не настраивайте UI элемент из вне
109 |
110 | Все UI элементы должны быть private. В каждом view/cell должен быть метод, куда передается простая модель, а view просто показывает ее свойства без всякой логики
111 | (кстати аутлеты уже давно не надо делать weak)
112 |
113 |
114 |
115 | 15) Выносите повторяющиеся строковые литералы в константу
116 |
117 | Обычно это reuseIdentifier ячеек и URL в сетевом слое, который повторяется несколько раз.
118 | Для ячеек используйте String(describing: Class.self) или NSStringFromClass, для URL - enum со статистическими свойствами.
119 |
120 |
121 |
122 | #
123 | Спасибо большое, за предоставленный материал [Антону Назарову!](https://t.me/m0rtymerr_channel)
124 |
--------------------------------------------------------------------------------
/roadmap/swift/Hashable.md:
--------------------------------------------------------------------------------
1 | # **Hashable**
2 | - Документация от эпл даёт такое определение:"Тип, который может быть хэширован в Hasher для получения целочисленного значения хэша."
3 | - К примеру, ключ для типа данных **Dictionary** должен конформить протокол Hashable, аналогично и для элементов типа данных **Set**.
4 | - @frozen struct Dictionary where Key : Hashable
5 | - @frozen struct Set where Element : Hashable
6 | #
7 | Однако неясно, что делает эти типы данных Hashable? И как мы можем сделать наши собственные типы данных Hashable?
8 |
9 | **Хеширование - что и почему?**
10 |
11 | Когда мы говорим о хэше, то часто слышим такие понятия: хэш, хеширование, хеш-код, хеш-значение, хеш-таблицу и хеш-функцию. Даже если мы не знакомы со всеми этими терминами, мы используем их в различных языках программирования.
12 |
13 | **Основные понятия**
14 | - Хеширование - процесс применения алгоритма для преобразования элемента данных в значение. Элемент данных может быть таким же простым, как целое число, строка или сложным, как объект с несколькими свойствами.
15 | - Алгоритм называется хеш-функцией или хешером. Преобразованное значение называется хэш-значением, хэш-кодом или просто хэшем.
16 | 
17 |
18 | - На приведенной выше схеме показан упрощенный пример процесса хеширования. По существу, мы начинаем с четырех видов фруктов (то есть элементов данных) в левой части.
19 | Алгоритм (то есть хеш-функция) может преобразовывать эти четыре имени как входные в четыре целых числа (то есть значения хеш-функции) как выходные в правой части, которые выглядят как случайные числа без очевидной связи с их исходными входами.
20 |
21 | Важно отметить:
22 | - **Процесс хеширования должен быть повторяемым.** Хеш-функция должна всегда создавать одно и то же хеш-значение, если она подается с теми же данными. Каждый раз, когда мы запрашиваем хеш-значение, первого элемента на диаграме - Apple, хеш-функция надежно вернет нам **839021**.
23 | - **Значения хэша должны быть уникальными.** Apple, Orange, Peach и Pineapple сопоставляются с различными значениями хэша, когда мы просим хеш-функцию вычислить новый фрукт, скажем Клубника, функция может дать значение, такое как **542381**, которое, как ожидается, будет отличаться от существующих на диаграмме.
24 | - **Хеш-значения должны быть кажущимися случайными.** Используя более технический жаргон, хэш-значения не должны быть легко инвертированы, чтобы узнать, каковы исходные элементы данных. Таким образом, хеш-значения должны выглядеть случайными, чтобы минимизировать возможность инверсии.
25 | - **Значения хэша не обязательно должны быть положительными целыми числами.** Диаграмма использует целые числа в качестве значений хэша только в демонстрационных целях. Хеш-функция определяет вывод (т.е. значения хеш-функции) и, таким образом, в зависимости от самой хеш-функции, значения хеш-функции могут быть отрицательными целыми числами, и они также могут содержать цифры, буквы и даже символы.
26 |
27 | #
28 | **Примеры использования**
29 |
30 | Хеширование широко используется в различных аспектах нашей повседневной жизни.
31 | Чтобы лучше понять хеширование, давайте разберём три распространенных случая использования хеширования в операциях с базами данных, криптографии и структурах данных в программировании:
32 |
33 | - **Операция базы данных: поиск**
34 |
35 | Практически все веб-сайты и мобильные приложения имеют функцию поиска где-то в своих приложениях. Реализация функции поиска предполагает использование хеширования. Предположим, что в таблице базы данных имеется список локальных малых предприятий. Как - то раз вы захотели сделать технический осмотр вашего автомобился и решили обратиться в John's Mechanic.
36 | 
37 |
38 | - **Без использования хеширования:**
39 | - При поиске в этой таблице с использованием имени магазина база данных должна сравнить эту 15-символьную строку с полем имени компании для каждой записи.
40 | Представьте, что количество записей может составлять сотни тысяч, так что этот процесс может быть очень длительным и недостаточно эффективным, так же сложность по времени функции поиска является O (n), что означает, что необходимое время линейно пропорционально размеру данных.
41 |
42 | - **С использованием хеширования:**
43 | - Каким-то образом владелец базы данных усвоил хитрость хеширования и использовал хеш-функцию для создания уникальных хеш-значений для каждого из предприятий и использовал их в качестве индекса для всех этих предприятий.
44 | Теперь при выполнении запроса поиска с использованием имени хранилища John's Mechanic база данных сначала будет использовать хеш-функцию для вычисления значения хеш-функции для критерия поиска.
45 | Как упоминалось ранее, ожидается, что хеш-функция создаст одно и то же хеш-значение для того же хранилища, так что мы просто рассмотрим индекс и выясним, где находится запись данных.
46 | В среднем, временная сложностью функции поиска с использованием хеширования является O (1) - постоянное количество времени, не зависящее от размера данных.
47 | 
48 |
49 | #
50 | **Криптография - пароль**
51 | Для обеспечения персональной работы, многие веб-сайты и приложения требуют от пользователей создания собственных учетных записей. В процессе регистрации мы обычно предоставляем свой адрес электронной почты и пароль для новой учетной записи на веб-сайте. Эти два фрагмента текста будут отправлены через Интернет до того, как они поступят на сервер.
52 |
53 | - **Без использования хеширования:**
54 | Разработчики сайта ничего не знали о хешировании, и думали, что кибербезопасность их не волнует, таким образом, запрос на регистрацию был разработан для отправки посредством обычного текста. Он имеет значительный риск для безопасности данных. Если запрос перехватят, то пароль будет украден,т.к это просто обычный текст.
55 |
56 | - **С использованием хеширования:**
57 | При использовании хеширования, пароль будет хэширован на случайное текстовое значение, а если запрос на регистрацию перехватят, то будет открыт только этот хэшированный пароль. Как упоминалось выше, мы обычно разрабатываем хеш-функции, которые делают практически невозможным инвертирование хеш-значений в их исходные элементы данных. Таким образом, это более безопасный подход. База данных веб-сайта может просто хранить этот хэшированный пароль.
58 | Когда мы попытаемся войти в систему, веб-сайт просто сравни предоставленный пароль (опять же, хэшированный) с сохраненным хэшированным паролем, потому что они должны быть такими же, как и входные данные (т.е. правильный пароль в обычном тексте) идентичны.
59 | 
60 |
61 | #
62 | **Структура данных - Словарь**
63 |
64 | Большинство языков программирования содержат в себе такой тип данных как словарь - неупорядоченная коллекция пар ключ - значение. Здесь мы не будем смотреть на детальную реализацию словаря, а затронем его хеш-таблицу, которая обеспечивает быстрый доступ к своим записям.
65 |
66 | - Для каждого ключа, хеш-функция вычисляет хеш-значение как индекс хранения этого элемента данных. Когда мы извлекаем элемент данных, программа вычисляет хеш-значение для предоставленного ключа и ищет индекс, если ключ с таким же хеш-значением существует. В этом случае возвращается соответствующий элемент данных. Если нет, будет возвращен nil. Поэтому тип данных, возвращаемый словарем, является необязательным, так как доступность не гарантируется.
67 | - Когда мы вставляем новую пару ключ-значение, хеш-функция просто вычисляет хеш-значение для ключа в качестве индекса для этого элемента данных, в то время как мы удаляем пару ключ-значение, мы просто удаляем индекс, который вычисляется из хеш-значения.
68 | #
69 | **Hashable Protocol**
70 | После общего понимания хеширования теперь мы можем сузить нашу область, сосредоточившись на протоколе Hashable в стандартной библиотеке Swift.
71 | При обращении к странице документации по протоколу Hashable выясняется, что определение Hashable таково: `“A type that can be hashed into a Hasher to produce an integer hash value.”`
72 |
73 | Определение очень четкое, и мы можем определить три ключевых слова в этом определении: Тип, Hasher и Целочисленное значение, а теперь давайте рассмотрим их детальнее.
74 |
75 | #
76 | **Тип**
77 |
78 | Как упоминалось выше, все элементы в типе данных **Set(множества)** должны быть хэшируемыми. На поверхности это просто означает, что каждый отдельный элемент является хэшируемым, однако фактически означает - что конкретный тип этого дженерика **Set** должен соответствовать протоколу **Hashable**. Другими словами, **Hashable** как и другие протоколы реализуется на уровне типа, например - класс, структура, так что все его экземпляры являются хэшируемыми.
79 | Кроме того, тип не только включает стандартные типы данных, такие как примитивы - целые числа, строки, но также включает свои собственные, пользовательские типы, пример будет ниже.
80 |
81 | 
82 |
83 | Важно отметить, что мы не вызываем метод **finalize** в методе **hash(into:)** потому-что компилятор автоматически завершит вычисление, вызвав метод **finalize** с помощью кода в **hashValue**, когда необходимо значение hash.
84 |
85 | #
86 | **Дополнительный материал:**
87 | - https://swiftrocks.com/how-hashable-works-in-swift
88 | - [Practical cryptography for developers](https://cryptobook.nakov.com)
89 |
--------------------------------------------------------------------------------
/roadmap/multithreading and concurrency/Actors.md:
--------------------------------------------------------------------------------
1 | # Модель Акторов
2 |
3 |
4 |
5 |
6 |
7 | Модель акторов была придумана в 1973 году, как некая теоретическая модель для описания различных параллельных систем, где свою популярность получила благодаря ЯП Эрланг.
8 |
9 | Одним из источников проблем, в многопоточном программировании, является наличие разделяемого состояния и многопоточности, т.е когда у нас `разделяемое, изменяемое состояние + параллелизм = проблемы`, как пример: **race conditions**, **data races**, необходимость в блокировках и прочие сложности.
10 |
11 | Одним из путей решения таких проблем, можно считать отказ от изменяемого состояния, т.е вместо того, чтобы иметь `разделяемые, изменяемые объекты` мы работаем с **неизменяемыми** данными и строим цепочки для обработки данных.
12 | Акторы предлагают совершенно другой подход, вместо того чтобы отказываться от `изменяемого состояния`, акторы предлагают отказаться от `разделяемого состояния`, т.е
13 | вместо того, чтобы потоки работали над общими данными, мы говорим, что наши данные являются приватными данными и с ними может работать строго один поток, т.е мы запрещаем любой параллелизм над этими данными!
14 |
15 | # Акторы в swift
16 | Тип данных, `ссылочный`, защищающий данные экземпляра от параллельного доступа. Это достигается путём "изоляции" актора, `во время компиляции`, которая гарантирует, что все обращения к данным этого экземпляра проходят через механизм синхронизации, который выполняется последовательно.
17 | Основная идея акторов состоит в том, чтобы обеспечить «островки одиночной многопоточности», которые не нуждаются во внутренней синхронизации, потому - что общее изменяемое состояние избегается в пользу передачи семантических типов значений через асинхронные сообщения.
18 |
19 | Акторы аналогичны другим типам в свифт: **структурам, перечеслениям** и **классам**. Акторы могут содержать в себе:
20 | - Методы, включая статические, свойства, индексы, инициализацию.
21 | - Не поддерживают наследование, поэтому исключают `convenience` инициализатор, переопределение (overriding), область видимости `open` и `final`.
22 |
23 |
24 |
25 |
26 |
27 | Акторы объединяют в себе лучшее из обоих инструментов, используя преимущества совместного пула потоков для эффективного планирования.
28 | При вызове метода для актора, который еще не выполняется, вызывающий поток может быть повторно использован для выполнения вызова метода.
29 | В случае, когда вызываемый актор уже запущен, вызывающий поток может приостановить выполняемую им функцию и перехватить другую работу.
30 |
31 |
32 |
33 |
34 |
35 | - Если очередь еще не запущена, мы говорим, что `нет конфликта (no contention)`
36 | - Если последовательная(serial) очередь уже запущена, будем считать, что очередь находится `в состоянии конфликта (under contention)`
37 |
38 | `@GlobalActor` - существуют из-за того факта, что синхронизация состояния не ограничивается локальными переменными, а это означает, что может потребоваться глобальный доступ к актору.
39 | Вместо того, чтобы заставлять всех писать повсюду синглтоны, Global Actors позволяют легко указать, что определенный фрагмент кода должен выполняться в рамках определенного глобального актора.
40 | - Можно создать свои собственные глобальные акторы, добавив к актору атрибут `@globalActor`
41 |
42 | `@MainActor` - глобальный актор, который обеспечивает выполнение всего кода в основном **(main)** потоке.
43 | - Добавив аттрибут `@MainActor` к классу, все свойства и методы как `@MainActor`, если - вдруг нужно избежать этого, для какой-то конкретной функции или свойству, то достаточно пометить эту сущность как `nonisolated`.
44 | ```
45 | @MainActor
46 | class ViewController: UIViewController {
47 | //...
48 | nonisolated var fetchBooks(with page: Int) -> [Books] { ... }
49 | //...
50 | }
51 | ```
52 |
53 | # Какие проблемы решают акторы?
54 |
55 | **Гонка данных** - происходит, когда один поток обращается (читает) к изменяемому объекту, а другой записывает в него.
56 | - Если два потока, работающие параллельно, вызывают `worker(number: Int)` функцию, то в лучшем случае, будет неконсистентным объект, который мы изменяем, иначе, если **в один и тот же момент времени** оба потока обратятся к функции `worker(number: Int)`, то произойдет краш.
57 | ```
58 | public final class Counter {
59 |
60 | private var myVariable = 0
61 |
62 | func worker(number: Int) {
63 | myVariable += number
64 | print(myVariable)
65 | }
66 |
67 | }
68 | ```
69 |
70 |
71 |
72 |
73 |
74 | **До акторов** мы могли решить проблему гонки данных таким путём:
75 | 
76 | Можно заметить, что в решении выше, весьма достаточно кода для обеспечения синхронизации. Для записи данных - барьерный флаг необходим, чтобы остановить чтение, на мгновение, и разрешить запись даьше, т.е мы должны помнить об этом.
77 |
78 | Акторы, с другой стороны, позволяют swift максимально оптимизировать синхронизированный доступ. Используемый базовый лок является всего лишь детализацией реализации, где в результате компилятор swift может обеспечить синхронизированный доступ.
79 | Тот же пример, но с использованием актора:
80 | ```
81 | actor Counter {
82 | public var myVariable = 0
83 |
84 | /// We no longer need a barrier
85 | func addNumber(with number: Int) {
86 | myVariable += number
87 | }
88 |
89 | /// We no longer need a barrier
90 | func removeNumber(with number: Int) {
91 | myVariable -= number
92 | }
93 | }
94 | ```
95 | Теперь код стал намного меньше, плюс вся логика с синхронизацией доступа скрыта, как и детали реализации.
96 |
97 | # Как работают акторы?
98 |
99 | ```
100 | public protocol Actor: AnyObject, Sendable {
101 | nonisolated var unownedExecutor: UnownedSerialExecutor { get }
102 | }
103 |
104 | final class ImageDownloader: Actor {
105 | // ...
106 | }
107 | ```
108 | - [`Sendable`](https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md) — протокол чья цель "пометить", что тип `безопасен` для работы в параллельной среде.
109 | - [`nonisolated`](https://github.com/apple/swift-evolution/blob/main/proposals/0313-actor-isolation-control.md#non-isolated-declarations) отключает проверку безопасности и позволяет «переопределить» проверки акторов во время компиляции Swift.
110 | - [`UnownedSerialExecutor`](https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md) - слабая ссылка на протокол `SerialExecutor`
111 |
112 | У `SerialExecutor: Executor` от Executor есть метод func enqueue(_ job: UnownedJob), который выполняет задачи. Сначала пишем это:
113 | ```
114 | let imageDownloader = ImageDownloader()
115 | Task {
116 | await imageDownloader.setImage(for: "image", image: UIImage())
117 | }
118 | ```
119 | Т.е семантически происходит следующее:
120 | ```
121 | let imageDownloader = ImageDownloader()
122 | Task {
123 | imageDownloader.unownedExecutor.enqueue {
124 | setImage(for: "image", image: UIImage())
125 | }
126 | }
127 | ```
128 |
129 | По умолчанию Swift генерирует стандартный **SerialExecutor** для кастомных акторов, где кастомные реализации **SerialExecutor** переключают потоки.
130 | Так работает **MainActor** - актор, у которого **Executor** переводит в главный поток, где создать его нельзя, но можно обратиться к его экземпляру **MainActor.shared**.
131 |
132 | # Общие советы по работе с акторами
133 |
134 | - Делайти ваши акторы как можно меньше. Ваши акторы защитят вас от многих пробле, благодаря компилятору **Swift**, но они не защитят вас от такой проблемы, как `Reentrancy`, вот пример [один](https://swiftsenpai.com/swift/actor-reentrancy-problem/), вот [два](https://stackoverflow.com/questions/70586562/how-to-prevent-actor-reentrancy-resulting-in-duplicative-requests). Небольшой размер акторов поможет вам эффективно их использовать.
135 |
136 | - Держите все операции вашего актора атомарными. Атомарная операция рассматривается как единое целое, в этом поможет краткость ваших методов. В общем, атомарность означает, что вы либо завершаете всю свою работу сразу, либо в случае сбоя одной операции вся операция отбрасывается. Избегайте вызовов `await` внутри акторов насколько это возможно и это поможет с проблемой с атомарности при использовании акторов.
137 |
138 | - Все предположения о необходимом состоянии должны идти после вызовов `await` при использовании внутри методов актора. Это очень легко сделать, если ваш метод содержит один вызов `await`, но если у вас их несколько, может быть сложно рассуждать о состоянии вашей программы.
139 |
140 | - Не используйте актор в качестве `ObservableObject` в SwiftUI. У компилятора нет проблем, позволяющих вам это сделать, но `ObservableObject` будет ожидать, что он всегда будет работать в `main` потоке. Наличие актора с дорогостоящими вызовами приведет к видимым задержкам в вашем приложении SwiftUI.
141 |
142 | # Дополнительные материалы:
143 | - [Документация от Apple](https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md)
144 | - [WWDC21, Protect mutable state with Swift actors](https://developer.apple.com/videos/play/wwdc2021/10133/)
145 | - [Акторы Swift под капотом](http://swiftrocks.com/how-actors-work-internally-in-swift)
146 |
--------------------------------------------------------------------------------
/roadmap/design principles/Protocol Oriented Programming.md:
--------------------------------------------------------------------------------
1 | > В POP ключевую роль играют следующие средства языка: protocol, extensions и constraints.
2 |
3 | 1. `Протокол как тип`
4 | Аналогичен понятию интерфейс из ООП и контракту из контрактного программирования.
5 | Служит для описания функциональности объекта.
6 | Может использоваться в качестве типа свойства, в качестве типа результата функции, типа элемента гетерогенной коллекции.
7 | Из-за ограничений языка, протоколы имеющие associated types или Self-requirements не могут использоваться в качестве типов.
8 |
9 | 2. `Протокол как шаблон типа`
10 | Аналогичен понятию концепт из обобщённого программирования.
11 | Так же служит для описания функциональности объекта, но в отличие от «протокол как тип», используется как требование к типу в обобщённых функциях.
12 | Может содержать associated types.
13 | Чёткой грани, в каком случае использовать протокол как тип, а в каком — как ограничение на тип, нет, более того — иногда требуется использовать протокол в обоих сценариях.
14 | Можно попытаться выделить случаи использования:
15 | - **Классы**, которые предоставляют функциональность для более высоких слоёв приложения и передаются классам-потребителям как зависимости — это сервисы, репозитории, api-клиенты, пользовательские настройки, и прочее.
16 | > В этом случае удобнее использовать протокол как тип — его можно будет зарегистрировать в IOC контейнере, а без его использования — не потребуется в каждой функции, где используется этот сервис, добавлять тип-параметр.
17 | - **Протокол** с описанием математических операций, например сравнение, сложение, конкатенация и подобные вещи.
18 | В этом случае удобно воспользоваться Self-requirement (когда в функции или свойстве протокола используется псевдотип Self),
19 | чтобы избежать опасного приведения и использования разных типов,
20 | когда операция допускает параметры только одного типа (Int и String в Swift соответствуют протоколу Equatable, но если попытаться проверить их на равенство между собой, компилятор выдаст ошибку, поскольку оператор сравнения требует, чтобы параметры были одного типа).
21 | Поэтому в этом случае протокол используется как шаблон типа.
22 | - Иногда требуется сохранить в приватном свойстве протокол имеющий associated types,
23 | но в этом случае мы не можем использовать протокол как тип.
24 | Есть разные способы решения этой проблемы, например создание аналогичного протокола, в котором использование associated types будет заменено на конкретные типы; использование приёма type erasure — в этом случае associated types переедут в generic параметры типа Any[YourProtocolName].
25 | Ещё варианты — сохранять не сам экземпляр, а его функции. Либо захватить экземпляр в замыкание, которое сохранить в свойство.
26 |
27 | 3. `Протокол как Trait`
28 | Trait (типаж) — сущность, предоставляющая набор реализованной функциональности. Служит набором строительных блоков для классов/структур/enum-ов.
29 |
30 | - Добавление trait не влияет на семантику класса — нет различий между тем, используются методы из traits или методы, определённые прямо в классе. — Верно для протоколов — посмотрев на код, мы не можем определить, где определён метод — в protocol extension или типе, соответствущем протоколу;
31 | - Композиция trait не влияет на семантику trait — составной trait эквивалентен «плоскому» trait, содержащему те же методы. — Использование протокола Foo с методом foo(), который унаследован от протоколов Bar с методом bar() и Baz с методом baz() не отличается от использования протокола, с этими 3 методами: foo(), bar(), baz().
32 |
33 | # Правила
34 |
35 | 1. `«Don't start with a class. Start with a protocol.»`. Это утверждение Dave Abrahams с вышеупомянутой сессии. Можно трактовать 2 способами:
36 | - начинайте не с реализации, а с описания контракта (описание функциональности, которую объект будет обязан предоставить потребителям)
37 | - описывайте переиспользуемую логику в протоколах, а не классах. Используйте протокол как единицу переиспользования кода, а класс — как место для уникальной логики. По другому можно описать этот принцип — encapsulate what varies. Хорошим аналогом может стать паттерн «Шаблонный метод». Его идея — отделить общий алгоритм от деталей реализации. Базовый класс содержит общий алгоритм, а дочерние переопределяют определённые шаги алгоритма. В POP общий алгоритм будет содержаться в protocol extension, protocol будет определять шаги алгоритма и используемые типы, а реализация шагов — в классе.
38 | 2. `Композиция через расширения`. Многие слышали фразу «предпочитайте композицию наследованию». В ООП, когда от объекта требуется разный набор функциональности (полиморфное поведение), эту функциональность можно либо разбить на части и организовать иерархию классов, где каждый класс наследует функциональность от предка и добавляет свою, либо разбить на несвязанные иерархией классы, экземпляры которых использовать в связующем классе. Используя возможность добавить соответствие протоколу через расширение мы можем использовать композицию не прибегая к созданию вспомогательных классов. Таким способом зачастую пользуются, когда добавляют viewController-у соответствие различным делегатам. Преимущество перед добавлением соответствия протоколам в самом классе — лучше организованный код:
39 |
40 | ```
41 | extension MyTableViewController: UITableViewDelegate {
42 | // реализация методов из UITableViewDelegate
43 | }
44 |
45 | extension MyTableViewController: UITableViewDataSource {
46 | // реализация методов из UITableViewDataSource
47 | }
48 |
49 | extension MyTableViewController: UITextFieldDelegate {
50 | // реализация методов из UITextFieldDelegate
51 | }
52 | ```
53 |
54 | 3. `Вместо наследования используйте протоколы`.
55 | Dave Abrahams сравнил протоколы с суперклассами, поскольку они позволяют достичь подобия множественного наследования.
56 | Если ваш класс содержит много логики, стоит попытаться разбить его на отдельные наборы функциональности, которые вынести в протоколы.
57 | Разумеется, в случае использования сторонних фреймворков, как Cocoa, наследования не избежать.
58 |
59 | 4. `Используйте Retroactive modeling.`
60 |
61 | Интересный пример с всё той же сессии «Protocol-Oriented Programming in Swift».
62 | Вместо того, чтобы написать класс реализующий протокол Renderer для отрисовки с помощью CoreGraphics, классу CGContext через extension добавляется соответствие этому протоколу.
63 | Перед добавлением нового класса, реализующего протокол, стоит задуматься, есть ли тип (класс/структура/enum), который можно адаптировать к соответствию протокола?
64 | Включайте в протоколы методы, которые можно переопределить (Requirements create customization points).
65 |
66 | Если появилась необходимость переопределить общий метод, определённый в protocol extension, для конкретного класса, то перенесите сигнатуру этого метода в protocol requirements.
67 | Другие классы не придётся править, т.к. продолжат использовать метод из расширения.
68 | Различие будет в формулировке — теперь это «default implementation method» вместо «extension method».
69 |
70 | 5. `Включайте в протоколы методы, которые можно переопределить (Requirements create customization points).`
71 |
72 | Если появилась необходимость переопределить общий метод, определённый в protocol extension, для конкретного класса, то перенесите сигнатуру этого метода в protocol requirements.
73 | Другие классы не придётся править, т.к. продолжат использовать метод из расширения.
74 | Различие будет в формулировке — теперь это «default implementation method» вместо «extension method».
75 |
76 | # Отличия POP от OOP
77 |
78 | **Абстракция**
79 |
80 | В ООП роль абстрактного типа данных играет класс. В POP — протокол. Преимущества протокола как абстракции, по утверждению Apple:
81 |
82 | - `Поддержка типов значения и классов`
83 | - `Поддержка статичных типов и динамичной диспечеризации`
84 | - `Не монолитный`
85 | - `Поддержка retroactive modeling`
86 | - `Не накладывает данные экземпляра на модели`
87 | - `Не накладывает нагрузку инициализации на модели`
88 | - `Четко указывает, что следует реализовать`
89 |
90 | **Инкапсуляция**
91 |
92 | Cвойство системы, позволяющее объединить данные и методы, работающие с ними, в классе.
93 | Протокол не может содержать сами данные, он может содержать только требования на свойства, которые эти данные бы предоставляли.
94 | Как и в ООП, необходимые данные должны быть включены в класс/структуру, но функции могут быть определены как в классе, так и в `extensions`.
95 |
96 | **Полиморфизм**
97 |
98 | POP/Swift поддерживает 2 вида полиморфизма:
99 |
100 | - `полиморфизм подтипов. Он же используется в ООП:`
101 |
102 | ```
103 | func process(service: ServiceType) { ... }
104 | ```
105 |
106 | - `параметрический полиморфизм. Используется в обобщенном программировании.`
107 |
108 | ```
109 | func process(service: Service) { ... }
110 | ```
111 | Набор функций принимаемого типа и его associated types определяется по ограничениям. Мы можем не накладывать ограничения, но в этом случае параметр будет аналогичен типу Any:
112 | ```
113 | func foo(value: T) { ... }
114 | ```
115 | В случае с полиморфизмом подтипов, нам неизвестен конкретный тип, который передаётся в функцию — нахождение реализации методов этого типа будет осуществляться во время выполнения `(Dynamic dispatch)`.
116 | При использовании параметрического полиморфизма — тип параметра известен во время компиляции, соответственно и его методы `(Static dispatch)`.
117 | За счёт того, что на этапе сборки известны используемые типы, компилятор имеет возможность лучше оптимизировать код — в первую очередь, за счёт использования подстановки (inline) функций.
118 |
119 | **Наследование**
120 |
121 | Наследование в ООП служит для заимствования функциональности от родительского класса.
122 | В POP получение нужной функциональности происходит за счёт добавления соответствий протоколам, которые предоставляют функции через extensions.
123 | При этом мы не ограничены классами, имеем возможность расширять за счёт протоколов структуры и enum-ы.
124 |
125 | Протоколы могут наследоваться от других протоколов — это означает добавление к собственным требованиям требований от родительских протоколов.
126 |
127 | Если выделить `основные особенности` протоколов в Swift, у нас получится следующие 7 пунктов:
128 |
129 | - Протоколы не могут хранить состояние
130 | - Протоколы могут быть унаследованы другими протоколами
131 | - Протоколы могут применяться к структурам (struct), классам(class) и перечислениям (enum), определяя функционал типов
132 | - Дженерик протоколы (Generic-protocol) позволяют задавать сложные зависимости между типами и протоколами во время их наследования
133 | - Протоколы не определяют «сильные» и «слабые» ссылки на переменные
134 | - В расширениях к протоколам можно описывать конкретные реализации методов, и вычисляемых переменных (computed values)
135 | - Классовые протоколы разрешают себя наследовать только классам
136 |
137 | # Дополнительные материалы:
138 | - [WWDC16, Protocol-Oriented Programming in Swift](https://developer.apple.com/videos/play/wwdc2015/408/)
139 | - [Protocol-Oriented Programming, реализация](https://www.raywenderlich.com/6742901-protocol-oriented-programming-tutorial-in-swift-5-1-getting-started)
140 |
--------------------------------------------------------------------------------
/roadmap/swift/The SwiftUI render loop.md:
--------------------------------------------------------------------------------
1 | # The SwiftUI render loop
2 |
3 | | Грейд | Middle |
4 | |--------|---------------------------------------|
5 | | Секция | SwiftUI |
6 | | Авторы | https://rensbr.eu/blog/swiftui-render-loop/|
7 |
8 | Так же как и **UIKit**, в **SwiftUI** реализован цикл событий, который отправляет сообщения в UIную часть кода, где
9 | UI интерфейс, в свою очередь, может инициировать повторную отрисовку частей экрана,
10 | где обработка сообщений и отображение графики на экране образуют цикл рендеринга приложения.
11 | Все фреймворки пользовательского интерфейса основаны на цикле отрисовки и
12 | большую часть времени цикл отрисовки работает под капотом, и нам не нужно ничего об этом знать - это
13 | удивительно, так как мы можем писать UI код и даже не понимая, что такое цикл событий, и не беспокоясь о том, как часто отображать содержимое экрана.
14 | Но в некоторых случаях полезно знать, что происходит "под капотом".
15 |
16 | Сначала мы рассмотрим ряд примеров таких случаев, когда полезно знать, как работает цикл отрисовки SwiftUI.
17 | Затем мы рассмотрим цикл отрисовки более подробно и зададим такие вопросы: когда именно `body` будет пересмотрено для отрисовки в SwiftUI.
18 | Даже не "когда", а при каких обстоятельствах:
19 | - В какой момент времени?
20 | - Всегда ли `view` отображается на экране сразу после пересмотра `body`?
21 | - Насколько вообще связаны - пересмотр `body` и рендеринг экрана?
22 | - Мы иногда используем слово "рендеринг" для пересмотра вьюшки на перерисовку, имеет ли это вообще смысл?
23 |
24 | # onAppear
25 | В SwiftUI у нас нет столь много функций из жизненного цикла `UIViewController`, которые мы знаем из UIKit.
26 | Если мы хотим выполнить действие при появлении `view` элемента, то мы можем использовать только одну функцию: `onAppear`.
27 | Но когда именно `onAppear` вызывается?
28 | Вызывается ли он до того, как представление будет отображено и станет видимым на экране, как вo `viewWillAppear?`
29 | И если да, можем ли мы на это положиться?
30 |
31 | Давайте рассмотри вот такой пример:
32 | ```
33 | class UserViewModel: ObservableObject {
34 | @Published var userName: String = "Alex"
35 |
36 | func fetch() {
37 | self.userName = "loading"
38 | // ...
39 | }
40 | }
41 |
42 | struct ContentView: View {
43 | @StateObject var model = UserViewModel()
44 |
45 | var body: some View {
46 | Text(model.userName)
47 | .font(.t1Medium)
48 | .onAppear { model.fetch() }
49 | }
50 | }
51 | ```
52 |
53 | Эта вьюшка готова к отображению только после `fetch` вызова функции вьюмодели в `onAppear`.
54 | Если мы протестируем, то все будет окей. Что происходит на медленном iPhone или на быстром iPhone, который способен отобразить намного больше кадров?
55 | Может ли нам не повезти с частотой обновления дисплея, чтобы надпись "loading" мигала до изменений на "Alex"?
56 | Кроме того, может ли это причинить нам проблемы, если мы добавим переходы из `ContentView` дальше? Насколько это неэффективно?
57 | Мы можем видеть, что `body` будет пересмотрено дважды. А будет ли содержимое отрисовываться дважды?
58 |
59 | # Кастомная вёрстка с preference keys
60 | Мы можем встретиться с такой вёрсткой, которую невозможно будет создать с помощью одних только базовых инструментов, таких как stacks, alignment guides и frames.
61 | Для вёрстки элементов, где вьюшка должна знать размер своих дочерних представлений, этих инструментов может быть недостаточно.
62 |
63 | В качестве примера, допустим, нам нужно представление контейнера, которое ведет себя как **HStack**, пока его дочерние элементы больше
64 | не будут помещаться на экран, после чего он должен продолжить размещение дочерних элементов в следующей строке, например:
65 |
66 | 
67 |
68 | Вот решение такой задачи:
69 | ```
70 | struct Flow: View {
71 | let content: [Content]
72 |
73 | @State private var sizes: [CGSize] = []
74 |
75 | var body: some View {
76 | ZStack(alignment: .topLeading) {
77 | ForEach(0 ..< sizes.count, id: \.self) { i in
78 | content[i]
79 | .background(GeometryReader {
80 | Color.clear
81 | .preference(key: SizesPreferenceKey.self, value: [$0.size])
82 | })
83 | .offset(self.calculateOffset(i)) // uses self.sizes
84 | }
85 | }
86 | }
87 | .onPreferenceChange(SizesPreferenceKey.self) {
88 | sizes = $0
89 | }
90 | }
91 |
92 | // ...
93 | }
94 | ```
95 |
96 | Сначала мы создадим `@State` переменную, которая будет содержать размеры дочерних элементов.
97 | Затем мы используем `GeometryReader*` в бэкграунде дочерних вьюх, чтобы прочитать их размер, и передать эту информацию во вью.
98 | Вернувшись в родительскую вьюшку, мы можем использовать эти настройки в `onPreferenceChange` обработчике для обновления состояния.
99 | С помощью переменной sizes дочерние вьюшки теперь могут быть расположены правильно.
100 |
101 | Этот трюк работает, но `body` вьюшки-контейнера необходимо пересмотреть дважды. При первом проходе, перменная `sizes` еще не заполнена,
102 | поэтому она еще не может правильно расположить дочерние элементы. При проходе по дочерним элементам, `size preference` обновляется.
103 | Затем `body` вьюшки-контейнера оценивается во второй раз, где уже может правильно разместить свое содержимое.
104 |
105 | При первом пересмотре вьюшки - оно еще не готово к отображению, поэтому мы должны задать себе те же вопросы, что и в первом примере.
106 | Только теперь двойной пересмотр `body` может происходить чаще, чем при первом отображении.
107 | Можем ли мы быть уверены, что представление контейнера никогда не отображается с исходной, еще некорректной вёрсткой?
108 | Сколько ненужной отрисовки мы выполняем?
109 |
110 | `*GeometryReader` - используйте с умом. Т.к использовав его направо и налево может привезти к лоу фпс.
111 |
112 | # Аппаратная симфония
113 | Имея базовые знания о низкоуровневых технологиях, используемых в iOS для обработки таких событий, как касания и отображение содержимого на экране, мы теперь можем рассмотреть полный цикл рендеринга SwiftUI.
114 |
115 | 
116 |
117 | Когда юзер ничего не делает, приложение SwiftUI будет бездействовать с `CFRunLoop`.
118 | Ранлуп будет ожидать событий от `источников ввода (input source)`: касания, сетевые события, таймеры или обновление дисплея.
119 | В ответ на касание SwiftUI может вызвать обработчик действия кнопки. Если мы поместим брекпоинт внутри этого обработчика действия,
120 | мы увидим `__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__` где-нибудь в трассировке стека.
121 | Это связано с тем, что события касаний доставляются из [0 input source.](https://stackoverflow.com/questions/65767035/where-is-object-id-of-0-coming-from-in-this-swift-code-that-is-supposed-to-pass)
122 |
123 | В ответ на действие, которое мы выполняем в ответ на событие из `input source`, мы можем обновить некоторую `@State` переменную в представлении
124 | или вызвать функцию для `@ObservedObject`, которая, в свою очередь, вызывает запуск ее `objectWillChange`.
125 | В этом случае вьюшка SwiftUI становится инвалидированным - это означает, что её тело необходимо пересмотреть, но было бы неэффективно делать это немедленно.
126 | Возможно, та же функция, которая изменила `@State` переменную, изменит другую `@State` переменную.
127 | Поэтому пересмотр `body` планируется выполнять позже.
128 |
129 | Если мы поместим брейкпоинт в любую точку `body` вьюшки, то мы сможем увидеть `__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__` -
130 | в трассировке стека. Точно так же, как планирование неявного `CATransaction`, пересмотр для инвалидированного `body` вью элемента
131 | планируется выполнить в конце текущего цикла ранлупа. Это снова реализуется с помощью наблюдателя цикла выполнения, который наблюдает,
132 | когда цикл выполнения входит в `CFRunLoopActivity.beforeWaiting` стадию. Если вью дважды становится инвалидированным в одном и том же цикле выполнения,
133 | оно не будет пересматриваться для отрисовки дважды.
134 |
135 | После того, как все инвалидированные вью элементы были повторно пересмотрены для отрисовки, SwiftUI не сразу возвращает управление обратно в ранлуп.
136 | Сначала вызываются обработчики изменений, такие как `onChange` или `onPreferenceChange` и `onAppear` -
137 | эти обработчики могут сделать вью элементы инвалидированны во второй раз.
138 | Для второго пересмотра отображения вью элемента - SwiftUI не использует наблюдателя из ранлупа.
139 |
140 | Если этот второй пересмотр `body` снова вызовет обработчик изменений и это приведет к еще одной инвалидированному вью элемента,
141 | то SwiftUI временно отключит инвалидированность для вьюшки, чтобы предотвратить бесконечные циклы. Он также выведет предупреждение, подобное этому:
142 |
143 | `onChange(of: _) action tried to update multiple times per frame`
144 |
145 | При повторном пересмотре вью, встроенные вью `(с типом Never)` могут вносить изменения в `CALayer`.
146 | Как мы видели, эти изменения не отображаются на экране немедленно, но они запускают неявное `CATransaction` отображение.
147 | Таким образом, SwiftUI использует ту же оптимизацию, которую мы использовали вручную в приложениях UIKit.
148 |
149 | Только когда зафиксировано неявный `CATransaction`, содержимое вью элементов отображается на экране.
150 | Это также момент, когда вызывается код рендеринга, использующий центральный процессор.
151 | Если SwiftUI выходит из строя в этой части цикла рендеринга, [может быть трудно понять](https://steipete.com/posts/state-of-swiftui/) как это исправить,
152 | потому что трудно увидеть, какая часть какого представления вызвала это.
153 |
154 | В цикле рендеринга существует общий шаблон, используемый для оптимизации кода и обеспечения того, чтобы он вызывался столько раз, сколько необходимо.
155 | Когда вызов функции или изменение переменной запускает обновление, это обновление выполняется не сразу.
156 | Вместо этого это запланировано на более позднее время. Это происходит, когда вьюшки становятся инвалидируются из-за изменения их состояния,
157 | когда вызываются обработчики, такие как `onChange` или `onAppear`, и когда основной анимации необходимо нарисовать графику.
158 | Некоторые из них используют наблюдателей из `CFRunLoop`, и иногда это обрабатывается внутри фреймворка.
159 |
160 | Зная цикл рендеринга, мы можем понять, почему безопасно использовать код, как в примерах в начале.
161 | Ни одно из изменений после первого пересмотра `body` не отображается, поскольку неявная транзакция, частью которой они являются, еще `не зафиксирована.`
162 | Также полезно знать, что делает SwiftUI при отладке или попытке повысить производительность.
163 |
164 | Цикл рендеринга в SwiftUI может быть хорошо скрыт, технологии, которые он использует, те же, что мы использовали в приложениях UIKit,
165 | и они хорошо документированы. Если мы лучше разберемся в том, как это работает, мы сможем лучше понять побочные эффекты кода, который мы пишем, и принимать лучшие решения.
166 | Иногда мы можем сказать "рендеринг" для вью, если имеем в виду оценку его `body`. Но иногда понимание различия может быть очень полезным.
167 |
168 | # Дополнительные ресурсы
169 |
170 | - [The SwiftUI Render Loop](https://mjtsai.com/blog/2022/09/04/the-swiftui-render-loop/)
171 | - [SwiftUI View Lifecycle](https://www.vadimbulavin.com/swiftui-view-lifecycle/)
172 |
--------------------------------------------------------------------------------
/roadmap/memory management/ARC/Side table and object reletionship.md:
--------------------------------------------------------------------------------
1 | # Типы ссылок
2 | Всего в Swift **три** счетчика ссылок: **strong**, **weak** и **unowned**.
3 |
4 | Объект живет в памяти пока на него есть `хотя бы одна strong ссылка`.
5 | И если объекты ссылаются перекрестными сильными ссылками, то они никогда не уничтожаются.
6 | Чтобы этого избежать, нужно одним из объектов сослаться **weak** или **unowned** ссылкой на другой.
7 | Если в момент обращения к weak переменной на объект уже нет strong ссылок, тогда мы получим **nil**. А при обращении к **unowned** будет выброшено исключение.
8 |
9 | # Как было раньше?
10 |
11 | Прежде как перейти к текущей реализации счетчиков ссылок, хочется упомянуть старую, чтобы прояснить, для чего сделали новые механизмы и какие проблемы решили.
12 | До Swift 4, счетчики ссылок располагались для свойств класса прямо в объекте. Класс имел только два счетчика — **weak** и **strong**.
13 |
14 | 
15 |
16 | На объект начинает ссылаться два внешних объекта — один сильно, другой слабо, счетчики прибавляются по одному.
17 |
18 | 
19 |
20 | В один момент времени объект с сильной ссылкой удаляется из памяти, и теперь у нас осталась только одна слабая ссылка. Что происходит в этот момент?
21 |
22 | 
23 |
24 | Данные объекта уничтожаются, но память не освобождается, так как счетчик еще требуется хранить. В памяти остается так называемый «зомби объект», на который ссылается слабая ссылка. Только при обращении по слабой ссылке в runtime будет выполнена проверка: «зомби» (NSZombie) этот объект или нет. Если да, счетчик ссылок уменьшается.
25 |
26 | Xcode умеет находить такие объекты и сообщать о них, плюс [имеет инструмент для этого](https://help.apple.com/instruments/mac/current/#/dev612e6956).
27 |
28 | Данный подход достаточно прозрачный, но главный минус в том, что так объекты могут долго оставаться в памяти, занимая лишнее место, хотя не несут никакой пользы.
29 | Встречался еще один достаточно критичный баг: получение (загрузка) объекта по слабой ссылке было не потокобезопасным!
30 |
31 | ```
32 | import Foundation
33 |
34 | class Target {}
35 |
36 | class WeakHolder {
37 | weak var weak: Target?
38 | }
39 |
40 | for i in 0..<1000000 {
41 | print(i)
42 | let holder = WeakHolder()
43 | holder.weak = Target()
44 | dispatch_async(dispatch_get_global_queue(0, 0), {
45 | let _ = holder.weak
46 | })
47 | dispatch_async(dispatch_get_global_queue(0, 0), {
48 | let _ = holder.weak
49 | })
50 | }
51 | ```
52 |
53 | Данный кусок кода может получить ошибку в **Runtime**. Суть именно в том механизме, который был рассмотрен ранее. Два потока могут одновременно обратиться к объекту по слабой ссылке. Перед тем, как получить объект, они проверяют, является ли проверяемый объект «зомби». И если оба потока получат ответ true, они отнимут счётчик и постараются освободить память. Один из них сделает это, а второй просто вызовет краш, так как попытается освободить уже освобожденный участок памяти.
54 | Такая реализация не очень хороша и с этим нужно что-то делать.
55 |
56 | # Side table
57 | Это дополнительная область памяти в которой хранится дополнительная информация об объекте.
58 | Она опциональна, это означает что она не обязательно будет присутствовать у объекта.
59 | Объекты которые нуждаются в ней - несут некоторые потери в производительности, а те которым она не нужна не испытывают ее “нагрузки”.
60 |
61 | Вместо того чтобы указывать на сам объект, слабые ссылки теперь указывают на побочную таблицу.
62 | Так как побочные таблицы занимают немного места — мы избавляемся от проблемы растраты памяти из-за слабых ссылок на большие объекты.
63 | Это наталкивает на простое решение проблемы потокобезопасности: не обнулять слабые ссылки.
64 |
65 | Так как побочные таблицы малы в размере, мы можем сохранять слабые ссылки на нее пока сами ссылки не будут перезаписаны или уничтожены.
66 | Как только мы начинаем ссылаться на объект слабо `weak reference` - то создается боковая таблица, и теперь объект вместо сильного счетчика ссылок хранит ссылку на боковую таблицу.
67 |
68 | Сама боковая таблица также имеет ссылку на объект. Еще боковая таблица может создаваться, когда `происходит переполнение счетчика`, и он уже не помещается в поле (счетчики ссылок будут маленькими на 32-битных машинах).
69 |
70 | С таким механизмом слабые ссылки ссылаются не напрямую на объект, а на боковую таблицу, которая указывает на объект. Это решает две предыдущие проблемы:
71 | - Экономие памяти: объект удаляется из памяти, если на него больше нет сильных ссылок.
72 | - Это позволяет безопасно обнулять слабые ссылки, поскольку слабая ссылка теперь не указывает напрямую на объект и не является предметом `race condition.`
73 |
74 | # Как устроена side table
75 | Вот так **side table** выглядит в [исходниках языка](https://github.com/apple/swift/blob/c39901d7fb34debbaf51d225b01f2869cd0b101f/stdlib/public/SwiftShims/RefCount.h#L1310):
76 | ```class HeapObjectSideTableEntry {
77 | std::atomic object;
78 | SideTableRefCounts refCounts;
79 | }
80 | ```
81 | - В самом первом поле хранится указатель на объект, которому принадлежит эта **side table**.
82 | - Следом лежит его счетчик ссылок, представленный структурой типа **SideTableRefCounts**. Внутри нее хранится битовое поле со счетчиками ссылок и флагами.
83 |
84 | Side table работает в паре с классом [**WeakReference**](https://github.com/apple/swift/blob/9a5bb49067e21d33c73b32843dcc95f8a88d7a9d/stdlib/public/runtime/WeakReference.h#L156). По сути экземпляр класса WeakReference создается для каждой новой weak переменной.
85 | И взаимодействие со свойствами и методами объекта происходит через него. Класс WeakReference определен следующим образом:
86 | ```
87 | class WeakReference {
88 | union {
89 | std::atomic nativeValue;
90 | #if SWIFT_OBJC_INTEROP
91 | id nonnativeValue;
92 | #endif
93 | };
94 | }
95 | ```
96 | - `nativeValue` сохраняется указатель на нативный объект. Нативным называется объект, структура которого известна рантайму Swift и он может жить без рантайма Objective-C.
97 | - `nonnativeValue` сохраняется объект, который наследуется от NSObject и им управляет рантайм Objective-C.
98 |
99 | Так как это объединение, то в один момент может хранится только одно значение. Флаг `SWIFT_OBJC_INTEROP` указывает на то,
100 | нужна ли интероперабельность с Objective-C – то есть можно ли из Swift кода работать с объектами Objective-C.
101 | На всех платформах от Apple этот флаг активирован.
102 |
103 | # Как работает WeakReference
104 | **WeakReference** хранит указатель на оригинальный объект.
105 | И для его получения вызывается функция **swift_weakLoadStrong**.
106 | Она принимает **WeakReference** единственным аргументом и возвращает указатель на **HeapObject**.
107 | Вызов **swift_weakLoadStrong** также увеличивает на единицу количество strong ссылок.
108 | Эта дополнительная единица сохраняется до конца текущей области видимости weak переменной.
109 |
110 | В конце области видимости **strong** счетчик уменьшается на единицу.
111 | А когда объект уже деалоцирован, то вызов **swift_weakLoadStrong** вернет **null**.
112 | Таким образом, в рантайме реализуется семантика слабых ссылок.
113 | Ведь экземпляр **HeapObject** физически еще присутствует в памяти.
114 | А **WeakReference** выступает в роли обертки и проверяет, не уничтожен ли еще объект с точки зрения рантайма.
115 |
116 | # Счетчик ссылок
117 | Счетчик ссылок хранится внутри структуры **HeapObject**. **HeapObject** – это внутреннее представление объекта в рантайме. То есть каждый экземпляр класса в рантайме это экземпляр структуры с типом **HeapObject**.
118 |
119 | В алгоритме работы счетчика ссылок определено пять состояний, в которых объект находится на всем пути от создания до удаления из памяти.
120 | Можно провести параллель с жизненным циклом **UIViewController**.
121 | Он создается, отображает визуальные элементы, реагирует на вызовы от операционной системы и в конце деаллоцируется.
122 | Состояния объекта перечислены ниже:
123 |
124 | 1. `Live` – объект создан и находится в памяти.
125 | 2. `Deiniting` – объект находится в процессе деинициализации, то есть у него вызван метод `deinit`.
126 | 3. `Deinited` – объект полность деинициализирован.
127 | 4. `Freed` – выделенная память под объект освобождена, но `side table` еще существует.
128 | 5. `Dead` – память занятая side table освобождается.
129 |
130 | 
131 |
132 | - `Live` его счетчики инициализируются со значениями strong — 1, unowned — 1, weak — 1.
133 | На данный момент нет боковой таблицы. Операции с **unowned** переменными работают нормально. Когда `strong reference count` достигает нуля, вызывается deinit(), и объект переходит в следующее состояние (deiniting).
134 | - `Deiniting` - на данном этапе операции со `strong` ссылками не действуют. При чтении через **unowned** ссылку будет срабатывать **assertion failure**.
135 | Но новые **unowned** ссылки еще могут добавляться. Если есть боковая таблица, то **weak** операции будут возвращать nil. Далее из этого состояния уже можно перейти в два других.
136 | - Если нет боковой таблицы, т.e нет `weak ссылок` и нет `unowned ссылок`, то объект переходит в `Dead` состояние и сразу удаляется из памяти.
137 | - Если у нас есть `unowned или weak ссылки`, объект переходит в состояние `Deinited`.
138 | В этом состоянии функция deinit() завершена, сохранение и чтение сильных или слабых ссылок невозможно.
139 | Как и сохранение новых `unowned ссылок`. При попытке чтения `unowned ссылки` вызывается assertion failure. Из этого состояния также возможно два исхода.
140 | - В случае наличия `weak ссылок`, а значит и боковой таблицы, осуществляется переход в состояние `Freed`.
141 | В `Freed` состоянии объект уже полностью освобожден и не занимает места в памяти, но его боковая таблица остается жива.
142 | - После того как счетчик слабых ссылок достигает нуля, боковая таблица также удаляется и освобождает память, и осуществляется переход в финальное состояние — `Dead`.
143 | В этом состоянии от объекта ничего не осталось, кроме указателя на него. Указатель на `HeapObject` освобождается из кучи, не оставляя следов объекта в памяти.
144 |
145 | # Инварианты счетчиков ссылок
146 | Весь жизненный цикл сопровождается инвариантами счетчиков ссылок.
147 | Инвариантность — это выражение, определяющее непротиворечивое внутреннее состояние объекта.
148 |
149 | - Если счетчик strong ссылок становится равен нулю, то объект всегда переходит в состояние **deiniting**. `Unowned` ссылки выкидывают ошибку в **runtime**, а чтение **weak** ссылок возвращает **nil**.
150 | - Счетчик `unowned` ссылок получает +1 от счетчика strong ссылок, который впоследствие уменьшается после завершения функции `deinit()` объекта.
151 | - Счетчик weak ссылок получает +1 от счетчика `unowned` ссылок. Он уменьшается после освобождения (freed) объекта из памяти.
152 |
153 | # Дополнительные материалы:
154 | - [RefCount.h at Swift repo](https://github.com/apple/swift/blob/main/stdlib/public/SwiftShims/RefCount.h)
155 | - [Object life cycle](https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/ObjectLifeCycle.html)
156 | - [Swift 4 Weak References](https://mikeash.com/pyblog/friday-qa-2017-09-22-swift-4-weak-references.html)
157 | - [Swift 4 — слабые ссылки](https://habr.com/ru/post/341014/)
158 |
--------------------------------------------------------------------------------
/roadmap/books/Book list.md:
--------------------------------------------------------------------------------
1 | # 📚 Книги:
2 | Если вы новичок, то вам точно нужно прочитать **The Swift Programming Language** от Apple. Тут даже и описывать нечего, т.к нужно пробежаться по основам языка, синтаксис, конструкции, возможности языка и т.д. Книга постоянно обновляется на актуальную версию языка, поэтому обязательно к прочтению для всех новичков.
3 | - [The Swift Programming Language](https://books.apple.com/ru/book/the-swift-programming-language-swift-5-7/id881256329)
4 | - 💚 trainee
5 | - `основные знания`
6 | #
7 | Данная книга - просто мастхэв для чтения. Хорошо структуированная информация, очень ёмко и точно подано. Эту книгу стоит начинать читать, когда вы уже прошли по основам и хочите углубиться в них.
8 | - [Advanced Swift](https://www.objc.io/books/advanced-swift/)
9 | - 💛 junior+
10 | - `основные знания`, `углубление в материал`
11 | #
12 | Хотите изучить новые инструменты для работы с многопоточность? `Actors`, `tasks` и `async/await` - всё это ждет вас в этой книге.
13 | - [Modern Concurrency in Swift](https://www.raywenderlich.com/books/modern-concurrency-in-swift)
14 | - 💛 junior+
15 | - `многопоточность`, `углубление в материал`, `дополнительные знания`
16 | #
17 | Если вам необходимо структуировать ваши знания или выполнить проекте, ориентируясь на бест практис, то благодаря этой книге, вы сможете изучить/подтянуть:
18 | > **Архитектура приложения**: Узнаете, как организовать свой код с помощью MVVM и разбиение на фичи.
19 |
20 | > **Создание фичей**: Изучите, как структурировать код для работы с разбиением на фичи, которые можно протестировать.
21 |
22 | > **Создавайте код, который масштабируется**: Узнаете принципы создания надежного кода с помощью S.O.L.I.D.
23 |
24 | > **Async/await**: Узнаете, как новая современная модель для работы с многопоточностью может помочь вам в написании хорошо структурированного и асинхронного кода.
25 |
26 | > **Доступность и привлекательные приложения**: Поймёте, как создавать приложения, которые будут хорошо выглядеть и нравиться любой аудитории.
27 |
28 | > **Модульность**: Узнаете, как создавать модульный код, который можно переиспользовать.
29 | - [Real-World iOS](https://www.raywenderlich.com/books/real-world-ios-by-tutorials)
30 | - 💛 junior+
31 | - `основные знания`, `Углубленный материал`, `дополнительные знания`
32 | #
33 | Если вы решили изучить SwiftUI, то в начале вашего пути вам хорошо подойдет эта книга. Посмотрите как привычный подход к разработке на UIKit'e применяется для SwiftUI фреймворка.
34 | - 💚 junior
35 | - `Основные знания`
36 | - [Thinking in SwiftUI](https://www.objc.io/books/thinking-in-swiftui/)
37 | #
38 | Если книга выше, про изучение SwiftUI, вам не подходит или не зашла, то рекомендую ознакомиться этой книгой, где очень все хорошо расписано, и постоянно идет обновление до последней, доступной, версии SUI.
39 | - 💚 junior
40 | - `Основные знания`, `SwiftUI`
41 | - [SwiftUI Views Mastery](https://www.bigmountainstudio.com/views-16)
42 | #
43 | Для работы с SwiftUI вам так - же необходимо будет знать, как правильно взаимодействовать с данными. Всё по делу, ёмко, четко, без излишиств, крайне советую.
44 | - 💚 junior
45 | - `Основные знания`, `SwiftUI`
46 | - [Working with Data in SwiftUI](https://www.bigmountainstudio.com/data)
47 | #
48 | Если вы новичок со SwiftUI - эта книга хороший путеводитель при знакомстве с данным фреймворком + обновлена до swift 5.5.
49 | - 💛 junior+
50 | - `Основные знания`, `SwiftUI`, `Дополнительные знания`
51 | - [SwiftUI Apprentice](https://www.kodeco.com/books/swiftui-apprentice/v1.0)
52 | #
53 | Эту книгу я бы посоветовал для людей, которые только погружаются в разработку. Здесь собраные общие вопросы про то, как лучше обустраивать свою деятельность, обучение, решение задач и т.д. Книга не несёт каких-то глубоких смыслов или мировых открытий, но для людей которые заходят с полного 0 - в некоторых местах может - быть полезна + книга очень маленькая.
54 | - 🤍 Trainee
55 | - `Дополнительные знания`
56 | - [Surviving the coding bootcamp](https://www.amazon.com/Coding-Bootcamp-Survival-Guide-Dream/dp/B09ZGY92JN)
57 | #
58 | Данная книга отлично рассказывает про то, какие софт скилы вам могут помочь в работе, как взаимодействовать с менеджерами, как вести себя на митингах и всё то, что связанно с вашим рабочим процессом, т.е вы и работа, и с окружающими вас коллегами. Однозначно рекомендую эту книгу всем, кто уже работает, но не читал её. Если вы еще не работаете, то можете тоже начать чтение, но будьте готовы не понимать нюансы некоторые и в будущем перечитать её.
59 | - 💚 junior
60 | - `Soft Skills`
61 | - [Engineers Survival Guide: Advice, tactics, and tricks after a decade of working at Facebook, Snapchat, and Microsoft.](https://www.amazon.com/Engineers-Survival-Guide-Facebook-Microsoft/dp/B09MBZBGFK)
62 | #
63 | Документация на стеройдах, но с оговоркой - вот как я бы мог описать эту книгу. Книга хорошая, видно что постарались на славу, особо радует, что чуть больше где-то углубились в материал + в конце каждой главы есть: вывод главы и небольшие упражнения.
64 | - 💚 junior
65 | - `Основные знания`
66 | - [Swift Apprentice](https://www.kodeco.com/books/swift-apprentice/v7.0)
67 | #
68 | Занимательная книга на изучение auto layout’a, где вас будут обучать не только вёрстке из кода или при помощи Interface Builder’a, но так же расскажут про нюансы вёрстки, такие как: layoutSubview, layoutIfNeeded. Разберут на практике адаптивный дизайн, оптимизацию UI компонентов + опытные разработчики делятся с рекомендациями и своим опытом. Так же нравится в этой книге то, что здесь делают упор на основные UI компоненты, которые очень часто встречаются, т.е не растрачивают страницы на элементы, которые могут встретить раз в год.
69 | - 💚 junior
70 | - `Основные знания`, `Углубленный материал`
71 | - [Auto Layout by Tutorials](https://www.kodeco.com/books/auto-layout-by-tutorials)
72 | #
73 | Возможно, вы слышали, что Auto Layout описывается как механизм компоновки, основанный на ограничениях. Что это значит? Нужно ли вам знать математику и писать уравнения? Почему это лучше, чем вручную рассчитывать размер и положение каждого вида в макете? Много подробного материала, разбор не тривиальных кейсов, разбор того, как это всё работет “под капотом”. Если вы её не читали, то это 100% мастхэв.
74 | - 💚 junior
75 | - `Основные знания`, `Углубленный материал`
76 | - [Modern Auto Layout](https://useyourloaf.com/assets/docs/Modern%20Auto%20Layout%20Preview.pdf)
77 | #
78 | В книге изложены основы синхронизации, такие как мьютексы, семафоры, барьеры и условные переменные, и дается множество примеров на языке Python. Эти примеры показывают, как синхронизировать потоки выполнения и предотвратить возможные проблемы, такие как гонки данных и взаимоблокировки. Книга подходит для начинающих программистов и любого, кто интересуется параллельным программированием. Она также может быть полезна для опытных разработчиков, которые хотят освежить свои знания в области синхронизации. Кроме того, книга включает интересные задания в конце каждой главы, которые помогут читателю закрепить полученные знания и улучшить свои навыки программирования.
79 | - 🧡 middle
80 | - `Основные знания`, `Углубленный материал`, `Многопоточность`
81 | - [The Little Book of Semaphores](https://greenteapress.com/semaphores/LittleBookOfSemaphores.pdf)
82 | #
83 | Книга объясняет различные шаблоны проектирования приложений и методы их реализации на примере одного приложения, которое было полностью реализовано с использованием пяти разных шаблонов.
84 | Вместо того чтобы отстаивать какой-либо конкретный шаблон, книга описывает проблемы, которые все архитектуры пытаются решить: построение компонентов приложения, обмен информацией между представлением и моделью, и работа с состоянием, не относящимся к модели. Авторы показывают общие решения для этих проблем и разбирают их на уровне реализации для пяти разных шаблонов проектирования - двух распространенных и трех экспериментальных.
85 | Распространенными шаблонами являются Model-View-Controller и Model-View-ViewModel + Coordinator. Помимо концептуального и уровня реализации, книга обсуждает решения для часто встречающихся проблем, таких как многофункциональные контроллеры представлений.
86 | Изучая эти экспериментальные шаблоны, авторы извлекают ценные уроки, которые могут быть применены к другим шаблонам и существующим кодовым базам.
87 | - 💚 junior
88 | - `Основные знания`, `Углубленный материал`, `Архитектура`
89 | - [App Architecture](https://www.objc.io/books/app-architecture/)
90 | #
91 | "Swift Concurrency by Example" - это практическое руководство для разработчиков.
92 | В книге рассматриваются различные аспекты параллельного и асинхронного программирования, такие как гонка данных, блокировки, потокобезопасность, атомарные операции и другие. Она также показывает, как использовать новые конструкции языка, такие как async/await и structured concurrency, для более простого и понятного кода.
93 | В книге приводятся множество примеров, начиная от простых сценариев до более сложных, таких как многопоточная обработка данных, параллельная обработка изображений и другие.
94 | - 🧡 middle
95 | - `Основные знания`, `Углубленный материал`, `Архитектура`
96 | - [Swift concurrency by example](https://www.hackingwithswift.com/quick-start/concurrency)
97 | #
98 | Книга очень полезна для разработчиков и инженеров, которые работают с распределенными системами или хотят изучить их. Она предоставляет практические примеры и иллюстрации, а также объясняет ключевые концепции и технологии, используемые в распределенных системах.
99 | Некоторые из основных тем, рассмотренных в книге, включают в себя:
100 | • Принципы распределенных систем, такие как прозрачность, надежность и масштабируемость.
101 | • Ключевые концепции, такие как клиент-серверная архитектура, мультиагентные системы и peer-to-peer сети.
102 | • Распределенные алгоритмы, такие как алгоритмы выбора лидера, алгоритмы репликации данных и алгоритмы взаимной блокировки.
103 | • Распределенные системы времени реального мира, такие как системы управления трафиком и авионикой.
104 | - 🤎 middle+
105 | - `Углубленный материал`, `Архитектура`
106 | - [Распределенные системы](https://vk.com/wall-54530371_189394)
107 | #
108 | Книга "Practical Core Data” - это практическое руководство по использованию фреймворка Core Data. Автор подробно объясняет, как создавать и работать с моделями данных, как использовать объекты управления контекстом для сохранения, извлечения и обновления данных, а также как использовать Core Data для реализации функций, таких как фильтрация, сортировка и агрегация данных. Книга повествует о том, как работать с отношениями между объектами, использовать запросы для извлечения данных, и синхронизации данных между несколькими устройствами и серверами. Особое внимание уделено лучшим практикам и советам по проектированию приложений с использованием Core Data.
109 | - 🧡 middle
110 | - `Основные знания`, `Углубленный материал`, `База данных`
111 | - [Practical Core Data: A modern guide to the Core Data framework](https://donnywals.gumroad.com/l/practical-core-data)
112 | #
113 | В книге представлены сложные алгоритмы в понятном и доступном формате, используя примеры из реальной жизни и множество иллюстраций. Книга начинается с базовых алгоритмов, таких как бинарный поиск и сортировка выбором, и постепенно переходит к более сложным алгоритмам, таким как графовые алгоритмы и динамическое программирование. Книга также включает в себя упражнения и задачи, которые помогут закрепить материал и применить новые знания на практике. Одной из особенностей книги является то, что она подходит как для начинающих, так и для опытных программистов. Независимо от уровня знаний, тут можно множество полезной информации и примеров, которые помогут ему лучше понять и применять алгоритмы в своей работе.
114 | - 💛 junior+
115 | - `Основные знания`, `Углубленный материал`, `Дополнительный материал`, `Общее программирование`
116 | - [Грокаем Алгоритмы](https://vk.com/wall-54530371_184116)
117 |
--------------------------------------------------------------------------------
/roadmap/data structures/Arrays.md:
--------------------------------------------------------------------------------
1 | # Массивы
2 | `Array` - структура, тип значения, является универсальным контейнером общего назначения для хранения **упорядоченной** коллекции элементов,
3 | где через него можно выполнить итерацию по крайней мере один раз.
4 |
5 | 
6 |
7 | - `_ContiguousArrayStorage` - выделение памяти для хранения элементов, обеспечение быстрого доступа по индексу.
8 | - `_ArrayBridgeStorage` - абстракция, позволяющая использовать как нативное хранилище, так и **NSArray**.
9 | - `_ArrayBuffer` - реализация copy on write.
10 | - `Array` - публичный интерфейс массива.
11 |
12 | # Размерность массива
13 | Каждый массив резервирует определенный объем памяти для хранения его содержимого.
14 | Когда вы добавляете элементы в массив и этот массив начинает превышать свою зарезервированную емкость, массив выделяет большую область памяти
15 | и копирует свои элементы в новое хранилище, где новое хранилище кратно размеру старого хранилища.
16 |
17 | 
18 |
19 | Эта экспоненциальная стратегия роста означает, что добавление элемента происходит за постоянное время `O(1)`, усредняя производительность многих операций добавления.
20 | Операции добавления, запускающие перераспределение, имеют затраты на производительность, но они происходят все реже по мере увеличения массива.
21 |
22 | Пример:
23 | Создадим массив с десятью элементами. Swift выделит этому массиву достаточную емкость для хранения только этих десяти элементов,
24 | таким образом и для `array.capacity` и для `array.count` будут равны 10.
25 |
26 | ```
27 | var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
28 |
29 | print(array.capacity) // 10
30 | print(array.count) // 10
31 | ```
32 | Давайте добавим **11** и **12** элементы. Наш массив не имеет для этого емкости, поэтому ему нужно освободить место - он найдет память для хранения большего
33 | количества элементов, скопирует массив туда, а затем добавит **11** и **12** элементы, где время выполнения этих вставок составляет сложность - `O(n)`, где n - количество элементов в массиве.
34 |
35 | ```
36 | array.append(11)
37 | array.append(12)
38 |
39 | print(array.capacity) // 20
40 | print(array.count) // 12
41 | ```
42 |
43 | Таким образом, при добавлении 11 и 12 элементов в массив с **емкостью 10**, swift создаст массив с размером **20**.
44 | И когда мы превысим этот размер, то следующая ёмкость будет 40, потом 80, пото 160 и так далее.
45 |
46 | > Если приблизительно известно, сколько элементов необходимо сохранить, используйте метод `reserveCapacity(_:)` перед добавлением в массив,
47 | чтобы избежать промежуточных перераспределений.
48 | >> Используйте свойства `capacity` и `count`, чтобы определить, сколько элементов массив может хранить без выделения больших ресурсов.
49 |
50 | ```
51 | var stringArray = Array()
52 | stringArray.reserveCapacity(128)
53 | ```
54 |
55 | Для массивов с типом `T`, который является ссылочным типом, например `класс` или типом протокола `@objc`,
56 | это место хранения может быть смежным блоком памяти или экземпляром `NSArray`, где **NSArray** - это неизменяемый класс, ссылочного типа, а `NSMutableArray` - изменяемый сабкласс от NSArray.
57 | Поскольку любой произвольный подкласс **NSArray** может стать **Array**, в этом случае нет никаких гарантий относительно представления или эффективности в данном кейсе.
58 |
59 | Примечание:
60 | `Array` подобен `ContiguousArray`, если **T** не является **ссылочным типом** или **объектом Objective-C**.
61 | В противном случае он может использовать для хранения «NSArray», подключенный от Cocoa.
62 |
63 | 
64 |
65 | `ContiguousArray` - является специализированным массивом, который всегда хранит свои блоки памяти непрерывно. Если тип элемента массива является ссылочным типом или протоколом @objc и нет необходимости соединять массив с `NSArray` или передавать массив к `Objective-C API`, то использование `ContiguousArray` может быть более эффективным и иметь более предсказуемую производительность, чем Array. Если тип элемент массива является тип значения, например **структура** или **перечисление**, то Array и ContiguousArray должны иметь одинаковую эффективность.
66 |
67 | Список, когда `Array` хранится непрерывно, подобно `ContiguousArray` хранению:
68 | - Массив созданный в Swift
69 | - Элементами массива являются тип - значения: структуры, перечесления и etc.
70 | - Массивы на платформах без среды выполнения Objective-C
71 |
72 | Единственный раз, когда `Array` не будет непрерывно храниться - это если его элемент ссылочного типа, например классы и был подключен к `NSArray`. Но даже тогда, во многих случаях, `NSArray` будет непрерывно храниться. Этот случай называю "ленивый мост" (lazy bridging), поэтому в свифте и есть надобность в `ContiguousArray`, т.к оптимизатор не может устранить ветвь, которая проверяет ленивый мост для каждого элемента, получаемого из массива. Например, при итерации, когда этот массив содержит классы, так что иногда можно обнаружить, что использование `ContiguousArray` дает небольшое ускорение для некоторого кода. Обычно это нано-оптимизация, а также никогда не применяется в Linux или с массивами структур.
73 |
74 | Сравнение **Array** и **ContiguousArray**, так же **lazy Array** и **lazy ContiguousArray**:
75 |
76 | ```
77 | import Foundation
78 |
79 | protocol Possibles {
80 | init(repeating: Bool, count: Int)
81 | subscript(index: Int) -> Bool { get set }
82 | var count: Int { get }
83 | }
84 | extension Array: Possibles where Element == Bool {}
85 | extension ContiguousArray: Possibles where Element == Bool {}
86 |
87 | func lazySieveOfEratosthenes(makeStorage: () -> Storage) -> [Int] {
88 | var possibles = makeStorage()
89 | let limit = possibles.count - 1
90 | return (2 ... limit).lazy.filter { i in // Lazy, so that `possibles` is updated before it is used.
91 | possibles[i]
92 | }.map { i in
93 | stride(from: i * i, through: limit, by: i).lazy.forEach { j in
94 | possibles[j] = false
95 | }
96 | return i
97 | }.reduce(into: [Int]()) { (result, next) in
98 | result.append(next)
99 | }
100 | }
101 |
102 | func forSieveOfEratosthenes(makeStorage: () -> Storage) -> [Int] {
103 | var possibles = makeStorage()
104 | let limit = possibles.count - 1
105 | var result = [Int]()
106 | for i in 2 ... limit where possibles[i] {
107 | var j = i * i
108 | while j <= limit {
109 | possibles[j] = false
110 | j += i
111 | }
112 | result.append(i)
113 | }
114 | return result
115 | }
116 |
117 | func test(_ name: String, _ storageType: String, _ function: (Int) -> [Int]) {
118 | let start = Date()
119 | let result = function(100_000)
120 | let end = Date()
121 | print("\(name) \(storageType) biggest: \(result.last ?? 0) time: \(end.timeIntervalSince(start))")
122 | }
123 | test("for ", "Array ") { limit in
124 | forSieveOfEratosthenes {
125 | Array(repeating: true, count: limit + 1)
126 | }
127 | }
128 | test("for ", "ContiguousArray") { limit in
129 | forSieveOfEratosthenes {
130 | ContiguousArray(repeating: true, count: limit + 1)
131 | }
132 | }
133 | test("lazy ", "Array ") { limit in
134 | lazySieveOfEratosthenes {
135 | Array(repeating: true, count: limit + 1)
136 | }
137 | }
138 | test("lazy ", "ContiguousArray") { limit in
139 | lazySieveOfEratosthenes {
140 | ContiguousArray(repeating: true, count: limit + 1)
141 | }
142 | }
143 | ```
144 | Результат:
145 | ```
146 | for Array biggest: 99991 time: 41.016937017440796
147 | for ContiguousArray biggest: 99991 time: 40.648478984832764
148 | lazy Array biggest: 99991 time: 3.3549970388412476
149 | lazy ContiguousArray biggest: 99991 time: 3.5851539373397827
150 | ```
151 | Еще один прогон и результат:
152 | ```
153 | for Array biggest: 99991 time: 41.801795959472656
154 | for ContiguousArray biggest: 99991 time: 42.37710893154144
155 | lazy Array biggest: 99991 time: 3.438219904899597
156 | lazy ContiguousArray biggest: 99991 time: 3.4085270166397095
157 | ```
158 | Как видно, не всегда `Array` медленнее, чем `ContiguousArray`
159 | > Прогон проходил на такой спеке:
160 | - macOS Monterey (Version 12.3)
161 | - Processor 2.3 GHz 8-Core Intel core i9
162 | - Memory 16 GB DDR4
163 | - Xcode Version 13.3.1 (13E500a), Playground
164 |
165 | Пример кода взят [отсюда](https://forums.swift.org/t/execution-time-contiguousarray-vs-array/12467/21), советую ознакомиться со всем тредом)
166 |
167 | P.S:
168 | 
169 | По-поводу того, что `ContiguousArray` оказался медленне `Array` я нашел только вот такое, небольшое сообщение, но не стоит брать эти слова за догмму, так как, возможно, код выше где-то оказался неточным 🤔
170 |
171 | # Массив с классами
172 |
173 | Если элементы в массиве являются **экземплярами класса**, семантика одинакова, хотя сначала они могут выглядеть разными. В этом случае значения, хранящиеся в массиве, являются ссылками на объекты, находящиеся вне массива. При изменении ссылки на объект в одном массиве **только этот массив** имеет ссылку на новый объект. Однако если два массива содержат ссылки на один и тот же объект, можно наблюдать изменения свойств этого объекта из обоих массивов.
174 |
175 | ```
176 | class MyClass {
177 | var name = "Name"
178 | }
179 |
180 | var firstArray = [MyClass(), MyClass()]
181 | var secondArray = firstArray
182 |
183 | firstArray[0].name = "Another Name"
184 | print(firstArray[0].name) // "Another name"
185 | print(secondArray[0].name) // "Another name
186 | ```
187 | - Замены, добавления и удаления имеют область видимости только для массива, к которому применяется действи:
188 | ```
189 | firstArray[0] = MyClass()
190 |
191 | print(firstArray[0].name) // "Name"
192 | print(secondArray[0].name) // "Another name
193 | ```
194 |
195 | Операции с массивом и их сложность:
196 | - `O(1) — Константное время(самое быстрое)` Доступ к элементу в массиве по его индексу:
197 | ```
198 | let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
199 |
200 | array[3]
201 | ```
202 | - `O(n log n) - Логарифмическое время(немного по-хуже, чем константное время)` Сортировка по возрастанию, стандартная функция:
203 | ```
204 | var people = ["Sandra", "Mike", "James", "Donald"]
205 | people.sort()
206 | ```
207 | - `O(n) - Линейное время(немного по-хуже, чем логарифмическая сложность)` Подсчёт суммы элементов, при помощи стандартной функции `forEach` или же for i in array:
208 | ```
209 | let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
210 | var sumOfNumbers = 0
211 |
212 | numbers.forEach {
213 | sumOfNumbers += $0
214 | }
215 |
216 | print(sumOfNumbers) // 55
217 | ```
218 | - `O(n²) - Квадратичное время(немного по-хуже, чем O(n log n)` Прохождение по 2D массиву:
219 | ```
220 | var twoDimensionalArray = [["first one", "second one"], ["first two", "second two"], ["first third", "second third"]]
221 |
222 | for i in 0.. Bool in
15 | return s1 > s2
16 | })
17 | ```
18 |
19 | # non-escaping closure
20 |
21 | Замыкание может быть обозначено как **`non-escaping`**, что означает, что оно не может быть вызвано позже, когда функция, в которую оно было передано, уже завершена.
22 |
23 | Таким образом, замыкание не может "выбраться из" функции и будет выполнено только внутри функции. Обычно это используется, когда замыкание используется только внутри функции и не нужно хранить ссылку на него вне функции.
24 |
25 | Обозначение **`non-escaping`** замыкания также указывает компилятору, что замыкание не может вызываться асинхронно, что может помочь оптимизировать код.
26 |
27 | Вот пример того, как можно обозначить замыкание как **`non-escaping`**:
28 |
29 | ```
30 | func someFunction(closure: () -> Void) {
31 | // функция использует замыкание только внутри себя
32 | }
33 |
34 | someFunction {
35 | // это не-выбрасывающееся замыкание
36 | }
37 | ```
38 |
39 | Использование `non-escaping` closure позволяет оптимизировать производительность, так как компилятор может быть уверен, что замыкание не будет вызвано позже, и не будет создано ненужное временное хранилище для его хранения.
40 |
41 | > 🎓
42 | >
43 | > Содержимое `non-escaping` closure хранится на стеке памяти, потому что оно не может быть вызвано после того,
44 | > как функция, которая его принимает, завершила свою работу. Это означает, что как только функция завершает работу,
45 | > все локальные переменные, включая non-escaping closure, удаляются из стека.
46 |
47 | # escaping closure
48 |
49 | Это замыкание, которое может быть вызвано **после того**, как функция, которая его принимает, завершила свою работу. То есть это замыкание "выбирается наружу" из функции и может быть вызвано в будущем.
50 |
51 | Чтобы указать, что замыкание является escaping, нужно использовать ключевое слово **`@escaping`** перед типом замыкания в определении функции. Например:
52 |
53 | ```
54 | func someFunction(@escaping closure: () -> Void) {
55 | // тело функции
56 | }
57 | ```
58 |
59 | Использование escaping closure позволяет запускать какие-то действия после того, как функция завершит свою работу. Например, можно выполнить
60 | некоторую работу в фоновом режиме, а затем вызвать escaping closure, чтобы уведомить пользователя о результатах.
61 |
62 | > 🎓
63 | >
64 | > `Escaping closure` хранится в куче, так как оно может быть вызвано после того, как функция, которая его принимает, завершила свою работу.
65 |
66 | # Почему не стоит использовать escaping closures в SwiftUI
67 |
68 | В **Swift 5.1** появилась возможность группировать объекты в нечто единое целое в декларативном стиле.
69 | Это похоже на массив внутри closure-блока, однако элементы перечисляются с новой строки без запятых и return.
70 | Данный механизм назвали Function Builder. Это нашло широкое применение в SwiftUI.
71 | На основе `Function Builder` сделали `ViewBuilder` — декларативный конструктор интерфейса.
72 | Используя `ViewBuilder` нам больше не нужно писать `addSubview` для каждого элемента – достаточно перечислить все
73 | `View` с новой строки внутри closure-блока. SwiftUI сам добавит и сгруппирует элементы в более сложный родительский контейнер.
74 |
75 | ```
76 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
77 | @_functionBuilder public struct ViewBuilder {
78 |
79 | /// Builds an empty view from an block containing no statements, `{ }`.
80 | public static func buildBlock() -> EmptyView
81 |
82 | /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through
83 | /// unmodified.
84 | public static func buildBlock(_ content: Content) -> Content where Content : View
85 | }
86 | ```
87 |
88 | Как пример реализации:
89 | ```
90 | struct Collapsable: View {
91 | @State var collapsed = false
92 |
93 | init(@ViewBuilder content: () -> Content) {
94 | // ...
95 | }
96 |
97 | var body: some View {
98 | VStack {
99 | if !collapsed {
100 | // content...
101 | }
102 | Button(collapsed ? "↓ open" : "↑ close") {
103 | collapsed.toggle()
104 | }
105 | }
106 | }
107 | }
108 | ```
109 |
110 | Применение:
111 |
112 | ```
113 | var body: some View {
114 | // ...
115 | Collapsable {
116 | ExtraOptions()
117 | }
118 | }
119 | ```
120 |
121 | При реализации `Collapsable` вью есть выбор его реализации. Либо это замыкание, которое возвращает вьюху немедленно и хранит `content` внутри вью.
122 | Либо это `escaping closure`.
123 |
124 | ```
125 | // Подход # 1
126 | struct Collapsable: View {
127 | let content: Content
128 |
129 | init(@ViewBuilder content: () -> Content) {
130 | self.content = content()
131 | }
132 |
133 | // ...
134 | }
135 |
136 | // Подход # 2
137 | struct Collapsable: View {
138 | let content: () -> Content
139 |
140 | init(@ViewBuilder content: @escaping () -> Content) {
141 | self.content = content
142 | }
143 |
144 | // ...
145 | }
146 | ```
147 |
148 | Подход #2 на превый взгляд может показаться весьма эффективным - использовать `content` только тогда, когда нам это нужно.
149 |
150 | # SwiftUI views as functions.
151 |
152 | Мы думаем о замыканиях как о функциях, но **view** в SwiftUI концептуально *также* являются функциями. Входными данными являются переменные, некоторые из них определяются как константы и устанавливаются в инициализаторе, некоторые из них управляются SwiftUI и реализуются при помощи проперти врапперов, как пример - `@State`. Результатом представления SwiftUI, рассматриваемого как функция, является **body**.
153 |
154 | **View** в SwiftUI не выполняют никакой работы сами по себе, все состояния управляются SwiftUI, и единственный раз, когда ему нужны **view** - это когда изменяется один из входных данных, и ему нужно знать, какая **output view** соответствует этим **input view**.
155 |
156 | https://www.hackingwithswift.com/quick-start/swiftui/all-swiftui-property-wrappers-explained-and-compared
157 |
158 | Если сравнить замыкание и SwiftUI view в памяти runtime исполнения, то будет видно, что они очень похожи. SwiftUI view в runtime’e представляют собой просто набор констант и переменных, которыми они инициализируются, и ссылку на их body. Замыкания в runtim’e представляют собой набор параметров, которые они захватывают, и ссылку на функцию.
159 |
160 | **SwiftUI** использует структуры вместо замыканий, чтобы мы могли определять переменные, которыми **SwiftUI** управляет извне. Но еще одна причина, по которой **SwiftUI** использует структуры вместо замыканий для **view**, заключается в том, что теперь можно выполнять серьезную оптимизацию, чтобы **переоценивать body для отрисовки** столько раз, сколько это необходимо.
161 |
162 | В контексте функций, многие из этих оптимизаций имеют имена. Например, SwiftUI может принять решение не **переоценивать body для отрисовки**, если его переменные не изменились, это называется [memoization](https://en.wikipedia.org/wiki/Memoization).
163 |
164 | # Lazy without closures
165 |
166 | Чтобы понять, почему аналогия view и функций полезна, и чтобы увидеть, почему подход #1 неплохо влияет на производительность, интересно взглянуть на `NavigationLink` и на то, как мы можем определить его `destination`.
167 |
168 | ```
169 | NavigationLink("Details", destination: DetailsView())
170 | ```
171 |
172 | Это означает, что **DetailsView** уже создан в момент отображения нашей NavigationLink, разве это эффективно? Причина, по которой это не так, заключается в том, что **destination** является вью элементом, и следовательно, уже является функцией. Его **output** - это **body** и он не будет **оценивать свой body для отрисовки**, пока не будет отображен.
173 |
174 | Можно подумать, что это изменится, когда будем использовать более сложный **DetailView**, который использует ******************viewModel******************, которая запускает сетевой запрос при инициализации, но это не так. **ObservableObject** поступает извне, и если view использует **viewModel** как **@StateObject**, то тогда **viewModel** не создается до тех пор, пока не будет отображенa **view**. Это одна из причин, по которой **StateObject** инициализируется с [autoclosure](https://developer.apple.com/documentation/swiftui/stateobject/init(wrappedvalue:)).
175 |
176 | Точно так же, как **destination** у **NavigationLink** не **оценивает свой body для отрисовки** до тех пор, пока не отобразится. Содержимое **Collapsable** в подходе #1 поступит аналогично - не будет **оценивать свой body для отрисовки**, пока не отоброзится. В обоих подходах нам нужно хранить переменные, которые использует **content**, и знать какое **body** вычислять для этих переменных. Хранение замыкания нас ни от чего не спасает.
177 |
178 | # Сравнение реализаций Collapsable элемента
179 |
180 | В подходе #2 реализации **Collapsable** используется замыкание для хранения **content**. Из-за оптимизации, которую SwiftUI использует для view, подход №2 имеет нежелательный и интересный побочный эффект.
181 |
182 | Сначала проверим поведение view для Collapsable, написанного с использованием подхода #1:
183 |
184 | ```
185 | struct Collapsable: View {
186 | @State var collapsed: Bool = true
187 | let content: Content
188 |
189 | init(@ViewBuilder content: () -> Content) {
190 | self.content = content()
191 | }
192 |
193 | var body: some View {
194 | VStack {
195 | if !collapsed {
196 | content
197 | }
198 | Button(collapsed ? "↓ open" : "↑ close") {
199 | collapsed.toggle()
200 | }
201 | }
202 | }
203 | }
204 | ```
205 |
206 | Реализуем этот кастомный элемент так, чтобы значение счетчика можно было свернуть - развернуть и увеличить:
207 |
208 | ```
209 | struct ContentView: View {
210 | @State var value: Int = 0
211 |
212 | var body: some View {
213 | VStack {
214 | Collapsable {
215 | Text("Value: \(value)")
216 | }
217 | Button("Increase") { value += 1 }
218 | }
219 | }
220 | }
221 | ```
222 |
223 | Все работает как положено - нажатие на кнопку увеличения обновит состояние, а значение счетчика можно будет скрыть и отобразить.
224 |
225 | Теперь всё тоже самое, только решение #2:
226 |
227 | ```
228 | struct Collapsable: View {
229 | @State var collapsed: Bool = true
230 | let content: () -> Content
231 |
232 | init(@ViewBuilder content: @escaping () -> Content) {
233 | self.content = content
234 | }
235 |
236 | var body: some View {
237 | VStack {
238 | if !collapsed {
239 | content()
240 | }
241 | Button(collapsed ? "↓ open" : "↑ close") {
242 | visible.toggle()
243 | }
244 | }
245 | }
246 | }
247 | ```
248 |
249 | В результате имплементации #2 появилась ошибка. Обычно это работает нормально, но если увеличить счетчик до того, как **Collapsable** **view** будет раскрыта в **первый раз**, и только потом развернуть его - увидим неправильное значение **0**. Если увеличить состояние ещё раз, оно перепрыгнет с 0 на текущее значение счетчика.
250 |
251 | **Основной причиной** этой ошибки является еще одна интересная оптимизация **SwiftUI**: если переменная **@State** не используется при **оценивании своего body для отрисовки**, изменение этой переменной не запускает **оценивание body для отрисовки** вьюхи и не запускает обновление свойств этого **view** до их значений. Это умная оптимизация, и она хорошо работает вместе с мемоизацией:
252 |
253 | > Если входная переменная не используется, view не нужно оценивание body для отрисовки при изменении этой переменной. Когда переменная используется, view нужно оценить body для отрисовки, только когда переменная изменилась.
254 |
255 | Конкретно в этом случае это работает некорректно. Когда мы **оцениваем** **body для отрисовки** в первый раз и текст свернут - переменная **@State** никогда не используется, поэтому кажется, что она не нужна при **оценивании body для отрисовки**. Когда мы расширяем текст, **@State** внезапно считывается, даже несмотря на то, что сам **ContentView** не **оценивает** **body** повторно, а **@State** не подготовлен должным образом.
256 |
257 | Есть способы исправить эту ошибку, сохранив наш **content** в качестве замыкания, но сейчас идет борьба с оптимизациями **SwiftUI.**
258 |
259 | # Сравнение замыканий и вывод
260 | Если проверить то, как оценивается `body для отрисовки` Collapsable, то увидим, что второй способ предотвращает мемоизацию. Это можно проверить, добавив print в `body` Collapsable (хотя инвалидация представления — это внутреннее поведение SwiftUI, которое ведет себя очень неожиданным образом):
261 |
262 | ```
263 | var body: some View {
264 | let _ = print("Evaluating body")
265 | // ...
266 | }
267 | ```
268 |
269 | https://swiftui-lab.com/equatableview/
270 |
271 | Добавим **@State** переменную в **ContentView** и кнопку, которая отображает эту переменную и позволяет его изменить:
272 |
273 | ```
274 | struct ContentView: View {
275 | @State var value: Int = 0
276 | @State var unrelated: Int = 0
277 |
278 | var body: some View {
279 | VStack {
280 | Collapsable {
281 | Text(value: "\(value)")
282 | }
283 | Button("Increase") { value += 1 }
284 | Button("Unrelated state (\(unrelated))") { unrelated += 1 }
285 | }
286 | }
287 | }
288 | ```
289 |
290 | Это дополнение поможет принудительно *переоценивать body для отрисовки.*
291 |
292 | Если использовать реализацию подхода #1, только нажатие кнопки «Увеличить» выведет на экран ( при помощи добалвенного print’a) «Evaluating body». Но при реализации подхода #2 видно, что даже вторая кнопка вызывает **переоценку** **body** **для отрисвоки** Collapsable view. Ясно, что мемоизация сломалась, но почему?
293 |
294 | В данном случае причина довольно проста. Чтобы SwiftUI мог это сделать, ему необходимо **сравнить** **входные** данные. Но замыкания не являются **Equatable** и SwiftUI не может проверить, изменилось ли это **закрытие** с момента последней **оценки body для отрисовки**.
295 |
296 | Да, замыкания можно использовать. Есть случаи, когда вам нужно сохранить замыкание. Такие элементы, как **ForEach**, принимают параметр и должны хранить замыкание. Но в других случаях - лучше не делать этого.
297 |
298 | Хранение замыкания, которое возвращает **view** вместо реализации “просто view” - не является оптимизацией. View — это функции, и эти функции очень легковесны. Кроме того, они позволяют SwiftUI наиболее эффективно выполнять оптимизацию.
299 |
300 | > 📖
301 | >
302 | > Огромное спасибо вот этой статье за такой весьма важный разбор и нюанс. [ссылка](https://rensbr.eu/blog/swiftui-escaping-closures/)
303 |
304 | # Дополнительный материал
305 | - [Clojure Don’ts: Lazy Effects](https://stuartsierra.com/2015/08/25/clojure-donts-lazy-effects)
306 | - [Don't use escaping closures in SwiftUI](https://rensbr.eu/blog/swiftui-escaping-closures/)
307 | - [Swift Closures Explained](https://www.youtube.com/watch?v=ND44vQ5iJyc&t=1s)
308 | - [Swift. Урок 11: Замыкания - основы программирования](https://www.youtube.com/watch?v=r99kIrhJDRY)
309 | - [Swift 5 - Замыкания (или closures, блоки, лямбды)](https://www.youtube.com/watch?v=xChc880qvWE)
310 |
--------------------------------------------------------------------------------
/roadmap/design principles/Solid.md:
--------------------------------------------------------------------------------
1 | # Принципы SOLID
2 |
3 | Это не паттерны и их нельзя назвать догмами, которые обязательно применять при разработке, однако следование этим принципам улучшает код программы, упрощает его изменение и поддержку.
4 |
5 | В первую очередь, всегда старайтесь придерживаться `SRP` и `OCP`. Что же касается `DIP` и `LSP`, порой ими можно пренебречь, если того требует контекст.
6 | В идеале, разумеется, следует придерживаться всех 5 акронимов (мнение автора этого репозитория).
7 |
8 | # Single Responsibility Principe
9 | [`Single Responsibility Principe`](https://solidbook.vercel.app/srp) - Каждый объект должен иметь одну ответственность и эта ответственность должна быть полностью инкапсулирована в эту сущность.
10 | Всё поведение объекта должно быть направлено исключительно на обеспечение этой ответственности.
11 |
12 | К примеру, следование первому принципу (`SRP`) может означать, что в вашем файле с отрисовкой `UI` не должно быть иного функционала. Добавление в этот файл бизнес-логики (запрос в базу данных, запрос на сервер) или всего того, что отличается от отрисовки интерфейса и также добавляет еще одну ответственность, и тем самым нарушает принцип акронима `SRP`.
13 |
14 | 
15 |
16 | Т.е принцип `единственной ответственности`:
17 | - Помогает разбивать и декомпозировать задачи по одной на модуль;
18 | - Уменьшает количество модулей, которые надо изменить при изменении требований;
19 | - Ограничивает влияние изменений, помогая контролировать сложность системы.
20 |
21 | Пример реализации `SRP`- принципа, данный класс выполняет единственную задачу - отрисовывает элементы интерфейса.
22 | ```
23 | class CustomView: UIView {
24 |
25 | private let view = UIView()
26 |
27 | private let label = CustomLabel()
28 |
29 | init() {
30 | setupAppearance()
31 | }
32 |
33 | func setupAppearance() {
34 | view.backgroundColor = .green
35 | }
36 |
37 | func set(data: SomeData) {
38 | label.set(with: data)
39 | }
40 |
41 | }
42 | ```
43 |
44 | Но вы всё еще можете добавлять функционал, не нарушая принцип `SRP`.
45 | Для этого реализуйте сохранение в отдельной сущности или файле.
46 | ```
47 | class CustomView: UIView {
48 |
49 | private let view = UIView()
50 |
51 | private let label = CustomLabel()
52 |
53 | private let dataBase = SomeDataBase()
54 |
55 | init() {
56 | setupAppearance()
57 | }
58 |
59 | func setupAppearance() {
60 | view.backgroundColor = .green
61 | }
62 |
63 | func set(data: SomeData) {
64 | label.set(with: data)
65 | }
66 |
67 | func saveToDB(name: String) {
68 | dataBase.save(name)
69 | }
70 |
71 | }
72 | ```
73 |
74 | # Open Closed Principle
75 | [`Open Closed Principle`](https://solidbook.vercel.app/ocp) - Помогает исключить такую проблему. Согласно ему модули должны быть открыты для расширения, но закрыты для изменения.
76 |
77 | - **Открыто для расширения:** Расширение или изменение поведения без кардинальных трансформаций
78 | - **Закрыто для изменения:** Расширение класса без изменения реализации.
79 |
80 | Основная причина, по которой вносить изменения бывает трудно или дорого — когда небольшое изменение в одной части системы вызывает лавину изменений в других частях. Грубо и утрировано: если в программе для изменения цвета кнопки надо поправить 15 модулей, такая система спроектирована плохо.
81 |
82 | 
83 |
84 | Простыми словами — модули надо проектировать так, чтобы их требовалось менять как можно реже, а расширять функциональность можно было с помощью создания новых сущностей и композиции их со старыми.
85 |
86 | Модули, которые `удовлетворяют OCP`:
87 |
88 | открыты для расширения — их функциональность может быть дополнена с помощью других модулей, если изменятся требования;
89 | закрыты для изменения — расширение функциональности модуля не должно приводить к изменениям в модулях, которые его используют.
90 |
91 | Класс **Logger** итерирует массив автомобилей и печатает детали.
92 | ```
93 | class Car {
94 | let name: String
95 | let color: String
96 | init(name: String, color: String) {
97 | self.name = name
98 | self.color = color
99 | }
100 | func printDetails() -> String {
101 | return "I have \(self.color) color \(self.name)."
102 | }
103 | }
104 |
105 | class Logger {
106 | func printData() {
107 | let cars = [ Car(name: "BMW", color: "Red"),
108 | Car(name: "Audi", color: "Black")]
109 | cars.forEach { car in
110 | print(car.printDetails())
111 | }
112 | }
113 | }
114 | ```
115 | Но если вы хотите, чтобы класс **Logger** печатал детали других классов, потребуется вручную добавлять каждый новый класс в реализацию `printData`.
116 |
117 | ```
118 | class Bike {
119 | let name: String
120 | let color: String
121 | init(name: String, color: String) {
122 | self.name = name
123 | self.color = color
124 | }
125 | func printDetails() -> String {
126 | return "I have \(self.name) bike of color \(self.color)."
127 | }
128 | }
129 |
130 | class Logger {
131 | func printData() {
132 | let cars = [ Car(name: "BMW", color: "Red"),
133 | Car(name: "Audi", color: "Black")]
134 | cars.forEach { car in
135 | print(car.printDetails())
136 | }
137 | let bikes = [ Bike(name: "Homda CBR", color: "Black"),
138 | Bike(name: "Triumph", color: "White")]
139 | bikes.forEach { bike in
140 | print(bike.printDetails())
141 | }
142 | }
143 | }
144 | ```
145 | Можно решить эту проблему, создав протокол **Printable**, который будет реализован классами для регистрации. Тем самым **printData()** напечатает массив **Printable**.
146 |
147 | Таким образом, создается новый абстрактный слой между **printData()** и классом для регистрации, позволяющий печатать другие классы, такие как **Bike**, без изменения реализации **printData()**.
148 |
149 | ```
150 | protocol Printable {
151 | func printDetails() -> String
152 | }
153 |
154 | class Car: Printable {
155 | let name: String
156 | let color: String
157 | init(name: String, color: String) {
158 | self.name = name
159 | self.color = color
160 | }
161 | func printDetails() -> String {
162 | return "I have \(self.color) color \(self.name)."
163 | }
164 | }
165 |
166 | class Bike: Printable {
167 | let name: String
168 | let color: String
169 | init(name: String, color: String) {
170 | self.name = name
171 | self.color = color
172 | }
173 | func printDetails() -> String {
174 | return "I have \(self.name) bike of color \(self.color)."
175 | }
176 | }
177 |
178 | class Logger {
179 | func printData() {
180 | let vehicles: [Printable] = [Car(name: "BMW", color: "Red"),
181 | Car(name: "Audi", color: "Black"),
182 | Bike(name: "Honda CBR", color: "Black"),
183 | Bike(name: "Triumph", color: "White")]
184 | vehicles.forEach { vehicle in
185 | print(vehicle.printDetails())
186 | }
187 | }
188 | }
189 | ```
190 |
191 | # Liskov Substitution Principle
192 | [`Liskov Substitution Principle`](https://solidbook.vercel.app/lsp) - Принцип подстановки Барбары Лисков. Объекты могут заменяться на экземпляры их подтипов, сохраняя работоспособность программы.
193 |
194 | Класс-наследник должен дополнять базовый класс, а не изменять его. Этот принцип может помочь использовать наследование корректно.
195 | Простыми словами — классы-наследники не должны противоречить базовому классу. Например, они не могут предоставлять интерфейс ýже базового. Поведение наследников должно быть ожидаемым для функций, которые используют базовый класс.
196 |
197 | 
198 |
199 | `Принцип подстановки Барбары Лисков:`
200 |
201 | - Помогает проектировать систему, опираясь на поведение модулей;
202 | - Вводит ограничения и правила наследования объектов, чтобы их потомки не противоречили базовому поведению;
203 | - Делает поведение модулей последовательным и предсказуемым;
204 | - Помогает избегать дублирования, выделять общую для нескольких модулей функциональность в общий интерфейс;
205 | - Позволяет выявлять при проектировании проблемные абстракции и скрытые связи между сущностями.
206 |
207 | ```
208 | let requestKey: String = "NSURLRequestKey"
209 |
210 | // NSError subclass provide additional functionality but don't mess with original class.
211 |
212 | class RequestError: NSError {
213 | var request: NSURLRequest? {
214 | return self.userInfo[requestKey] as? NSURLRequest
215 | }
216 | }
217 |
218 | // I forcefully fail to fetch data and will return RequestError.
219 |
220 | func fetchData(request: NSURLRequest) -> (data: NSData?, error: RequestError?) {
221 | let userInfo: [String:Any] = [requestKey : request]
222 | return (nil, RequestError(domain:"DOMAIN", code:0, userInfo: userInfo))
223 | }
224 |
225 | func willReturnObjectOrError() -> (object: AnyObject?, error: NSError?) {
226 |
227 | let request = NSURLRequest()
228 | let result = fetchData(request: request)
229 |
230 | return (result.data, result.error)
231 | }
232 |
233 | let result = willReturnObjectOrError()
234 |
235 | // RequestError
236 |
237 | if let requestError = result.error as? RequestError {
238 | requestError.request
239 | }
240 | ```
241 |
242 | # Interface Segregation Principle
243 | [Interface Segregation Principle](https://solidbook.vercel.app/isp) - Принцип разделения интерфейсов говорит о том, что слишком «толстые» интерфейсы необходимо разделять на меньшие и специфические, чтобы программные сущности меньших интерфейсов знали только о методах, которые необходимы им в работе.
244 |
245 | `Принцип разделения интерфейса:`
246 |
247 | - Помогает бороться с наследованием или реализацией ненужной функциональности;
248 | - Даёт возможность спроектировать модули так, чтобы их затрагивали изменения только тех интерфейсов, которые они действительно реализуют;
249 | - Снижает зацепление модулей;
250 | - Уничтожает наследование ради наследования, поощряет использование композиции;
251 | - Позволяет выявлять более высокие абстракции и находить неочевидные связи между сущностями.
252 |
253 | 
254 |
255 | ```
256 | // Изначально есть один протокол, реализующий функцию отрисовки
257 |
258 | protocol DrawableProtocol {
259 | func didDraw()
260 | }
261 |
262 | // Далее, вы решили добавить еще несколько методов отрисовки
263 |
264 | protocol DrawableProtocol {
265 | func didDraw()
266 | func didDrawCircle()
267 | func didDrawSquare()
268 | }
269 | ```
270 | Если класс **CustomDraw** реализует протокол `DrawableProtocol`, то необходимо будет реализовать и все его методы.
271 |
272 | ```
273 | class CustomDraw: DrawableProtocol {
274 | func didDraw() { }
275 |
276 | func didDrawCircle() { }
277 |
278 | func didDrawSquare() { }
279 | }
280 |
281 | // Даже если вам потребуется отрисовать только квадрат, протокол всё равно подтянет остальные функции
282 |
283 | class DrawSquare: DrawableProtocol {
284 | func didDraw() { }
285 |
286 | func didDrawCircle() { }
287 |
288 | func didDrawSquare() { }
289 | }
290 | ```
291 |
292 | Исходя из принципа `ISP`, реализация кода должна быть следующей:
293 |
294 | ```
295 | protocol DrawableProtocol {
296 | func didDraw()
297 | }
298 |
299 | protocol CircleDrawableProtocol {
300 | func didDrawCircle()
301 | }
302 |
303 | protocol SquareDrawableProtocol {
304 | func didDrawSquare()
305 | }
306 |
307 | class CustomDraw: DrawableProtocol, CircleDrawableProtocol, SquareDrawableProtocol {
308 | func didDraw() { }
309 |
310 | func didDrawCircle() { }
311 |
312 | func didDrawSquare() { }
313 | }
314 |
315 | class DrawSquare: SquareDrawableProtocol {
316 | func didDrawSquare() { }
317 | }
318 | ```
319 |
320 | # Dependency Inversion Principle
321 | [Dependency Inversion Principle](https://medium.com/movile-tech/dependency-inversion-principle-in-swift-18ef482284f5) - Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
322 | Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
323 |
324 | `Зацепление и связность`
325 |
326 | Зацепление (coupling) не стоит путать со связностью (cohesion).
327 |
328 | [Зацепление](https://ru.wikipedia.org/wiki/Зацепление_(программирование)) — степень взаимозависимости разных модулей. Чем выше зацепление, тем более хрупкой получается система, и тем сложнее вносить изменения.
329 |
330 | [Связность](https://ru.wikipedia.org/wiki/Связность_(программирование)) — степень, в которой задачи некоторого модуля, связаны друг с другом. Чем выше связность, тем строже модули следуют SRP, тем выше сфокусирован модуль на конкретной задаче.
331 |
332 | 
333 |
334 | `DIP и тестируемость`
335 | При тестировании модуля, который зависит от других модулей, нам нужно либо создавать экземпляр каждой зависимости, либо создать заглушки.
336 |
337 | DIP упрощает тестирование системы. Если модули зависят от интерфейсов, нам достаточно создать заглушку, реализующую этот интерфейс.
338 |
339 | `Принцип инверсии зависимостей:`
340 |
341 | - Вводит правила и ограничения для зависимости одних модулей от других;
342 | - Снижает зацепление модулей;
343 | - Делает тестирование модулей проще;
344 | - Позволяет проектировать систему так, чтобы модули были заменяемы на другие.
345 |
346 | ```
347 | class FileSystemManager {
348 | func save(string: String) {
349 | // Открыть файл
350 | // Сохранить string в этот файл
351 | // Закрыть файл
352 | }
353 | }
354 |
355 | class Handler {
356 | let fileManager = FilesystemManager()
357 | func handle(string: String) {
358 | fileManager.save(string: string)
359 | }
360 | }
361 | ```
362 | **FileSystemManager** - это низкоуровневый модуль, который легко использовать в других проектах. Проблема заключается в том, что модуль **High-level** **Handler** не используется повторно, поскольку он тесно связан с **FileSystemManager**. Вы должны иметь возможность повторно использовать модуль высокого уровня с различными типами хранилищ (e.g., база данных, облако).
363 |
364 | Можно решить эту зависимость с помощью протокола **Storage**. Таким образом, **Handler** может использовать этот абстрактный протокол без учета типа хранилища. При таком подходе легко перейти от файловой системы к базе данных.
365 |
366 | ```
367 | protocol Storage {
368 | func save(string: String)
369 | }
370 |
371 | class FileSystemManager: Storage {
372 | func save(string: String) {
373 | // Открыть файл
374 | // Сохранить string в этот файл
375 | // Закрыть файл
376 | }
377 | }
378 |
379 | class DatabaseManager: Storage {
380 | func save(string: String) {
381 | // Сконнектить базу данных
382 | // Выполнить запрос для сохранения строки в таблице
383 | // Завершить соединение с базой данных
384 | }
385 | }
386 |
387 | class Handler {
388 |
389 | let storage: Storage
390 |
391 | init(storage: Storage) {
392 | self.storage = storage
393 | }
394 |
395 | func handle(string: String) {
396 | storage.save(string: string)
397 | }
398 | }
399 | ```
400 |
401 | # Дополнительный материал:
402 | - [Single Responsibility Principle. Не такой простой, как кажется](https://habr.com/ru/post/454290/)
403 | - [Refactoring and Open / Closed principle](https://softwareengineering.stackexchange.com/questions/170547/refactoring-and-open-closed-principle)
404 | - [Ковариантность и контравариантность](https://ru.wikipedia.org/wiki/Ковариантность_и_контравариантность_(программирование))
405 |
--------------------------------------------------------------------------------
/roadmap/memory management/Delayed deallocation.md:
--------------------------------------------------------------------------------
1 | > 🎓
2 | >
3 | > `allocation` - выделение памяти, а `deallocation` - высвобождение памяти.
4 | >
5 | > `MRC` - manual reference counting (ручной подсчет ссылок). В этой модели мы полагались на ключевые слова `retain` и `release` для аллокации и деаллокации объекта.
6 |
7 | > 🎓
8 | >
9 | > `ARC` - automatic reference counting (автоматический подсчет ссылок). В этой модели аллокация и деаллокация памяти происходит автоматически, т.е не нужно в ручную проставлять `retain` и `release` .
10 |
11 | В большинстве случаев **ARC** работает так, как надо. Разработчику обычно не нужно беспокоиться об утечках памяти, когда неиспользуемые объекты остаются неосвобожденными на неопределённое время. Но не всегда! Возможны утечки памяти.
12 |
13 | Как вы считаете в этом примере будет утечка памяти?
14 |
15 | ```
16 | DispatchQueue.main.async {
17 | self.alert?.text = "Goodbye :)"
18 | }
19 | ```
20 |
21 | А в этом?
22 |
23 | ```
24 | let changeColorToRed = DispatchWorkItem { [weak self] in
25 | self?.view.backgroundColor = .red
26 | }
27 | ```
28 |
29 | # Retain cycle
30 | Представим ситуацию, когда два объекта больше не используются, но каждый из них ссылается на другой. Так как у каждого счетчик ссылок не равен 0, ни один из них не будет освобождён.
31 | 
32 |
33 | Это цикл сильных ссылок **(strong reference cycle)** называется как “retain cycle”. Такая ситуация сбивает с толку ARC и не позволяет ему очистить память. Счетчик ссылок в конце не равен 0 и, хотя никакие объекты уже не нужны, object1 и object2 не будут освобождены.
34 |
35 | # Что из себя представляет жизненный цикл объекта?
36 |
37 | 1. **Выделение памяти (аллокация):** берет память из стека или кучи.
38 | 2. **Инициализация**: выполняется `init` код
39 | 3. **Эксплуатация**: объект используется
40 | 4. **Деинициализация**: выполняется `deinit` код
41 | 5. `Высвобождение памяти (деаллокация):` память возвращается стеку или куче обратно.
42 |
43 | # Strong, Weak и Unowned
44 |
45 | Если используется **self** внутри замыкания, **scope** замыкания будет поддерживать сильную ссылку на **self** в течение **всего срока** жизни этого замыкания.
46 |
47 | **«Сильный» захват (strong capturing)**
48 | До тех пор, пока явно не указан способ захвата, используется **«сильный» захват**. Это означает, что замыкание захватывает используемые внешние значения на протяжение жизни замыкания.
49 |
50 | **«Слабый» захват (weak capturing)**
51 |
52 | Можно создать "список захвата", чтобы определить, каким именно образом захватываются используемые значения. Альтернативой «сильному» захвату является «слабый» и его применение приводит к следующим последствиям:
53 | 1. «Слабо» захваченные значения не удерживаются замыканием и, таким образом, они могут быть освобождены и установлены в nil
54 | 2. Как следствие первого пункта, «слабо» захваченные значения всегда optional.
55 |
56 | **«Бесхозный» захват (unowned capturing)**
57 | Альтернативой «слабому» захвату является «бесхозный» и разница между ними в том, что значение не является **optional** , но в случае обращения к объекту помеченному как **unowned**, в момент когда значение будет равно **nil** приведёт к крашу в рантайме.
58 |
59 | > P.S. Про side table и другие более углубленные различия и описания типов ссылок можно узнать тут: ⇒ [ссылка](https://github.com/SomeStay07/iOS-Developer-Roadmap/blob/main/roadmap/memory%20management/ARC/Side%20table%20and%20object%20reletionship.md)
60 |
61 | 
62 |
63 | # Escaping vs non-escaping closures
64 |
65 | Существует два вида замыканий: non-escaping и escaping.
66 |
67 | **non-escaping:**
68 | - Когда замыкание передается в аргументах функции и используется до того, как выполнится тело функции и управление вернется обратно.
69 | - Когда функция завершается, переданное замыкание выходит из области видимости и больше не существует в памяти.
70 |
71 | **escaping:**
72 | - Когда замыкание передается в аргументах функции и используется после того, как выполнится тело функции и управление вернется обратно.
73 | - Когда функция завершается, переданное замыкание продолжает существовать в области видимости и находится в памяти, пока замыкание не будет выполнено.
74 |
75 | # Delayed Deallocation
76 |
77 | Отложенное освобождение - это сайд эффект, который возникает как при escaping, так и при **non-escaping **замыканиях. Это не совсем утечка памяти, но это может привести к нежелательному поведению, например: задисмиссили вьюконтроллер, но его память не освобождается до тех пор, пока не будут завершены все ожидающие замыкания/операции).
78 | Вот несколько событий, которые сохраняют область видимости в рабочем состоянии:
79 | - Замыкание (escaping или non-escaping) может выполнять какую-то долгую последовательную (serial) работу, тем самым задерживая возврат своей области до тех пор, пока вся работа не будет завершена.
80 | - Замыкание (escaping или non-escaping) может использовать механизм блокировки потока (например семафоры), который может задержать или предотвратить возврат области видимости.
81 | - **escaping** замыкание может быть запланировано для выполнения после задержки, например: DispatchQueue.asyncAfter или UIViewPropertyAnimator.startAnimation
82 | - **escaping** замыкание может ожидать обратного вызова с длительным таймаутом, например: **URLSession timeoutIntervalForResource**
83 |
84 | ```
85 | func delayedAllocAsyncCall() {
86 | let url = URL(string: "https://www.google.com:81")!
87 |
88 | let sessionConfig = URLSessionConfiguration.default
89 | sessionConfig.timeoutIntervalForRequest = 999.0
90 | sessionConfig.timeoutIntervalForResource = 999.0
91 | let session = URLSession(configuration: sessionConfig)
92 |
93 | let task = session.downloadTask(with: url) { localURL, _, error in
94 | guard let localURL = localURL else { return }
95 | let contents = (try? String(contentsOf: localURL)) ?? "No contents"
96 | print(contents)
97 | print(self.view.description)
98 | }
99 |
100 | task.resume()
101 | }
102 | ```
103 |
104 | - Интервал ожидания запроса составляет 999 секунд.
105 | - **weak** или **unowned** не используется.
106 | - Внутри замыкания используется **self**
107 | - Задача нигде не сохраняется, она выполняется немедленно.
108 |
109 | Основываясь на последнем пункте, эта задача не должна вызывать сильного захвата, однако, если запустить приложение по описанному выше сценарию, а затем задисмиссить вью-контроллер, не отменяя задачу загрузки, то можно убедиться в том, что память вью-контроллера не была освобождена.
110 |
111 | Есть **escaping** замыкание, которое ожидает обратного вызова, и мы дали ему длительный интервал ожидания. Это замыкание держит сильную ссылку на любые объекты, на которые ссылаются внутри своего тела (в данном случае **self**), то до тех пор, пока оно не будет вызвано, пройдет таймаут или если задача будет отменена.
112 |
113 | > (Я не уверен, как URLSession работает под капотом, но я предполагаю, что он сохраняет сильную ссылку на задачу до тех пор, пока она не будет выполнена, отменена или не достигнет крайнего срока.)
114 | >
115 |
116 | Здесь нет **сильного захвата**, но замыкание будет держать **self** до тех пор, пока это необходимо, тем самым задерживая высвобождение **self**. Использование **[weak self]** предотвратило бы задержку, позволив **self** немедленно высвободиться.
117 |
118 | Использование [**unowned self**] привело бы к сбою.
119 |
120 | # ‘guard let self = self’ vs Optional Chaining
121 |
122 | При использовании [weak self] существует потенциальный сайд эффект использования **guard let self = self**, вместо доступа к **self** с использованием опциональной цепочки.
123 |
124 | В замыканиях, которые могут задерживать высвобождение из-за долгой, последовательной работы или из-за механизма блокировки потоков, например семафор, использование **guard let self = self else { return }** в начале замыкания, не предотвратит эту задержку высвобождения.
125 |
126 | Чтобы показать почему, допустим, у есть замыкание, которое последовательно выполняет несколько долгих операций над UIImage:
127 |
128 | ---
129 |
130 | ```swift
131 | func process(image: UIImage, completion: @escaping (UIImage?) -> Void) {
132 | DispatchQueue.global(qos: .userInteractive).async { [weak self] in
133 | guard let self = self else { return }
134 | // perform expensive sequential work on the image
135 | let rotated = self.rotate(image: image)
136 | let cropped = self.crop(image: rotated)
137 | let scaled = self.scale(image: cropped)
138 | let processedImage = self.filter(image: scaled)
139 | completion(processedImage)
140 | }
141 | }
142 | ```
143 |
144 | Используется **[weak self]** вместе с синтаксисом **guard let** в начале замыкания. Что на самом деле делает **guard let** здесь? - Проверяет, является ли `self` `nil'oм`, и если это не так, он создает временную сильную ссылку на **self** на время действия области **видимости**.
145 |
146 | Когда исполнение программы дойдет до долгой работы (**let rotated**), обращение к **self** будет как к сильной ссылке, которая предотвращает **высвобождение self** до тех пор, пока не достигнется конец области для замыкания. Другими словами, **guard let** гарантирует, что **self** будет жить пока живет замыкание.
147 |
148 | Если не использовать синтаксис guard let, а вместо этого использовать опциональную цепочку для self. Проверка на nil для self будет выполняться при каждом вызове метода вместо создания сильной ссылки в начале замыкания. Это означает, что если self окажется равным nil в любой момент во время выполнения замыкания, он автоматически пропустит вызов этого метода и перейдет к следующей строке.
149 |
150 | # GCD
151 |
152 | Ни один из этих вызовов не вызовет утечку памяти, даже без [**weak self**], потому-что они выполняются немедленно:
153 |
154 | ---
155 |
156 | ```swift
157 | func nonLeakyDispatchQueue() {
158 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
159 | self.view.backgroundColor = .red
160 | }
161 |
162 | DispatchQueue.main.async {
163 | self.view.backgroundColor = .red
164 | }
165 |
166 | DispatchQueue.global(qos: .background).async {
167 | print(self.navigationItem.description)
168 | }
169 | }
170 | ```
171 |
172 | Однако **DispatchWorkItem** вызовет **утечку**, потому-что мы храним его в локальном свойстве и ссылаемся на **self** внутри замыкания, без использования [**weak self**]:
173 |
174 | ---
175 |
176 | ```swift
177 | func leakyDispatchQueue() {
178 | let workItem = DispatchWorkItem { self.view.backgroundColor = .red }
179 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: workItem)
180 | self.workItem = workItem // stored in a property
181 | }
182 | ```
183 |
184 | # UIView.Animate and UIViewPropertyAnimator
185 |
186 | Схоже с **GCD**, вызов анимации не представляет риска сильного захвата, если только **UIViewPropertyAnimator** не хранится в свойстве.
187 |
188 | Например, эти вызовы безопасны:
189 |
190 | ---
191 |
192 | ```swift
193 | func animteToRed() {
194 | UIView.animate(withDuration: 3.0) {
195 | self.view.backgroundColor = .red
196 | }
197 | }
198 | ```
199 |
200 | ---
201 |
202 | ```swift
203 | func setupAnimation() {
204 | let anim = UIViewPropertyAnimator(duration: 2.0, curve: .linear) {
205 | self.view.backgroundColor = .red
206 | }
207 | anim.addCompletion { _ in
208 | self.view.backgroundColor = .white
209 | }
210 | anim.startAnimation()
211 | }
212 | ```
213 |
214 | ---
215 |
216 | Следующий код, вызовет сильный захват ссылок, потому-что сохраняем анимацию для последующего использования без использования [**weak self**]:
217 |
218 | ---
219 |
220 | ```swift
221 | func setupAnimation() {
222 | let anim = UIViewPropertyAnimator(duration: 2.0, curve: .linear) {
223 | self.view.backgroundColor = .red
224 | }
225 | anim.addCompletion { _ in
226 | self.view.backgroundColor = .white
227 | }
228 | self.animationStorage = anim
229 | }
230 | ```
231 |
232 | ---
233 |
234 | # Хранение функции в свойстве
235 |
236 | Следующий пример демонстрирует **хитрую** утечку памяти, которая может-быть не обнаружена.
237 |
238 | Может быть полезно передать замыкания или функции одного объекта другому, который будет сохранен в свойстве. Допустим, вы хотите, чтобы объект **A** вызывал какой-либо метод из объекта **B** анонимно, не подвергая объект **B** воздействию **A**. Думайте об этом как о легкой альтернативе **делегированию**.
239 |
240 | В качестве примера, здесь есть вью-контроллер, который хранит замыкание в свойстве:
241 |
242 | ---
243 |
244 | ```swift
245 | class PresentedController: UIViewController {
246 | var closure: (() -> Void)?
247 | }
248 | ```
249 |
250 | ---
251 |
252 | Есть главный вью-контроллер (которому принадлежит вышеупомянутый вью-контроллер), и хотим передать один из методов главного вью-контроллера, который будет сохранен в замыкании вызванного вью-контроллера:
253 |
254 | ---
255 |
256 | ```swift
257 | class MainViewController: UIViewController {
258 |
259 | var presented = PresentedController()
260 |
261 | func setupClosure() {
262 | presented.closure = printer
263 | }
264 |
265 | func printer() {
266 | print(self.view.description)
267 | }
268 | }
269 | ```
270 |
271 | ---
272 |
273 | `printer()` - это функция на главном вью-контроллере и эта функция была присвоена свойству **closure**. Обратите внимание, что идёт присваение, а не включение в круглые скобки, т.к нет возвращаемого значение функции. Вызов замыкания изнутри вью-контроллера теперь выведет описание **MainViewController**.
274 |
275 | Этот код тянет за собой **сильный захват**, даже несмотря на то, что явно не использовали **self**. Здесь подразумевается **self** (думайте об этом как о **self.printer**), поэтому замыкание будет поддерживать сильную ссылку на **self.printer**, в то время как **self** владеет вью-контроллером, который, в свою очередь, владеет замыканием.
276 |
277 | Чтобы зафиксить это, можно поступить вот так:
278 |
279 | ```swift
280 | func setupClosure() {
281 | self.presented.closure = { [weak self] in
282 | self?.printer()
283 | }
284 | }
285 | ```
286 |
287 | # Таймеры
288 |
289 | Таймеры могут вызвать проблемы, даже если они не хранитятся в свойстве.
290 |
291 | ---
292 |
293 | ```swift
294 | func leakyTimer() {
295 | let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
296 | let currentColor = self.view.backgroundColor
297 | self.view.backgroundColor = currentColor == .red ? .blue : .red
298 | }
299 | timer.tolerance = 0.1
300 | RunLoop.current.add(timer, forMode: RunLoop.Mode.common)
301 | }
302 | ```
303 |
304 | 1. Таймер повторяется
305 | 2. **self** используется без [********weak self]********
306 |
307 | До тех пор, пока выполняются эти два условия, таймер будет препятствовать освобождению вью-контроллера/объектов. Так что технически это скорее **отложенная аллокация**, чем утечка памяти; задержка просто длится бесконечно.
308 |
309 | Обязательно аннулируйте таймеры, когда они больше не нужны, чтобы избежать утечки памяти, и не забудьте использовать **[weak self]** чтобы не получить захвать сильной ссылки.
310 |
311 | # Вывод
312 |
313 | - `[unowned self]` может - быть очень плохой идеей
314 | - Non-escaping замыкания не требуют указания [weak self] или [unowned self] **до тех пор**, пока мы не задумываемся о [****Delayed Deallocation****](https://www.notion.so/Delayed-Deallocation-7465394b2e8649acb658c868c235c293)
315 | - Escaping замыкание требует `[weak self]` или `[unowned self]` если они где-то хранятся или передаются другому замыканию, а объект внутри них сохраняет ссылку на замыкание.
316 | - `guard let self = self` - в некоторых случаях это может привести к задержке в высвобождении, что может быть хорошо или плохо, в зависимости от того, что нам нужно.
317 | - GCD и анимация не требуют использовать `[weak self]` или `[unowned self]`, если нет сохранения их в свойстве для последующего использования.
318 | - Будьте осторожны с таймерами.
319 | - Если есть сомнения, можно использовать `deinit`, либо `instrument's` либо `memory graph`
320 |
321 | [You don't (always) need [weak self]](https://medium.com/@almalehdev/you-dont-always-need-weak-self-a778bec505ef)
322 |
323 | [The Nested Closure Trap](https://medium.com/flawless-app-stories/the-nested-closure-trap-356a0145b6d)
324 |
--------------------------------------------------------------------------------
/roadmap/swift/Optional.md:
--------------------------------------------------------------------------------
1 | # Что такое optional в swift и как он используются
2 | `Optional` - механизм, указывающий на возможное отсутствие значения или ссылки на объект.
3 | Хоть и на понимание опциональности может потребоваться время, чтобы привыкнуть, для тех кто не знаком с этой концепцией, но использование опционального типа может сделать код более безопасным и выразительным.
4 |
5 | # Что является optional в Swift
6 | `Optional` является специальным типом в swift, экземпляр которого могут содержать или не содержать значения данного типа.
7 | **Optional** вариант может рассматриваться как коробка, которая предназначена для обуви, если там есть обувь - коробка содержит значение, если обуви нет, коробка не содержит значение.
8 |
9 | После создания **опционального** типа, экземпляр может находиться в одном из двух состояний - он либо содержит один экземпляр указанного обернутого типа, либо является пустым. Пустой, опциональный параметр эквивалентен нулю, который является специальным литералом, представляющим отсутствие значения.
10 |
11 | Опциональность является настолько важной частью swift, что в язык был встроен специальный синтаксис, чтобы сделать их простыми в использовании.
12 | Мы можем объявить **optional wrapping** любого типа в swift, просто постфиксируя имя типа, который будет упакован с вопросительным знаком `(?)`. Это относится не только к типам в стандартной библиотеке, таким как Int или String, но и к определяемым пользователем типам, включая ссылочные типы (классы и замыкания) и типы значений (структуры, перечисления и кортежи).
13 |
14 | В качестве примера мы объявляем опциональный тип **String** в качестве обёрнутого типа и даем ему значение **String** для записи.
15 |
16 | ```
17 | var justString: String? = "Just a string"
18 | ```
19 | Мы можем использовать `type(of:)` функцию, чтобы проверить `justString` и использовать проверку равенства, чтобы подтвердить, что это не равно нолю.
20 | ```
21 | print(type(of: justString)) // Optional
22 | print(justString == nil) // false
23 | ```
24 | Приведенная выше конструкция нарушает строгие правила ввода, поскольку мы можем назначить значение **String** переменной типа **Optional**.
25 | Это одна из многочисленных синтаксических удобств, встроенных в swift, чтобы упростить использование опциональности.
26 | При назначении экземпляра обернутого как опциональная переменная, то "под-копотом" создается опциональная оболочка данного экземпляра, которая назначается переменной.
27 |
28 | Та же автоматическая обертка работает при вызове функций, что означает, что если у нас есть функция с опциональным параметром, мы можем передать в качестве аргумента опциональный экземпляр типа, обернутого как опциональный тип.
29 |
30 | Важно отметить, что обратное не соответствует действительности.
31 | Если мы попытаемся назначить опциональную переменную или передать опциональную функцию, которая ожидает экземпляр не опционального типа, то мы получим ошибку.
32 |
33 | **Swift** позволяет создавать опциональный тип, без предоставления экземпляра.
34 | ```
35 | var justString: String?
36 | ```
37 | Если мы не даем опциональной переменной начальное значение при объявлении, то автоматически создается пустая опциональная переменная, равная нулю, и назначается ей.
38 | Это единственный случай, когда **Swift** автоматически инициализирует переменную.
39 | Мы можем подтвердить это, делая тип и равенство проверив **justString**.
40 | ```
41 | print(type(of: justString)) // Optional
42 | print(justString == nil) // true
43 | ```
44 | # Почему в Swift есть опциональность
45 | Бывают случаи, когда нам нужно каким-то образом сигнализировать в нашем коде, что значение или ссылка на объект могут существовать или не существовать.
46 | Другой пример, мы хотели бы быть уверенными, что когда мы ожидаем иметь значение или ссылку на объект, он будет там.
47 | В языках, не имеющих встроенной поддержки опционально, где такие гарантии не встроены в язык, это может привести к нежелательным последствиям в коде, таким как ненужная проверка для обеспечения того, чтобы ссылка на объект указывала на фактический объект, или же при работе со `скалярными типами`, используя [sentinel values](https://en.wikipedia.org/wiki/Sentinel_value), которые являются произвольными значениями, имеющими особое значение в данном контексте, например функция предназначенная для возврата индекса массива, возвращающего **-1**, когда нет допустимого значения индекса.
48 |
49 | Swift решает эти проблемы с нуля. Во-первых, swift **не имеет скалярных типов**. Все типы в Swift могут свободно называться типами объектов, что означает, что они могут быть созданы и сообщения могут быть отправлены в созданные таким образом экземпляры.
50 | Это справедливо для всех типов значений и ссылок в swift. Во-вторых, опциональность в swift формализует различие между ситуациями, когда значение или ссылка должны быть доступны, и ситуациями, когда значение или ссылка могут присутствовать или отсутствовать на законных основаниях.
51 |
52 | Неопциональная переменная в swift всегда должна иметь значение или ссылку, в зависимости от обстоятельств, и не может использоваться без инициализации. Аналогично, и для функций, где возвращаемый тип не является неопциональным и не будет компилироваться, если функция не возвращает значение или ссылку, в зависимости от того, является ли возвращаемый тип значением или ссылочным типом.
53 | Это обеспечивает железную гарантию того, что если тип переменной, или возвращаемый тип функции не был объявлен как опциональный, то он всегда будет иметь значение или ссылку.
54 | С другой стороны, если тип переменной или возвращаемый тип функции объявлен как опциональный, то любой код, который использует такую переменную или функцию,
55 | должен использовать специальный синтаксис, встроенный в язык, не только для проверки того, действительно ли существует значение или ссылка,
56 | но и для безопасного использования, если это так.
57 |
58 | # Как реализуется опциональность в Swift
59 |
60 | ```
61 | public enum Optional {
62 | case none
63 | case some(Wrapped)
64 | }
65 | ```
66 | Перечисление имеет **два случая**, чтобы представлять `отсутствие` и `присутствие` для экземпляра, который реализует опциональность.
67 | При отсутствии значения - опциональным значением является `.none`.
68 | Если опциональный экземпляр содержит значение, то это значение в `Optional` перечеслении является значением `.some`.
69 | Поскольку параметр `Optional` является универсальным типом (дженерик), то параметр `Wrapped` используется для определения типа обёрнутого экземпляра
70 | во время создания экземпляра опционального типа.
71 |
72 | Это объясняет, почему опциональные элементы, например `String`, `Int`, имеют типы `Optional` и `Optional` соответственно.
73 | Хотя мы могли бы объявить опционально с помощью этого синтаксиса, но гораздо удобнее и общепринято использовать `String?` и `Int?` - краткие имена типов.
74 | Литерал **nil** служит удобным кратким текстом для кейса `.none`.
75 |
76 | Опциональный параметр может быть инициализирован нулевым литералом, так как тип **Optional** соответствует протоколу `ExpressibleStartNilLiteral`, который имеет следующее требование:
77 | ```
78 | protocol ExpressibleByNilLiteral {
79 | init(nilLiteral: ())
80 | }
81 | ```
82 |
83 | `nilLiteral` - пустой кортеж, что означает отсутствие значения. При вызове этого инициализатора создается новый экземпляр `Optional` со значением `.none`.
84 | Ни один другой тип Swift не соответствует этому протоколу, и использование этого протокола с другими типами не рекомендуется во избежание путаницы с `Optional`.
85 | Этот инициализатор не должен вызываться напрямую, он автоматически вызывается компилятором, когда опциональный параметр инициализируется с помощью литерала `nil` (или создается опциональный экземпляр, как пример var example: Int?).
86 |
87 | # Когда использовать опциональность в Swift
88 | Формализуя концепцию опциональности, swift может помочь нам создать улучшенные модели и написать код, который может явно относиться к случаям,
89 | когда определенные отношения могут присутствовать или отсутствовать для конкретного экземпляра, либо изначально, либо в определенный момент времени.
90 |
91 | Мы часто встречаем ситуации, когда определенный результат не может быть гарантирован. Функции могут быть не в состоянии вернуть значение во всех случаях, создание новых экземпляров определенных типов может завершиться неудачей при определенных условиях, не все операции для соответствия типу **(as! или as?)** могут быть успешными и т.д.
92 | Варианты обеспечивают простой и элегантный способ решения таких ситуаций, когда причину неудачи можно определить из контекста.
93 | #
94 | 1. Свойства, которые не гарантируют содержание значения
95 | Часто встречаются свойства, которые могут иметь значение для некоторых экземпляров типа, но не имть для других.
96 | Аналогично, могут существовать свойства, которые могут иметь значение в определенные моменты времени, но не в другие.
97 | Опциональные варианты являются естественным выбором для официального выражения таких ограничений. Для иллюстрации давайте определим протокол для моделирования домашних животных и класс для моделирования людей, которые могут владеть этими домашними животными.
98 | ```
99 | protocol Pet {
100 | var name: String? { get set }
101 | func makeSound()
102 | }
103 |
104 | class Person {
105 | var name: String
106 | var pet: Pet?
107 |
108 | init(named name: String) {
109 | self.name = name
110 | }
111 | }
112 | ```
113 | Протокол **Pet** объявляет метод, который будет реализовывать звук, который издает каждый питомец.
114 | Он также объявляет свойство для имени домашнего животного. Класс **Person** имеет два свойства: одно для имени человека, а другое для домашнего животного, которым он может обладать.
115 |
116 | Поскольку не каждый человек может иметь домашнее животное, и тот человек, у которого может - быть один питомец, означается что поле `pet` для класса `Person` будет опциональным.
117 | Так же, имя человека **name** в классе **Person** является обязательным, но кличка питомца **name** в протоколе **Pet** является опциональным, отображая тем самым требование.
118 |
119 | #
120 | 2. Функция не гарантирует возврат значения
121 | Могут существовать функции, методы или замыкания с объявленными типами возврата, которые могут не вернуть значение.
122 | Обычно это означает, что операция не может быть выполнена по запросу.
123 |
124 | Например, протокол `Collectio`n в стандартной библиотеке определяет метод `firstIndex(of:)` доступный, когда элементы коллекции соответствуют Equatable,
125 | который возвращает первый индекс, где указанное значение появляется в коллекции. Однако возможно, что значение не отображается в коллекции и именно поэтому
126 | возвращаемый тип метода - это Int?. Если значение найдено, его целочисленный индекс возвращается в виде опционального значение, в противном случае возвращается значение `nil`.
127 |
128 | Чтобы продемонстрировать это, мы определяем простую и универсальную функцию, которая ищет первый индекс элемента в массиве,
129 | где элементы массива соответствуют `Equatable`, печатает первый индекс, если элемент найден, и печатает подходящее сообщение, если это не так.
130 | ```
131 | func printFirstIndex(of element: Element, in array: [Element]) {
132 | let maybeIndex = array.firstIndex(of: element)
133 | if let index = maybeIndex {
134 | print("Element \(element) was found at index \(index)")
135 | } else {
136 | print("Element \(element) not found")
137 | }
138 | }
139 | ```
140 | Вот как мы можем использовать вышеуказанную функцию с массивом целых чисел.
141 | ```
142 | let ints = [2, 3, 5]
143 | printFirstIndex(of: 3, in: ints) // Element 3 was found at index 1
144 | printFirstIndex(of: 7, in: ints) // Element 7 not found
145 | ```
146 | #
147 | 3. Инициализатор не гарантирует создание экземпляра
148 | Могли быть обстоятельства, при которых инициализатор типа может не быть в состоянии создать валидный случай. Это может быть связано с тем, что некоторые или все аргументы не являются допустимыми или не выполняются другие требуемые условия.
149 | Чтобы справиться с такими ситуациями, swift позволяет объявить **failable initializer**, который возвращает опциональный экземпляр. Если создание экземпляра завершается неуспешно, возвращается значение nil. **Failable initializer** объявляется путем указания вопросительного знака (?) перед открывающей скобкой списка параметров.
150 |
151 | Если смотреть на класс Person, определенный выше, свойство **name** для **Person** не является опциональным, так как каждый человек должен иметь имя. Но мы можем создать безымянного человека, передав пустую строку в качестве аргумента.
152 | ```
153 | let namelessPerson = Person(named: "")
154 | ```
155 | Чтобы этого не произошло, мы можем изменить класс Person
156 |
157 | ```
158 | class Person {
159 | var name: String
160 | var pet: Pet?
161 |
162 | init?(named name: String) {
163 | guard !name.isEmpty else { return nil }
164 |
165 | self.name = name
166 | }
167 | }
168 | ```
169 | Теперь невозможно инициализировать экземпляр **Person** с пустой строкой имени.
170 | ```
171 | let notAPerson = Person(named: "")
172 | print(type(of: notAPerson)) // Optional
173 | print(notAPerson == nil) // true
174 | ```
175 | #
176 | 4. Параметр функции не требуется в каждом случае
177 | Для каждого вызова функции могут не потребоваться все ее параметры.
178 | Дополнительные параметры обеспечивают элегантный способ обработки таких случаев.
179 | При использовании такой функции для каждого опционального параметра, вызывающий объект должен либо передать значение типа, обернутого как опциональный,
180 | либо использовать **nil**. В качестве альтернативы опциональному параметру может быть присвоено **nil** значение по умолчанию,
181 | что дает вызывающему абоненту возможность вызова функции с таким количеством аргументов, которое может иметь смысл для конкретного вызова,
182 | это также относится к инициализаторам.
183 |
184 | ```
185 | class Cat: Pet {
186 | var name: String?
187 |
188 | init(named name: String? = nil) {
189 | self.name = name
190 | }
191 |
192 | func makeSound() {
193 | print("Meow")
194 | }
195 | }
196 | ```
197 | Мы определили инициализатор с параметром типа **String?**, со значением по умолчанию **nil**.
198 | Этот инициализатор можно использовать для создания экземпляров Cat с именем или без него.
199 | ```
200 | let unnamedCat = Cat()
201 | let namedCat = Cat(named: "Bella")
202 | ```
203 | #
204 | 5. Приведение типов
205 | Когда мы приводим от подтипа к супертипу, то приведение к типу гарантированно будет успешным.
206 | То же самое справедливо при приведении экземпляра конкретного типа к типу протокола, которому соответствует конкретный тип.
207 | Для приведения к типу, который гарантированно будут успешными, мы используем `as` в качестве оператора.
208 | ```
209 | var cat = Cat()
210 | var somePet: Pet = cat as Pet
211 | ```
212 | Однако, когда мы приводим от супертипа к подтипу или от типа протокола к конкретному типу, мы не можем использовать оператор `as`, так как такие приведения к типу не гарантировано будут успешны. Мы демонстрируем это, используя переменные **somePet** и **cat**.
213 | ```
214 | somePet = Pet()
215 | cat = somePet as Cat
216 | // Error: 'Pet' is not convertible to 'Cat'
217 | ```
218 | Хотя экземпляр, который мы пытаемся привести, относится к классу **Cat**, мы получаем ошибку компилятора. Это связано с тем, что переменная **somePet** содержит тип **Pet**, и компилятор не может быть уверен, что тип исполняемого экземпляра, который мы пытаемся привести, всегда будет таким, для которого приведение будет успешным.
219 |
220 | Для приведения к типу, который не гарантированно будут успешным, мы можем установить оператор `as` с вопросительным знаком(?). В случае успеха приведения, мы получаем результат приведения к типу, завернутый как опциональный.
221 | ```
222 | var maybeCat = somePet as? Cat
223 | print(type(of: maybeCat)) // Optional
224 | print(maybeCat == nil) // false
225 | ```
226 | Если приведение типа терпит неудачу, мы получаем `nil`. Чтобы продемонстрировать это, мы определяем класс `Dog`, который также соответствует `Pet`.
227 | ```
228 | class Dog: Pet {
229 | var name: String?
230 |
231 | init(named name: String? = nil) {
232 | self.name = name
233 | }
234 |
235 | func makeSound() {
236 | print("Woof")
237 | }
238 | }
239 |
240 | somePet = Dog()
241 | maybeCat = somePet as? Cat
242 | print(maybeCat == nil) // true
243 | ```
244 | #
245 | 6. Упрощенная обработка ошибок
246 | Опциональность обеспечивает удобное средство для обработки простых ошибок, где причину ошибки можно легко определить из контекста.
247 | Обычный способ обработки ошибок в Swift - поместить любой оператор или вызов функции, которые могут вызвать ошибку в **выражение try**.
248 | Если возникает ошибка, она должна быть обработана каким-либо образом, либо путем ее захвата оператором **do-catch**, либо путем ее распространения,
249 | помечая функцию как **throwing**.
250 |
251 | Однако могут возникать ситуации, когда для конкретного случая требуется только знать, была ли вызвана ошибка, а не знать какая имеено это ошибка.
252 | Это может пригодиться при использовании фреймворков и сторонних библиотек, где нас могут не интересовать детали некоторых ошибок, а только случаи сбоя.
253 |
254 | ```
255 | enum TestError: Error {
256 | case someError
257 | }
258 |
259 | func possibleThrower(shouldThrow: Bool) throws -> String {
260 | if shouldThrow {
261 | throw TestError.someError
262 | }
263 | return "My string"
264 | }
265 | ```
266 | Мы можем использовать вышеуказанную функцию, чтобы увидеть результат `try?` выражения, когда функция `throwing` фактически порождает ошибку, так и когда она этого не делает.
267 | ```
268 | var stringResult = try? possibleThrower(shouldThrow: false)
269 | print(type(of: stringResult)) // Optional
270 | print(stringResult == nil) // false
271 |
272 | stringResult = try? possibleThrower(shouldThrow: true)
273 | print(stringResult == nil) // true
274 | ```
275 | Когда функция `try?` выражения не вызывает ошибки, мы получаем опциональную упаковку возвращаемого значения функции. Когда возникает ошибка, мы получаем `nil` для обозначения провала.
276 | Это относится к функциям с возвращаемым типом. Но как насчет функций без возвращаемого типа? Интересно, что в Swift нет такой вещи,
277 | как функция без возвращаемого типа. Функции без возвращаемого типа, неявно, имеют возвращаемый тип `Void` и специальное возвращаемое значение `()`,
278 | которое является пустым кортежем, тем самым опциональность может быть использована для `Void` тоже.
279 |
280 | ```
281 | func anotherPossibleThrower(shouldThrow: Bool) throws {
282 | if shouldThrow {
283 | throw TestError.someError
284 | }
285 | }
286 |
287 | var voidResult: Void? = try? anotherPossibleThrower(shouldThrow: false)
288 | print(voidResult == nil) // false
289 |
290 | voidResult = try? anotherPossibleThrower(shouldThrow: true)
291 | print(voidResult == nil) // true
292 | ```
293 | Функция **anotherPossibleThrower** имеет неявный возвращаемый тип **Void**, который становится Void? когда мы используем `try?` выражение.
294 | Мы можем зафиксировать неявное возвращаемое значение, чтобы проверить, равно ли оно `nil`, означающее, что произошла ошибка.
295 | Обратите внимание, что мы должны явным образом объявить тип переменной **voidResult**, чтобы избежать получения предупреждения от компилятора за попытку
296 | захвата возвращаемого значения, которое мы явно не объявили.
297 |
--------------------------------------------------------------------------------