1726 lines
54 KiB
Markdown
1726 lines
54 KiB
Markdown
# Swift: Полный справочник для разработчика с опытом Java/Kotlin
|
||
|
||
> **Целевая аудитория:** разработчики, знакомые с Java/Kotlin/Python, планирующие разрабатывать приложения для macOS, iOS и watchOS.
|
||
> **Версия:** Swift 6.x (актуально на 2026 год)
|
||
|
||
---
|
||
|
||
## Содержание
|
||
|
||
1. [Общее введение](#общее-введение)
|
||
2. [Синтаксические основы](#синтаксические-основы)
|
||
3. [Типы данных и коллекции](#типы-данных-и-коллекции)
|
||
4. [Управляющие конструкции](#управляющие-конструкции)
|
||
5. [Функции и замыкания](#функции-и-замыкания)
|
||
6. [Optionals — нулевая безопасность](#optionals--нулевая-безопасность)
|
||
7. [Перечисления (Enum)](#перечисления-enum)
|
||
8. [Структуры и классы](#структуры-и-классы)
|
||
9. [Протоколы и расширения](#протоколы-и-расширения)
|
||
10. [Обобщённые типы (Generics)](#обобщённые-типы-generics)
|
||
11. [Управление памятью — ARC](#управление-памятью--arc)
|
||
12. [Обработка ошибок](#обработка-ошибок)
|
||
13. [Конкурентность — async/await и Actors](#конкурентность--asyncawait-и-actors)
|
||
14. [Свойства: computed, observers, wrappers](#свойства-computed-observers-wrappers)
|
||
15. [Паттерн-матчинг](#паттерн-матчинг)
|
||
16. [Макросы (Swift 6)](#макросы-swift-6)
|
||
17. [SwiftUI, UIKit, AppKit, WatchKit](#swiftui-uikit-appkit-watchkit)
|
||
18. [Экосистема и инструменты](#экосистема-и-инструменты)
|
||
19. [Таблица сравнений Swift vs Kotlin](#таблица-сравнений-swift-vs-kotlin)
|
||
|
||
---
|
||
|
||
## Общее введение
|
||
|
||
Swift — статически типизированный, компилируемый язык общего назначения от Apple (2014), разработанный как замена Objective-C. Он открыт с 2015 года и доступен на [swift.org](https://swift.org). Swift работает поверх LLVM и компилируется в нативный машинный код.
|
||
|
||
### Ключевые особенности
|
||
|
||
- **Статическая типизация + вывод типов** — компилятор сам выводит типы из контекста.
|
||
- **Value Semantics** — `struct` и `enum` копируются при передаче, что снижает количество багов с общим состоянием.
|
||
- **ARC (Automatic Reference Counting)** — нет GC, нет пауз (в отличие от JVM).
|
||
- **Нулевая безопасность** — `nil` возможен только в `Optional<T>` (аналог `T?` в Kotlin, но реализован иначе).
|
||
- **Protocol-Oriented Programming** — протоколы мощнее интерфейсов Kotlin: поддерживают дефолтные реализации через `extension`, associated types, `where`-ограничения.
|
||
- **Structured Concurrency** — `async/await` и `actor` встроены в язык (Swift 5.5+, расширено в Swift 6).
|
||
- **Swift 6 (2024)** — строгая проверка конкурентности на уровне компилятора, Typed Throws, Embedded Swift, расширенная интероп с C++.
|
||
|
||
---
|
||
|
||
## Синтаксические основы
|
||
|
||
### Переменные и константы
|
||
|
||
```swift
|
||
let name: String = "Alice" // константа (аналог val в Kotlin)
|
||
var age = 30 // переменная, тип Int выведен автоматически
|
||
var score: Double = 9.5
|
||
|
||
// Несколько присваиваний
|
||
var x = 0, y = 0, z = 0
|
||
```
|
||
|
||
> **vs Kotlin:** `val`/`var` → `let`/`var`. Отличие: в Swift `let` на struct запрещает изменение любых свойств, а `let` на class — только перепривязывание ссылки (как в Kotlin `val`).
|
||
|
||
### Типы данных
|
||
|
||
| Swift | Kotlin/Java | Описание |
|
||
|-------------|-----------------|---------------------------|
|
||
| `Int` | `Int`/`Long` | 64-bit на 64-bit платформах |
|
||
| `Double` | `Double` | 64-bit floating point |
|
||
| `Float` | `Float` | 32-bit floating point |
|
||
| `Bool` | `Boolean` | `true` / `false` |
|
||
| `String` | `String` | Unicode, value type |
|
||
| `Character` | `Char` | Один символ Unicode |
|
||
| `Void` | `Unit` | Отсутствие возвращаемого значения |
|
||
| `Never` | `Nothing` | Функция никогда не вернётся |
|
||
| `Any` | `Any` | Любой тип |
|
||
| `AnyObject` | нет прямого аналога | Любой ссылочный тип |
|
||
|
||
### Строки
|
||
|
||
```swift
|
||
let greeting = "Hello"
|
||
let name = "World"
|
||
|
||
// Интерполяция строк — \() вместо ${} в Kotlin
|
||
let msg = "Hello, \(name)! 2 + 2 = \(2 + 2)"
|
||
|
||
// Многострочная строка
|
||
let multiline = """
|
||
Первая строка
|
||
Вторая строка
|
||
"""
|
||
|
||
// String — value type (struct), не Reference
|
||
let upper = name.uppercased()
|
||
let count = name.count
|
||
let hasPrefix = name.hasPrefix("Wo")
|
||
```
|
||
|
||
### Комментарии
|
||
|
||
```swift
|
||
// Однострочный комментарий
|
||
|
||
/*
|
||
Многострочный
|
||
комментарий
|
||
*/
|
||
|
||
/// Документация (аналог /** */ в Kotlin/Java)
|
||
/// - Parameter x: описание параметра
|
||
/// - Returns: описание результата
|
||
func square(_ x: Int) -> Int { x * x }
|
||
```
|
||
|
||
### Точка с запятой
|
||
|
||
Точка с запятой не нужна в конце строки (опциональна). Обязательна только для нескольких операторов на одной строке:
|
||
|
||
```swift
|
||
let a = 1; let b = 2 // допустимо, но не рекомендуется
|
||
```
|
||
|
||
---
|
||
|
||
## Типы данных и коллекции
|
||
|
||
### Array
|
||
|
||
```swift
|
||
// Создание
|
||
var numbers: [Int] = [1, 2, 3]
|
||
var empty = [String]() // пустой массив
|
||
var repeated = Array(repeating: 0, count: 5) // [0,0,0,0,0]
|
||
|
||
// Операции
|
||
numbers.append(4)
|
||
numbers.insert(0, at: 0)
|
||
numbers.remove(at: 2)
|
||
let first = numbers.first // Optional<Int>
|
||
let count = numbers.count
|
||
|
||
// Итерация
|
||
for n in numbers { print(n) }
|
||
for (index, value) in numbers.enumerated() {
|
||
print("\(index): \(value)")
|
||
}
|
||
|
||
// Функции высшего порядка (аналог Kotlin)
|
||
let doubled = numbers.map { $0 * 2 }
|
||
let evens = numbers.filter { $0 % 2 == 0 }
|
||
let sum = numbers.reduce(0, +)
|
||
let sorted = numbers.sorted()
|
||
```
|
||
|
||
### Dictionary
|
||
|
||
```swift
|
||
var dict: [String: Int] = ["a": 1, "b": 2]
|
||
dict["c"] = 3
|
||
dict["a"] = nil // удаление ключа
|
||
|
||
let val = dict["a"] // Optional<Int> — ключ может отсутствовать
|
||
let safe = dict["z"] ?? 0 // значение по умолчанию через nil-coalescing
|
||
|
||
// Итерация
|
||
for (key, value) in dict {
|
||
print("\(key) → \(value)")
|
||
}
|
||
```
|
||
|
||
### Set
|
||
|
||
```swift
|
||
var fruits: Set<String> = ["apple", "banana", "cherry"]
|
||
fruits.insert("mango")
|
||
fruits.remove("apple")
|
||
let contains = fruits.contains("banana") // true
|
||
|
||
// Теоретико-множественные операции
|
||
let a: Set = [1, 2, 3]
|
||
let b: Set = [2, 3, 4]
|
||
let union = a.union(b) // {1,2,3,4}
|
||
let intersection = a.intersection(b) // {2,3}
|
||
let difference = a.subtracting(b) // {1}
|
||
```
|
||
|
||
### Tuple
|
||
|
||
Кортежи — лёгкий способ вернуть несколько значений. **В Kotlin нет прямого аналога** (только `Pair`/`Triple`).
|
||
|
||
```swift
|
||
let point = (3.0, 4.0)
|
||
let (x, y) = point // destructuring
|
||
|
||
// Именованные поля
|
||
let http = (statusCode: 200, description: "OK")
|
||
print(http.statusCode)
|
||
print(http.description)
|
||
|
||
// Возврат нескольких значений из функции
|
||
func minMax(_ arr: [Int]) -> (min: Int, max: Int) {
|
||
return (arr.min()!, arr.max()!)
|
||
}
|
||
let result = minMax([1, 5, 3])
|
||
print(result.min, result.max)
|
||
```
|
||
|
||
### Range
|
||
|
||
```swift
|
||
let closed = 1...5 // 1, 2, 3, 4, 5 (аналог 1..5 в Kotlin)
|
||
let half = 1..<5 // 1, 2, 3, 4 (аналог 1 until 5 в Kotlin)
|
||
let from = 1... // от 1 до бесконечности (PartialRangeFrom)
|
||
let upTo = ..<5 // до 4 включительно
|
||
|
||
for i in 1...5 { print(i) }
|
||
let slice = [10, 20, 30, 40, 50][1...3] // [20, 30, 40]
|
||
```
|
||
|
||
---
|
||
|
||
## Управляющие конструкции
|
||
|
||
### if / else
|
||
|
||
```swift
|
||
let temp = 25
|
||
if temp > 30 {
|
||
print("Жарко")
|
||
} else if temp > 20 {
|
||
print("Тепло")
|
||
} else {
|
||
print("Прохладно")
|
||
}
|
||
|
||
// В Swift if НЕ является выражением (в отличие от Kotlin)
|
||
// Используй тернарный оператор:
|
||
let label = temp > 30 ? "Жарко" : "Нормально"
|
||
```
|
||
|
||
> **Swift 5.9+:** Появился `if`/`switch` как выражение:
|
||
> ```swift
|
||
> let label = if temp > 30 { "Жарко" } else { "Нормально" }
|
||
> ```
|
||
|
||
### switch
|
||
|
||
В Swift `switch` гораздо мощнее Java/Kotlin — поддерживает паттерн-матчинг, ranges, tuples, where-условия. **Не нужен `break` — нет fall-through по умолчанию.**
|
||
|
||
```swift
|
||
let score = 85
|
||
|
||
switch score {
|
||
case 90...100:
|
||
print("Отлично")
|
||
case 75..<90:
|
||
print("Хорошо")
|
||
case 60..<75:
|
||
print("Удовлетворительно")
|
||
default:
|
||
print("Неудовлетворительно")
|
||
}
|
||
|
||
// Паттерн-матчинг с where
|
||
let point = (1, -1)
|
||
switch point {
|
||
case let (x, y) where x == y:
|
||
print("На диагонали")
|
||
case let (x, y) where x == -y:
|
||
print("На антидиагонали")
|
||
case (let x, let y):
|
||
print("(\(x), \(y))")
|
||
}
|
||
```
|
||
|
||
### guard
|
||
|
||
`guard` — уникальная для Swift конструкция раннего выхода. Аналог `require()` в Kotlin, но встроен в язык.
|
||
|
||
```swift
|
||
func processAge(_ age: Int?) {
|
||
guard let age = age else {
|
||
print("Возраст не указан")
|
||
return // обязательный ранний выход
|
||
}
|
||
// age доступен здесь как Non-Optional Int
|
||
guard age >= 0 && age < 150 else {
|
||
print("Некорректный возраст")
|
||
return
|
||
}
|
||
print("Возраст: \(age)")
|
||
}
|
||
```
|
||
|
||
> `guard let` предпочтительнее `if let`, когда нужно избежать вложенности (Pyramid of Doom).
|
||
|
||
### for-in, while, repeat-while
|
||
|
||
```swift
|
||
// for-in
|
||
for i in 0..<5 { print(i) }
|
||
for char in "Hello" { print(char) }
|
||
|
||
// stride — аналог Kotlin step
|
||
for i in stride(from: 0, to: 10, by: 2) { print(i) } // 0,2,4,6,8
|
||
for i in stride(from: 10, through: 0, by: -2) { print(i) }
|
||
|
||
// while
|
||
var n = 1
|
||
while n < 100 { n *= 2 }
|
||
|
||
// repeat-while — аналог do-while в Java/Kotlin
|
||
repeat {
|
||
n /= 2
|
||
} while n > 1
|
||
```
|
||
|
||
---
|
||
|
||
## Функции и замыкания
|
||
|
||
### Объявление функций
|
||
|
||
```swift
|
||
// Базовый синтаксис: func имя(метка параметра: тип) -> ТипВозврата
|
||
func greet(person name: String, from city: String) -> String {
|
||
return "Привет, \(name) из \(city)!"
|
||
}
|
||
|
||
// Вызов — именованные аргументы обязательны (отличие от Kotlin)
|
||
greet(person: "Иван", from: "Москва")
|
||
|
||
// _ — убирает внешнюю метку параметра
|
||
func add(_ a: Int, _ b: Int) -> Int { a + b }
|
||
add(3, 5) // без меток
|
||
|
||
// Параметры по умолчанию
|
||
func connect(host: String, port: Int = 8080) { }
|
||
connect(host: "localhost")
|
||
|
||
// Variadic параметры
|
||
func sum(_ numbers: Int...) -> Int {
|
||
numbers.reduce(0, +)
|
||
}
|
||
sum(1, 2, 3, 4, 5)
|
||
|
||
// inout — передача по ссылке (аналог ref в C#)
|
||
func doubleInPlace(_ value: inout Int) {
|
||
value *= 2
|
||
}
|
||
var x = 5
|
||
doubleInPlace(&x) // x == 10
|
||
```
|
||
|
||
> **vs Kotlin:** В Swift функции объявляются через `func`, Kotlin через `fun`. Тип возврата: Swift использует `->`, Kotlin `:`.
|
||
> Значительное отличие — метки аргументов (argument labels): внешняя метка для читаемости API.
|
||
|
||
### Функции как типы первого класса
|
||
|
||
```swift
|
||
// Функция как тип
|
||
var operation: (Int, Int) -> Int = { $0 + $1 }
|
||
operation(3, 4) // 7
|
||
|
||
// Функция высшего порядка
|
||
func apply(_ op: (Int, Int) -> Int, to a: Int, and b: Int) -> Int {
|
||
op(a, b)
|
||
}
|
||
apply(+, to: 5, and: 3) // 8
|
||
apply(*, to: 5, and: 3) // 15
|
||
```
|
||
|
||
### Замыкания (Closures)
|
||
|
||
Замыкания — анонимные функции, захватывающие контекст. Аналог лямбд в Kotlin.
|
||
|
||
```swift
|
||
// Полный синтаксис
|
||
let multiply: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
|
||
return a * b
|
||
}
|
||
|
||
// Сокращённый синтаксис (trailing closure)
|
||
let numbers = [3, 1, 4, 1, 5, 9]
|
||
let sorted = numbers.sorted { $0 < $1 } // $0, $1 — shorthand arguments
|
||
let sorted2 = numbers.sorted(by: <) // ещё короче
|
||
|
||
// Trailing closure синтаксис — если последний аргумент — замыкание
|
||
numbers.forEach { print($0) }
|
||
|
||
// Несколько trailing closures (Swift 5.3+)
|
||
UIView.animate(withDuration: 0.3) {
|
||
// анимация
|
||
} completion: { finished in
|
||
// по завершению
|
||
}
|
||
```
|
||
|
||
### Захват значений (Capture List)
|
||
|
||
```swift
|
||
var counter = 0
|
||
let increment = { counter += 1 }
|
||
increment() // counter == 1
|
||
|
||
// Захват по значению через capture list
|
||
var value = 10
|
||
let closure = { [value] in // захватывает копию value на момент создания
|
||
print(value)
|
||
}
|
||
value = 20
|
||
closure() // выведет 10, не 20
|
||
|
||
// [weak self] — слабая ссылка, предотвращает retain cycle
|
||
class MyClass {
|
||
var name = "Test"
|
||
func doWork() {
|
||
DispatchQueue.global().async { [weak self] in
|
||
guard let self = self else { return }
|
||
print(self.name)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
> **Важно:** Без `[weak self]` в замыкании, захватывающем `self`, возникает retain cycle — `self` держит замыкание, замыкание держит `self`. Это классическая утечка памяти в Swift.
|
||
|
||
---
|
||
|
||
## Optionals — нулевая безопасность
|
||
|
||
Optional — это **обёртка над значением**, реализованная как `enum Optional<Wrapped> { case some(Wrapped); case none }`. Принципиальное отличие от Kotlin: Swift Optional — не просто аннотация типа, а реальный тип-обёртка.
|
||
|
||
### Объявление
|
||
|
||
```swift
|
||
var name: String? = "Alice" // Optional<String>
|
||
var age: Int? = nil // nil === .none
|
||
|
||
// Non-optional — никогда не nil
|
||
var definite: String = "Always here"
|
||
```
|
||
|
||
### Unwrapping
|
||
|
||
```swift
|
||
// 1. Forced Unwrapping (!) — опасно, может crash
|
||
let length = name!.count // crash если nil
|
||
|
||
// 2. Optional Binding — if let
|
||
if let unwrapped = name {
|
||
print("Имя: \(unwrapped)") // unwrapped — String, не String?
|
||
}
|
||
|
||
// Shorthand (Swift 5.7+): имя переменной совпадает
|
||
if let name {
|
||
print(name)
|
||
}
|
||
|
||
// 3. guard let — ранний выход + unwrap
|
||
func process(_ value: String?) {
|
||
guard let value else { return }
|
||
print(value) // value доступен как String дальше в функции
|
||
}
|
||
|
||
// 4. Nil-Coalescing Operator (??) — значение по умолчанию
|
||
let displayName = name ?? "Аноним"
|
||
|
||
// 5. Optional Chaining (?.) — безопасная цепочка
|
||
let count = name?.count // Optional<Int>
|
||
let upper = name?.uppercased() // Optional<String>
|
||
|
||
// Цепочка: если любое звено nil — весь результат nil
|
||
struct User {
|
||
var address: Address?
|
||
}
|
||
struct Address {
|
||
var city: String?
|
||
}
|
||
let user: User? = User(address: Address(city: "Москва"))
|
||
let city = user?.address?.city // Optional<String>
|
||
|
||
// 6. Implicitly Unwrapped Optional (T!) — используй с осторожностью
|
||
// Типично для IBOutlet и случаев, когда инициализация отложена
|
||
@IBOutlet weak var label: UILabel!
|
||
```
|
||
|
||
### Pattern Matching с Optional
|
||
|
||
```swift
|
||
switch name {
|
||
case .some(let value):
|
||
print("Есть значение: \(value)")
|
||
case .none:
|
||
print("Нет значения")
|
||
}
|
||
|
||
// Краткий вариант
|
||
switch name {
|
||
case let value?:
|
||
print(value)
|
||
case nil:
|
||
print("nil")
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Перечисления (Enum)
|
||
|
||
Swift `enum` — один из самых мощных инструментов языка, несравнимо богаче Java/Kotlin перечислений.
|
||
|
||
### Базовый enum
|
||
|
||
```swift
|
||
enum Direction {
|
||
case north, south, east, west
|
||
}
|
||
|
||
let dir = Direction.north
|
||
```
|
||
|
||
### Raw Values
|
||
|
||
```swift
|
||
enum Planet: Int {
|
||
case mercury = 1, venus, earth, mars // автоинкремент
|
||
}
|
||
let earth = Planet(rawValue: 3) // Optional<Planet>
|
||
print(Planet.venus.rawValue) // 2
|
||
|
||
enum HTTPMethod: String {
|
||
case get = "GET"
|
||
case post = "POST"
|
||
case put = "PUT"
|
||
case delete = "DELETE"
|
||
}
|
||
print(HTTPMethod.get.rawValue) // "GET"
|
||
```
|
||
|
||
### Associated Values — мощнейшая особенность Swift
|
||
|
||
```swift
|
||
enum NetworkResult {
|
||
case success(Data)
|
||
case failure(Error)
|
||
case loading(progress: Double)
|
||
}
|
||
|
||
// Работа с associated values
|
||
let result: NetworkResult = .success(someData)
|
||
switch result {
|
||
case .success(let data):
|
||
print("Данные: \(data)")
|
||
case .failure(let error):
|
||
print("Ошибка: \(error)")
|
||
case .loading(let progress):
|
||
print("Загрузка: \(progress * 100)%")
|
||
}
|
||
|
||
// if case — проверка одного конкретного кейса
|
||
if case .loading(let p) = result {
|
||
print("Загрузка: \(p)")
|
||
}
|
||
```
|
||
|
||
> **vs Kotlin:** Ближайший аналог `sealed class`. Swift enum с associated values компактнее и поддерживает exhaustive switch без `else`.
|
||
|
||
### Рекурсивный enum (indirect)
|
||
|
||
```swift
|
||
indirect enum BinaryTree<T> {
|
||
case leaf
|
||
case node(value: T, left: BinaryTree, right: BinaryTree)
|
||
}
|
||
```
|
||
|
||
### Методы и computed properties в enum
|
||
|
||
```swift
|
||
enum Suit: String {
|
||
case hearts, diamonds, clubs, spades
|
||
|
||
var isRed: Bool {
|
||
return self == .hearts || self == .diamonds
|
||
}
|
||
|
||
func display() {
|
||
print("Масть: \(rawValue), красная: \(isRed)")
|
||
}
|
||
}
|
||
Suit.hearts.display()
|
||
```
|
||
|
||
### CaseIterable
|
||
|
||
```swift
|
||
enum Season: CaseIterable {
|
||
case spring, summer, autumn, winter
|
||
}
|
||
Season.allCases.forEach { print($0) }
|
||
```
|
||
|
||
---
|
||
|
||
## Структуры и классы
|
||
|
||
### Фундаментальное различие: Value vs Reference
|
||
|
||
| Характеристика | `struct` (value type) | `class` (reference type) |
|
||
|---------------------|-----------------------|---------------------------|
|
||
| Хранение | Стек (обычно) | Куча |
|
||
| Передача | Копирование | Ссылка |
|
||
| Наследование | Нет | Да |
|
||
| `deinit` | Нет | Да |
|
||
| Управление памятью | - | ARC |
|
||
| Mutability | `let` запрещает всё | `let` запрещает перепривязывание |
|
||
| Аналог в Kotlin | `data class` (но ref) | `class` |
|
||
|
||
> **Рекомендация Apple:** По умолчанию используй `struct`. Переходи на `class`, когда нужно наследование, идентичность объекта или жизненный цикл.
|
||
|
||
### struct
|
||
|
||
```swift
|
||
struct Point {
|
||
var x: Double
|
||
var y: Double
|
||
|
||
// Memberwise initializer генерируется автоматически
|
||
|
||
// Computed property
|
||
var magnitude: Double {
|
||
(x * x + y * y).squareRoot()
|
||
}
|
||
|
||
// Mutating method — обязательно, т.к. struct — value type
|
||
mutating func translate(dx: Double, dy: Double) {
|
||
x += dx
|
||
y += dy
|
||
}
|
||
|
||
// Static method
|
||
static func origin() -> Point {
|
||
Point(x: 0, y: 0)
|
||
}
|
||
}
|
||
|
||
var p = Point(x: 3, y: 4)
|
||
print(p.magnitude) // 5.0
|
||
p.translate(dx: 1, dy: 0)
|
||
|
||
let immutable = Point(x: 1, y: 2)
|
||
// immutable.x = 5 // Ошибка: нельзя изменить let struct
|
||
```
|
||
|
||
### class
|
||
|
||
```swift
|
||
class Animal {
|
||
var name: String
|
||
private var energy: Int = 100
|
||
|
||
init(name: String) {
|
||
self.name = name
|
||
}
|
||
|
||
func eat() {
|
||
energy += 10
|
||
print("\(name) ест")
|
||
}
|
||
|
||
// Деструктор
|
||
deinit {
|
||
print("\(name) удалён из памяти")
|
||
}
|
||
}
|
||
|
||
class Dog: Animal {
|
||
var breed: String
|
||
|
||
init(name: String, breed: String) {
|
||
self.breed = breed
|
||
super.init(name: name) // super.init обязателен
|
||
}
|
||
|
||
override func eat() {
|
||
super.eat()
|
||
print("\(name) виляет хвостом!")
|
||
}
|
||
|
||
// final — запрещает переопределение
|
||
final func bark() { print("Гав!") }
|
||
}
|
||
```
|
||
|
||
### Инициализаторы в деталях
|
||
|
||
```swift
|
||
class Vehicle {
|
||
var make: String
|
||
var year: Int
|
||
|
||
// Designated initializer — основной
|
||
init(make: String, year: Int) {
|
||
self.make = make
|
||
self.year = year
|
||
}
|
||
|
||
// Convenience initializer — вторичный, вызывает designated
|
||
convenience init(make: String) {
|
||
self.init(make: make, year: 2024)
|
||
}
|
||
|
||
// Failable initializer — возвращает Optional
|
||
init?(rawYear: String) {
|
||
guard let year = Int(rawYear) else { return nil }
|
||
self.make = "Unknown"
|
||
self.year = year
|
||
}
|
||
}
|
||
```
|
||
|
||
### `actor` — для безопасной конкурентности (Swift 5.5+)
|
||
|
||
```swift
|
||
actor BankAccount {
|
||
private var balance: Double = 0
|
||
|
||
func deposit(_ amount: Double) {
|
||
balance += amount
|
||
}
|
||
|
||
func getBalance() -> Double {
|
||
balance
|
||
}
|
||
}
|
||
|
||
// Использование (требует await)
|
||
let account = BankAccount()
|
||
await account.deposit(100)
|
||
let bal = await account.getBalance()
|
||
```
|
||
|
||
`actor` — это `class` с автоматической сериализацией доступа. Обращение к методам актора из внешнего кода всегда `async`.
|
||
|
||
---
|
||
|
||
## Протоколы и расширения
|
||
|
||
### Protocol — аналог interface + trait в Kotlin
|
||
|
||
```swift
|
||
protocol Drawable {
|
||
var color: String { get } // read-only property
|
||
var area: Double { get set } // read-write property
|
||
func draw()
|
||
func resize(factor: Double) -> Self // Self requirement
|
||
|
||
// Инициализатор в протоколе
|
||
init(color: String)
|
||
}
|
||
|
||
// Conformance
|
||
struct Circle: Drawable {
|
||
var color: String
|
||
var radius: Double
|
||
var area: Double {
|
||
get { Double.pi * radius * radius }
|
||
set { radius = (newValue / Double.pi).squareRoot() }
|
||
}
|
||
|
||
init(color: String) { self.color = color; self.radius = 1.0 }
|
||
|
||
func draw() { print("Рисую \(color) круг") }
|
||
func resize(factor: Double) -> Circle {
|
||
Circle(color: color) // упрощённо
|
||
}
|
||
}
|
||
```
|
||
|
||
### Protocol Inheritance и Composition
|
||
|
||
```swift
|
||
protocol Printable {
|
||
func printDescription()
|
||
}
|
||
|
||
protocol Serializable {
|
||
func serialize() -> Data
|
||
}
|
||
|
||
// Протокол наследует другие протоколы
|
||
protocol Document: Printable, Serializable {
|
||
var title: String { get }
|
||
}
|
||
|
||
// Composition через & — без создания нового протокола
|
||
func process(_ item: Printable & Serializable) { }
|
||
```
|
||
|
||
### Расширения (Extensions)
|
||
|
||
```swift
|
||
// Добавление методов к существующим типам (даже стандартной библиотеки!)
|
||
extension String {
|
||
var isPalindrome: Bool {
|
||
self == String(self.reversed())
|
||
}
|
||
|
||
func trimmed() -> String {
|
||
trimmingCharacters(in: .whitespacesAndNewlines)
|
||
}
|
||
}
|
||
|
||
"level".isPalindrome // true
|
||
" hello ".trimmed()
|
||
|
||
// Ретроактивный conformance — добавляем протокол к чужому типу
|
||
extension Int: CustomStringConvertible {
|
||
public var description: String { "Число \(self)" }
|
||
}
|
||
```
|
||
|
||
### Protocol Extensions с дефолтными реализациями
|
||
|
||
```swift
|
||
protocol Greetable {
|
||
var name: String { get }
|
||
func greet() -> String
|
||
}
|
||
|
||
// Дефолтная реализация через extension
|
||
extension Greetable {
|
||
func greet() -> String {
|
||
return "Привет, \(name)!"
|
||
}
|
||
}
|
||
|
||
struct Person: Greetable {
|
||
var name: String
|
||
// greet() уже реализован через extension
|
||
}
|
||
|
||
// Условный extension — расширение с ограничением
|
||
extension Array where Element: Comparable {
|
||
func sortedUnique() -> [Element] {
|
||
Array(Set(self)).sorted()
|
||
}
|
||
}
|
||
[3, 1, 2, 1, 3].sortedUnique() // [1, 2, 3]
|
||
```
|
||
|
||
### Existential Types (any) vs Generic Constraints (some)
|
||
|
||
Swift 5.7 ввёл важные ключевые слова:
|
||
|
||
```swift
|
||
// any — existential type (упаковка в коробку, dynamic dispatch)
|
||
// Используй, когда тип может быть разным в runtime
|
||
var shapes: [any Drawable] = []
|
||
|
||
// some — opaque type (конкретный тип скрыт, static dispatch)
|
||
// Используй в возвращаемых типах когда тип один конкретный
|
||
func makeShape() -> some Drawable {
|
||
Circle(color: "red")
|
||
}
|
||
|
||
// SwiftUI активно использует some View
|
||
struct ContentView: View {
|
||
var body: some View { // конкретный тип известен компилятору
|
||
Text("Hello")
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Обобщённые типы (Generics)
|
||
|
||
### Generic Functions
|
||
|
||
```swift
|
||
func swap<T>(_ a: inout T, _ b: inout T) {
|
||
let temp = a
|
||
a = b
|
||
b = temp
|
||
}
|
||
|
||
var x = 5, y = 10
|
||
swap(&x, &y) // x=10, y=5
|
||
```
|
||
|
||
### Generic Types
|
||
|
||
```swift
|
||
struct Stack<Element> {
|
||
private var items: [Element] = []
|
||
|
||
mutating func push(_ item: Element) {
|
||
items.append(item)
|
||
}
|
||
|
||
mutating func pop() -> Element? {
|
||
items.popLast()
|
||
}
|
||
|
||
var top: Element? { items.last }
|
||
}
|
||
|
||
var stack = Stack<Int>()
|
||
stack.push(1)
|
||
stack.push(2)
|
||
print(stack.pop()) // 2
|
||
```
|
||
|
||
### Type Constraints
|
||
|
||
```swift
|
||
// T должен соответствовать протоколу Comparable
|
||
func findMax<T: Comparable>(_ array: [T]) -> T? {
|
||
array.max()
|
||
}
|
||
|
||
// where clause — сложные ограничения
|
||
func merge<T, U>(_ dict1: [T: U], _ dict2: [T: U]) -> [T: U]
|
||
where T: Hashable, U: Equatable {
|
||
dict1.merging(dict2) { $1 }
|
||
}
|
||
```
|
||
|
||
### Associated Types в протоколах
|
||
|
||
```swift
|
||
protocol Container {
|
||
associatedtype Item // тип-заполнитель
|
||
var count: Int { get }
|
||
mutating func append(_ item: Item)
|
||
subscript(i: Int) -> Item { get }
|
||
}
|
||
|
||
struct IntStack: Container {
|
||
typealias Item = Int // можно опустить — компилятор выведет сам
|
||
private var items: [Int] = []
|
||
var count: Int { items.count }
|
||
mutating func append(_ item: Int) { items.append(item) }
|
||
subscript(i: Int) -> Int { items[i] }
|
||
}
|
||
```
|
||
|
||
### Primary Associated Types (Swift 5.7+)
|
||
|
||
```swift
|
||
// Указываем primary associated type в угловых скобках
|
||
protocol Collection<Element> {
|
||
associatedtype Element
|
||
}
|
||
|
||
// Теперь можно использовать как параметризованный existential
|
||
var coll: any Collection<Int> // вместо any Collection
|
||
```
|
||
|
||
---
|
||
|
||
## Управление памятью — ARC
|
||
|
||
Swift использует **Automatic Reference Counting (ARC)** — нет GC, нет пауз. Память освобождается детерминировано.
|
||
|
||
### Принцип работы
|
||
|
||
1. Создание объекта → счётчик ссылок = 1
|
||
2. Каждое присваивание или передача в функцию → счётчик ++
|
||
3. Выход из scope → счётчик --
|
||
4. Счётчик = 0 → вызывается `deinit` → освобождение памяти
|
||
|
||
### Strong, Weak, Unowned
|
||
|
||
```swift
|
||
class Person {
|
||
var name: String
|
||
var apartment: Apartment?
|
||
init(name: String) { self.name = name }
|
||
deinit { print("\(name) deinitialized") }
|
||
}
|
||
|
||
class Apartment {
|
||
var unit: String
|
||
// weak — опциональная слабая ссылка, не удерживает объект
|
||
// обнуляется автоматически при удалении объекта
|
||
weak var tenant: Person?
|
||
init(unit: String) { self.unit = unit }
|
||
}
|
||
|
||
var john: Person? = Person(name: "John")
|
||
var apt: Apartment? = Apartment(unit: "4B")
|
||
john?.apartment = apt
|
||
apt?.tenant = john // weak — нет retain cycle
|
||
|
||
john = nil // Person deinitialized — нет утечки
|
||
```
|
||
|
||
```swift
|
||
// unowned — не удерживает, но НЕ обнуляется (нет Optional)
|
||
// Используй, когда уверен, что объект живёт не меньше ссылающегося
|
||
class CreditCard {
|
||
let number: String
|
||
unowned let customer: Customer // customer всегда живёт дольше карты
|
||
init(number: String, customer: Customer) {
|
||
self.number = number
|
||
self.customer = customer
|
||
}
|
||
}
|
||
```
|
||
|
||
### Retain Cycle в замыканиях
|
||
|
||
```swift
|
||
class Timer {
|
||
var handler: (() -> Void)?
|
||
var count = 0
|
||
|
||
func start() {
|
||
// Retain cycle: self → handler (замыкание) → self
|
||
// handler = { self.count += 1 } // ❌ утечка!
|
||
|
||
// Решение — capture list с [weak self]
|
||
handler = { [weak self] in
|
||
guard let self = self else { return }
|
||
self.count += 1
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Обработка ошибок
|
||
|
||
### throws / try / catch
|
||
|
||
```swift
|
||
// 1. Определяем тип ошибки
|
||
enum ValidationError: Error {
|
||
case tooShort(minimumLength: Int)
|
||
case invalidCharacters([Character])
|
||
case alreadyTaken
|
||
}
|
||
|
||
// 2. Функция, которая может бросить ошибку
|
||
func validateUsername(_ username: String) throws -> String {
|
||
guard username.count >= 3 else {
|
||
throw ValidationError.tooShort(minimumLength: 3)
|
||
}
|
||
let invalid = username.filter { !$0.isLetter && !$0.isNumber }
|
||
guard invalid.isEmpty else {
|
||
throw ValidationError.invalidCharacters(Array(invalid))
|
||
}
|
||
return username.lowercased()
|
||
}
|
||
|
||
// 3. Обработка ошибок
|
||
do {
|
||
let name = try validateUsername("ab")
|
||
print("Успех: \(name)")
|
||
} catch ValidationError.tooShort(let min) {
|
||
print("Слишком короткий: минимум \(min) символов")
|
||
} catch ValidationError.invalidCharacters(let chars) {
|
||
print("Недопустимые символы: \(chars)")
|
||
} catch {
|
||
print("Неизвестная ошибка: \(error)")
|
||
}
|
||
|
||
// try? — преобразует в Optional (нет ошибки → nil)
|
||
let name = try? validateUsername("alice") // Optional<String>
|
||
|
||
// try! — crash при ошибке (использовать только при полной уверенности)
|
||
let forced = try! validateUsername("validname")
|
||
```
|
||
|
||
### Typed Throws (Swift 6)
|
||
|
||
```swift
|
||
// До Swift 6: нельзя было указать конкретный тип ошибки
|
||
// Swift 6: типизированные throws
|
||
func parse(_ data: Data) throws(ValidationError) -> String {
|
||
throw ValidationError.alreadyTaken // только ValidationError, не любой Error
|
||
}
|
||
|
||
// В catch теперь можно без as-cast
|
||
do {
|
||
let result = try parse(someData)
|
||
} catch .alreadyTaken {
|
||
print("Уже занято")
|
||
} catch .tooShort(let min) {
|
||
print("Минимум \(min)")
|
||
}
|
||
```
|
||
|
||
### Result Type
|
||
|
||
```swift
|
||
// Result<Success, Failure: Error> — подходит для async и callback API
|
||
func fetchUser(id: Int) -> Result<User, NetworkError> {
|
||
guard id > 0 else {
|
||
return .failure(.invalidID)
|
||
}
|
||
return .success(User(id: id, name: "Bob"))
|
||
}
|
||
|
||
switch fetchUser(id: 42) {
|
||
case .success(let user):
|
||
print("Пользователь: \(user.name)")
|
||
case .failure(let error):
|
||
print("Ошибка: \(error)")
|
||
}
|
||
|
||
// map / flatMap для трансформации Result
|
||
let nameResult = fetchUser(id: 1).map { $0.name }
|
||
```
|
||
|
||
---
|
||
|
||
## Конкурентность — async/await и Actors
|
||
|
||
Swift 6 делает конкурентность безопасной на уровне компилятора — Data Race Safety стала compile-time ошибкой.
|
||
|
||
### async/await
|
||
|
||
```swift
|
||
// async функция — может приостановиться
|
||
func fetchWeather(city: String) async throws -> WeatherData {
|
||
let url = URL(string: "https://api.weather.com/\(city)")!
|
||
let (data, _) = try await URLSession.shared.data(from: url)
|
||
return try JSONDecoder().decode(WeatherData.self, from: data)
|
||
}
|
||
|
||
// Вызов из async контекста
|
||
func showWeather() async {
|
||
do {
|
||
let weather = try await fetchWeather(city: "Moscow")
|
||
print("Температура: \(weather.temperature)°C")
|
||
} catch {
|
||
print("Ошибка: \(error)")
|
||
}
|
||
}
|
||
|
||
// Точка входа для Task (аналог корутины в Kotlin)
|
||
Task {
|
||
await showWeather()
|
||
}
|
||
```
|
||
|
||
### Параллельное выполнение
|
||
|
||
```swift
|
||
// async let — запускает задачи параллельно
|
||
func loadDashboard() async throws -> Dashboard {
|
||
async let user = fetchUser(id: 1)
|
||
async let posts = fetchPosts(userId: 1)
|
||
async let notifications = fetchNotifications()
|
||
|
||
// Все три запроса выполняются одновременно
|
||
return Dashboard(
|
||
user: try await user,
|
||
posts: try await posts,
|
||
notifications: try await notifications
|
||
)
|
||
}
|
||
|
||
// TaskGroup — динамическое число параллельных задач
|
||
func fetchAllUsers(ids: [Int]) async throws -> [User] {
|
||
try await withThrowingTaskGroup(of: User.self) { group in
|
||
for id in ids {
|
||
group.addTask { try await fetchUser(id: id) }
|
||
}
|
||
var users: [User] = []
|
||
for try await user in group {
|
||
users.append(user)
|
||
}
|
||
return users
|
||
}
|
||
}
|
||
```
|
||
|
||
### Actors
|
||
|
||
```swift
|
||
actor Counter {
|
||
private var value = 0
|
||
|
||
func increment() { value += 1 }
|
||
func decrement() { value -= 1 }
|
||
func get() -> Int { value }
|
||
|
||
// nonisolated — метод не требует изоляции (read-only, Sendable data)
|
||
nonisolated func description() -> String {
|
||
"Counter actor"
|
||
}
|
||
}
|
||
|
||
let counter = Counter()
|
||
await counter.increment() // await обязателен извне
|
||
let v = await counter.get()
|
||
```
|
||
|
||
### @MainActor
|
||
|
||
```swift
|
||
// Гарантирует выполнение на главном потоке
|
||
@MainActor
|
||
class ViewModel: ObservableObject {
|
||
@Published var title = ""
|
||
|
||
func loadData() async {
|
||
let data = await fetchFromServer()
|
||
// Здесь мы уже на MainActor — безопасно обновляем UI
|
||
title = data.title
|
||
}
|
||
}
|
||
|
||
// MainActor.run — переключение на главный поток вручную
|
||
Task {
|
||
let result = await backgroundOperation()
|
||
await MainActor.run {
|
||
updateUI(with: result)
|
||
}
|
||
}
|
||
```
|
||
|
||
### Sendable
|
||
|
||
```swift
|
||
// Sendable — протокол безопасности для передачи между акторами
|
||
// struct автоматически Sendable (если все поля Sendable)
|
||
struct UserData: Sendable {
|
||
let id: Int
|
||
let name: String
|
||
}
|
||
|
||
// class требует явного @unchecked Sendable или thread-safe реализации
|
||
final class Cache: @unchecked Sendable {
|
||
private let lock = NSLock()
|
||
private var storage: [String: Any] = [:]
|
||
|
||
func set(_ value: Any, for key: String) {
|
||
lock.lock()
|
||
storage[key] = value
|
||
lock.unlock()
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Свойства: computed, observers, wrappers
|
||
|
||
### Computed Properties
|
||
|
||
```swift
|
||
struct Rectangle {
|
||
var width: Double
|
||
var height: Double
|
||
|
||
// Computed property — только get
|
||
var area: Double {
|
||
width * height
|
||
}
|
||
|
||
// get + set
|
||
var perimeter: Double {
|
||
get { 2 * (width + height) }
|
||
set { width = newValue / 2 - height } // newValue — специальное имя
|
||
}
|
||
|
||
// Lazy property — вычисляется при первом обращении
|
||
lazy var expensiveCalc: String = {
|
||
// Долгие вычисления
|
||
return "result"
|
||
}()
|
||
}
|
||
```
|
||
|
||
### Property Observers
|
||
|
||
```swift
|
||
class StepCounter {
|
||
var steps: Int = 0 {
|
||
willSet(newSteps) { // вызывается ДО изменения
|
||
print("Будет \(newSteps) шагов")
|
||
}
|
||
didSet { // вызывается ПОСЛЕ изменения
|
||
if steps > oldValue {
|
||
print("Добавлено \(steps - oldValue) шагов")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Property Wrappers
|
||
|
||
```swift
|
||
// Определение обёртки
|
||
@propertyWrapper
|
||
struct Clamped<Value: Comparable> {
|
||
private var value: Value
|
||
let range: ClosedRange<Value>
|
||
|
||
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
|
||
self.range = range
|
||
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
|
||
}
|
||
|
||
var wrappedValue: Value {
|
||
get { value }
|
||
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
|
||
}
|
||
|
||
// projectedValue ($) — дополнительное свойство
|
||
var projectedValue: ClosedRange<Value> { range }
|
||
}
|
||
|
||
struct Player {
|
||
@Clamped(0...100) var health: Int = 100
|
||
@Clamped(0...10) var level: Int = 1
|
||
}
|
||
|
||
var player = Player()
|
||
player.health = 150 // автоматически зажимается до 100
|
||
player.level = -1 // автоматически зажимается до 0
|
||
print(player.$health) // доступ к projectedValue: 0...100
|
||
```
|
||
|
||
> **SwiftUI Property Wrappers:** `@State`, `@Binding`, `@StateObject`, `@ObservedObject`, `@EnvironmentObject`, `@Published`, `@AppStorage`, `@FetchRequest` — это всё property wrappers, встроенные во фреймворк.
|
||
|
||
---
|
||
|
||
## Паттерн-матчинг
|
||
|
||
Swift обладает исключительно выразительным паттерн-матчингом.
|
||
|
||
```swift
|
||
// switch с where
|
||
let point = CGPoint(x: 3, y: -3)
|
||
switch (point.x, point.y) {
|
||
case (0, 0):
|
||
print("Начало координат")
|
||
case (let x, 0) where x > 0:
|
||
print("Положительная ось X")
|
||
case (0, let y) where y > 0:
|
||
print("Положительная ось Y")
|
||
case (let x, let y) where x == -y:
|
||
print("Антидиагональ")
|
||
default:
|
||
print("Произвольная точка")
|
||
}
|
||
|
||
// ~= оператор — кастомное сравнение
|
||
struct Range2D {
|
||
let xRange: ClosedRange<Double>
|
||
let yRange: ClosedRange<Double>
|
||
|
||
static func ~= (pattern: Range2D, value: CGPoint) -> Bool {
|
||
pattern.xRange.contains(value.x) && pattern.yRange.contains(value.y)
|
||
}
|
||
}
|
||
|
||
// for case — фильтрация при итерации
|
||
enum NetworkResult {
|
||
case success(Data)
|
||
case failure(Error)
|
||
}
|
||
let results: [NetworkResult] = [.success(data1), .failure(err), .success(data2)]
|
||
for case .success(let data) in results {
|
||
process(data) // только успешные результаты
|
||
}
|
||
|
||
// Вложенные паттерны
|
||
enum Shape {
|
||
case circle(radius: Double)
|
||
case rectangle(width: Double, height: Double)
|
||
}
|
||
|
||
let shapes: [Shape] = [.circle(radius: 5), .rectangle(width: 3, height: 4)]
|
||
for shape in shapes {
|
||
switch shape {
|
||
case .circle(let r) where r > 3:
|
||
print("Большой круг с радиусом \(r)")
|
||
case .rectangle(let w, let h) where w == h:
|
||
print("Квадрат со стороной \(w)")
|
||
default:
|
||
break
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Макросы (Swift 6)
|
||
|
||
Макросы — метапрограммирование на уровне компилятора. Генерируют код на этапе компиляции.
|
||
|
||
```swift
|
||
// Встроенные макросы Swift 5.9+
|
||
// @Observable — заменяет ObservableObject + @Published + objectWillChange
|
||
@Observable
|
||
class UserProfile {
|
||
var name = ""
|
||
var email = ""
|
||
// Автоматически генерируется код для наблюдения
|
||
}
|
||
|
||
// #Preview — макрос для SwiftUI превью
|
||
#Preview {
|
||
ContentView()
|
||
}
|
||
|
||
// #expect / #require — для Swift Testing
|
||
import Testing
|
||
@Test func validateInput() {
|
||
#expect(validateUsername("alice") == "alice")
|
||
}
|
||
|
||
// Кастомные макросы — определяются как отдельные пакеты
|
||
@attached(member, names: named(init))
|
||
macro AutoInit() = #externalMacro(module: "MyMacros", type: "AutoInitMacro")
|
||
```
|
||
|
||
---
|
||
|
||
## SwiftUI, UIKit, AppKit, WatchKit
|
||
|
||
### Выбор фреймворка
|
||
|
||
| Фреймворк | Платформы | Подход | Рекомендация |
|
||
|--------------|------------------------|-----------------|-----------------------|
|
||
| **SwiftUI** | iOS, macOS, watchOS, tvOS, visionOS | Декларативный | Новые проекты 2023+ |
|
||
| **UIKit** | iOS, iPadOS, tvOS | Императивный | Legacy, сложные кастомные компоненты |
|
||
| **AppKit** | macOS | Императивный | Legacy macOS, сложный UI |
|
||
| **WatchKit** | watchOS (до 7) | Императивный | Устарел, заменён SwiftUI |
|
||
|
||
### SwiftUI — основной выбор для новых приложений
|
||
|
||
```swift
|
||
import SwiftUI
|
||
|
||
// View — протокол, тело описывает структуру UI
|
||
struct ContentView: View {
|
||
@State private var count = 0 // локальное состояние
|
||
@State private var name = ""
|
||
|
||
var body: some View { // some View — opaque return type
|
||
VStack(spacing: 20) {
|
||
Text("Счётчик: \(count)")
|
||
.font(.largeTitle)
|
||
.foregroundStyle(.blue)
|
||
|
||
Button("Нажми меня") {
|
||
count += 1
|
||
}
|
||
.buttonStyle(.borderedProminent)
|
||
|
||
TextField("Имя", text: $name) // $name — Binding
|
||
.textFieldStyle(.roundedBorder)
|
||
|
||
if !name.isEmpty {
|
||
Text("Привет, \(name)!")
|
||
}
|
||
}
|
||
.padding()
|
||
}
|
||
}
|
||
|
||
// @Observable (Swift 5.9+ / iOS 17+) — современная замена ObservableObject
|
||
@Observable
|
||
class AppState {
|
||
var isLoggedIn = false
|
||
var currentUser: User?
|
||
}
|
||
|
||
// Навигация (iOS 16+)
|
||
struct NavigationExample: View {
|
||
@State private var path = NavigationPath()
|
||
|
||
var body: some View {
|
||
NavigationStack(path: $path) {
|
||
List(items) { item in
|
||
NavigationLink(value: item) {
|
||
Text(item.title)
|
||
}
|
||
}
|
||
.navigationDestination(for: Item.self) { item in
|
||
DetailView(item: item)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Cross-platform — один код для iOS и macOS
|
||
struct SharedView: View {
|
||
var body: some View {
|
||
List { }
|
||
#if os(macOS)
|
||
.listStyle(.sidebar)
|
||
#else
|
||
.listStyle(.insetGrouped)
|
||
#endif
|
||
}
|
||
}
|
||
```
|
||
|
||
### Архитектурные паттерны для SwiftUI
|
||
|
||
- **MVVM** — `View` + `@Observable ViewModel` — самый распространённый
|
||
- **TCA (The Composable Architecture)** — популярна в крупных проектах, строгая однонаправленность
|
||
- **VIPER** — переносится из UIKit, но громоздко в SwiftUI
|
||
|
||
### UIKit (для iOS когда нужны legacy или fine-grained компоненты)
|
||
|
||
```swift
|
||
class ViewController: UIViewController {
|
||
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
|
||
let label = UILabel()
|
||
label.text = "Hello"
|
||
label.translatesAutoresizingMaskIntoConstraints = false
|
||
view.addSubview(label)
|
||
|
||
NSLayoutConstraint.activate([
|
||
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||
])
|
||
}
|
||
}
|
||
|
||
// UIHostingController — вставка SwiftUI View в UIKit
|
||
let swiftUIView = ContentView()
|
||
let hostingController = UIHostingController(rootView: swiftUIView)
|
||
present(hostingController, animated: true)
|
||
```
|
||
|
||
### AppKit (macOS)
|
||
|
||
```swift
|
||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||
var window: NSWindow!
|
||
|
||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||
window = NSWindow(
|
||
contentRect: NSRect(x: 100, y: 100, width: 480, height: 300),
|
||
styleMask: [.titled, .closable, .miniaturizable, .resizable],
|
||
backing: .buffered,
|
||
defer: false
|
||
)
|
||
window.contentView = NSHostingView(rootView: ContentView())
|
||
window.makeKeyAndOrderFront(nil)
|
||
}
|
||
}
|
||
```
|
||
|
||
### SwiftData (замена CoreData, iOS 17+ / macOS 14+)
|
||
|
||
```swift
|
||
import SwiftData
|
||
|
||
@Model
|
||
class Task {
|
||
var title: String
|
||
var isCompleted: Bool = false
|
||
var createdAt: Date = Date.now
|
||
|
||
init(title: String) {
|
||
self.title = title
|
||
}
|
||
}
|
||
|
||
// Использование в SwiftUI
|
||
struct TaskListView: View {
|
||
@Environment(\.modelContext) private var context
|
||
@Query(sort: \Task.createdAt) private var tasks: [Task]
|
||
|
||
var body: some View {
|
||
List(tasks) { task in
|
||
Text(task.title)
|
||
}
|
||
.toolbar {
|
||
Button("Добавить") {
|
||
context.insert(Task(title: "Новая задача"))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Экосистема и инструменты
|
||
|
||
### Xcode
|
||
|
||
Единственная официальная IDE для разработки под Apple платформы. Включает:
|
||
- **Interface Builder** — визуальный редактор UI (менее актуален с SwiftUI)
|
||
- **SwiftUI Previews** — live preview в реальном времени
|
||
- **Instruments** — профилировщик (Memory, CPU, Leaks, Time Profiler)
|
||
- **Simulator** — симуляторы iOS/macOS/watchOS
|
||
- **Swift Package Manager (SPM)** — встроенный менеджер зависимостей
|
||
|
||
### Swift Package Manager (SPM)
|
||
|
||
```swift
|
||
// Package.swift
|
||
import PackageDescription
|
||
|
||
let package = Package(
|
||
name: "MyApp",
|
||
platforms: [.iOS(.v17), .macOS(.v14)],
|
||
dependencies: [
|
||
.package(url: "https://github.com/Alamofire/Alamofire", from: "5.8.0"),
|
||
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.53.0")
|
||
],
|
||
targets: [
|
||
.target(
|
||
name: "MyApp",
|
||
dependencies: ["Alamofire"]
|
||
)
|
||
]
|
||
)
|
||
```
|
||
|
||
### Популярные библиотеки
|
||
|
||
| Категория | Библиотека | Описание |
|
||
|-------------------|-------------------------------|--------------------------------|
|
||
| Networking | Alamofire / URLSession | HTTP клиент |
|
||
| JSON | Codable (встроен) | Сериализация/десериализация |
|
||
| Async | Combine / AsyncAlgorithms | Реактивное программирование |
|
||
| DI | Swinject / Factory | Dependency Injection |
|
||
| Testing | XCTest / Swift Testing | Юнит и UI тесты |
|
||
| Linting | SwiftLint / SwiftFormat | Стиль кода |
|
||
| Database | SwiftData / GRDB / Realm | Локальное хранилище |
|
||
| Архитектура | TCA (The Composable Arch.) | Строгая архитектура |
|
||
|
||
### Codable — встроенная сериализация
|
||
|
||
```swift
|
||
struct User: Codable {
|
||
let id: Int
|
||
let name: String
|
||
let email: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case name = "full_name" // переименование ключа
|
||
case email
|
||
}
|
||
}
|
||
|
||
// Декодирование из JSON
|
||
let json = """
|
||
{"id": 1, "full_name": "Alice", "email": "alice@example.com"}
|
||
""".data(using: .utf8)!
|
||
|
||
let user = try JSONDecoder().decode(User.self, from: json)
|
||
print(user.name) // Alice
|
||
|
||
// Кодирование в JSON
|
||
let encoded = try JSONEncoder().encode(user)
|
||
```
|
||
|
||
---
|
||
|
||
## Таблица сравнений Swift vs Kotlin
|
||
|
||
| Концепция | Kotlin | Swift |
|
||
|------------------------------|-------------------------------------|-------------------------------------|
|
||
| Переменная | `var x = 5` | `var x = 5` |
|
||
| Константа | `val x = 5` | `let x = 5` |
|
||
| Null/nil | `null` | `nil` |
|
||
| Nullable тип | `String?` | `String?` |
|
||
| Safe call | `str?.length` | `str?.count` |
|
||
| Elvis / Nil-coalescing | `str ?: "default"` | `str ?? "default"` |
|
||
| Функция | `fun greet(): String` | `func greet() -> String` |
|
||
| Лямбда | `{ x -> x * 2 }` | `{ x in x * 2 }` / `{ $0 * 2 }` |
|
||
| Интерфейс/протокол | `interface` | `protocol` |
|
||
| Data class | `data class Point(val x: Int, ...)` | `struct Point { var x: Int; ... }` |
|
||
| Extension | `fun String.trim()` | `extension String { func trim() }` |
|
||
| Sealed class | `sealed class Result` | `enum Result` с associated values |
|
||
| Корутина / Task | `suspend fun`, `coroutineScope` | `async func`, `Task` |
|
||
| Shared mutable state | `@Volatile`, `Mutex`, `StateFlow` | `actor`, `@MainActor` |
|
||
| Наследование | `: ParentClass()` | `: ParentClass` + `super.init()` |
|
||
| Generics | `fun <T> foo(x: T)` | `func foo<T>(_ x: T)` |
|
||
| when / switch | `when(x) { 1 -> ... }` | `switch x { case 1: ... }` |
|
||
| String interpolation | `"Hello $name"` | `"Hello \(name)"` |
|
||
| this / self | `this` | `self` |
|
||
| constructor / init | `constructor(x: Int)` | `init(x: Int)` |
|
||
| companion object / static | `companion object { ... }` | `static` / `class func` |
|
||
| try? | нет прямого аналога | `try?` |
|
||
| Tuples | `Pair`, `Triple` | `(Int, String)` — полноценный тип |
|
||
| Аннотации | `@Annotation` | нет (только атрибуты `@`) |
|
||
| guard | `require(...)`, `checkNotNull(...)` | `guard let ... else { return }` |
|
||
| typealias | `typealias` | `typealias` |
|
||
|
||
---
|
||
|
||
## Быстрый старт
|
||
|
||
1. Установи Xcode из Mac App Store
|
||
2. Создай новый проект: **File → New → Project → App**
|
||
3. Выбери **SwiftUI** как Interface и **Swift** как язык
|
||
4. Открой `ContentView.swift` и начни разработку
|
||
5. Для watchOS: добавь **WatchKit App** target к существующему проекту
|
||
|
||
### Ресурсы для изучения
|
||
|
||
- [Swift.org — официальная документация](https://swift.org/documentation)
|
||
- [The Swift Programming Language (бесплатная книга от Apple)](https://docs.swift.org/swift-book/)
|
||
- [Apple Developer Documentation](https://developer.apple.com/documentation)
|
||
- [Hacking with Swift (hackingwithswift.com)](https://www.hackingwithswift.com)
|
||
- [Stanford CS193p — iOS Development with SwiftUI](https://cs193p.stanford.edu) — бесплатный курс
|
||
- [Swift Evolution](https://github.com/swiftlang/swift-evolution) — актуальные предложения по развитию языка
|
||
|
||
---
|
||
|
||
*Последнее обновление: июнь 2026. Актуально для Swift 6.x, Xcode 16+, iOS 18+, macOS 15+.*
|