Основы
Переменные и константы
Используйте let по умолчанию для значений, которые не изменяются; переключайтесь на var только при необходимости изменения. Swift определяет типы во время компиляции, но явные аннотации улучшают читаемость для сложных или неоднозначных типов.
var name = "Alice" // mutable
let age = 30 // immutable constant
let pi: Double = 3.14159 // explicit type
var count: Int = 0
count = 10
print(type(of: age)) // IntОпционалы
Опционалы представляют отсутствие значения. Используйте if-let для безопасного извлечения, ?? для значений по умолчанию и ! только когда вы уверены, что значение существует (рискованно — может вызвать сбой).
var nickname: String? = nil
nickname = "Al"
if let n = nickname {
print("Hi, \(n)")
}
print(nickname ?? "Anonymous") // nil-coalescing
let force: String = nickname! // force unwrap (crashes if nil)Кортежи
Кортежи группируют несколько значений в одно составное значение. Полезны для возврата нескольких значений из функций. Именованные элементы улучшают читаемость.
let person = (name: "Alice", age: 30)
print(person.name) // Alice
print(person.0) // Alice
let (n, a) = person
print("\(n), \(a)") // Alice, 30Преобразование типов
Swift не выполняет неявное преобразование между типами. Всегда используйте явное преобразование (например, Double(intVal)) для избежания неоднозначности и предотвращения скрытых ошибок.
let intVal = 42
let doubleVal = Double(intVal)
let strVal = String(intVal)
let fromStr = Int("100")! // 100
let invalid = Int("abc") // nil
print(doubleVal, strVal)Утверждения и предусловия
Используйте assert для отладочных проверок (удаляются в релизных сборках) и precondition для критических инвариантов, проверяемых во всех сборках. Оба помогают выявлять логические ошибки на раннем этапе.
let age = -5
assert(age >= 0, "Age cannot be negative")
precondition(age >= 0, "Age must be non-negative")
// In debug builds, assert crashes if false
// precondition checks in release builds tooСтроки и символы
Интерполяция строк
Интерполяция строк с \(expr) встраивает любое выражение в строку. Она типобезопасна и вычисляется во время компиляции, что делает её безопаснее строк форматирования.
let name = "Alice"
let age = 30
let msg = "Name: \(name), Age: \(age)"
let calc = "5 + 3 = \(5 + 3)"
print(msg) // Name: Alice, Age: 30
print(calc) // 5 + 3 = 8Распространённые строковые методы
Строки Swift корректны с точки зрения Unicode, то есть count отражает фактическое количество символов (кластеров графем), а не байтов. Используйте эти методы вместо ручной индексации для безопасности.
let s = "Hello, World"
print(s.count) // 12
print(s.uppercased()) // HELLO, WORLD
print(s.lowercased()) // hello, world
print(s.hasPrefix("Hello")) // true
print(s.contains("World")) // true
print(s.reversed()) // "dlroW ,olleH"Подстроки и индексация
Индексы строк не являются целыми числами, поскольку символы могут иметь разный размер в байтах. Используйте index(_:offsetBy:) для навигации. Подстроки разделяют память с оригиналом — преобразуйте в String для долгосрочного хранения.
let s = "Hello, World"
let idx = s.index(s.startIndex, offsetBy: 7)
print(s[idx]) // W
let sub = s[idx...] // "World"
let newStr = String(sub) // convert to String
print(s.prefix(5)) // "Hello"Многострочные строки
Строки в тройных кавычках сохраняют переносы строк и отступы. Закрывающая """ определяет базовый отступ. Идеально для HTML, JSON или длинных текстовых блоков.
let poem = """
Roses are red,
Violets are blue,
Swift is great,
And so are you.
"""
print(poem)
// Use \\(expr) for interpolation in multilineПостроение и разделение строк
Используйте joined(separator:) для конкатенации с разделителем и split(separator:) для токенизации. Это эффективнее ручных циклов с конкатенацией через +.
let parts = ["apple", "banana", "cherry"]
let joined = parts.joined(separator: ", ")
print(joined) // apple, banana, cherry
let csv = "a,b,c"
let fields = csv.split(separator: ",")
print(fields) // ["a", "b", "c"]
let reversed = String(s.reversed())Структуры данных
Массив
Массивы — это упорядоченные коллекции с нулевой индексацией. Используйте append/insert для добавления, filter/map/reduce для преобразований. Предпочитайте типы-значения (Array является структурой) для потокобезопасности.
var nums = [1, 2, 3]
nums.append(4)
nums.insert(0, at: 0)
nums[1] = 20
print(nums.count) // 5
print(nums.contains(20)) // true
let evens = nums.filter { $0 % 2 == 0 }
print(nums.first, nums.last)Словарь
Словари хранят пары ключ-значение со средним временем поиска O(1). Используйте default: для избежания nil при доступе к отсутствующим ключам. Ключи должны соответствовать Hashable.
var ages: [String: Int] = ["Alice": 30]
ages["Bob"] = 25
ages["Alice"] = 31
print(ages["Alice", default: 0]) // 31
for (name, age) in ages {
print("\(name): \(age)")
}
print(ages.keys.sorted())Множество
Множества хранят уникальные значения с проверкой принадлежности за O(1). Идеально для дедупликации и операций над множествами (объединение, пересечение, разность). Элементы должны соответствовать Hashable.
var a: Set<Int> = [1, 2, 3]
var b: Set<Int> = [3, 4, 5]
print(a.union(b)) // {1,2,3,4,5}
print(a.intersection(b)) // {3}
print(a.subtracting(b)) // {1,2}
print(a.isSubset(of: b)) // false
a.insert(6)Диапазоны
..< — полузакрытый диапазон (исключает верхнюю границу), ... — закрытый диапазон (включает обе границы). Диапазоны полезны в циклах, срезах и сопоставлении шаблонов с оператором ~=.
for i in 0..<5 { print(i) } // 0,1,2,3,4
for i in 0...5 { print(i) } // 0,1,2,3,4,5
let nums = Array(1...5) // [1,2,3,4,5]
let prefix = nums.prefix(3) // [1,2,3]
if 5...10 ~= 7 { print("in range") }Функции высшего порядка
map преобразует каждый элемент, filter выбирает подходящие элементы, reduce объединяет все в один. Это основа функционального программирования в Swift и позволяет создавать лаконичные, читаемые конвейеры обработки данных.
let nums = [1, 2, 3, 4, 5]
let doubled = nums.map { $0 * 2 } // [2,4,6,8,10]
let evens = nums.filter { $0 % 2 == 0 } // [2,4]
let sum = nums.reduce(0, +) // 15
let strs = nums.map { String($0) } // ["1","2",...]
print(doubled, evens, sum)Поток управления
If / Else If / Else
Стандартное условное ветвление. Условия должны быть логическими (тип Bool). Swift требует фигурных скобок даже для одиночных операторов для предотвращения таких ошибок, как уязвимость Apple goto fail.
let score = 85
if score >= 90 {
print("A")
} else if score >= 80 {
print("B")
} else if score >= 70 {
print("C")
} else {
print("F")
}Switch (сопоставление шаблонов)
Switch в Swift мощный: поддерживает привязку значений, кортежи, условия where и диапазоны. Он должен быть исчерпывающим (default покрывает остальные случаи) и не проваливается по умолчанию.
let point = (2, 0)
switch point {
case (0, 0):
print("origin")
case (_, 0):
print("on x-axis")
case (0, _):
print("on y-axis")
case let (x, y) where x == y:
print("on diagonal")
default:
print("elsewhere: \(point.0),\(point.1)")
}Циклы For-In
For-in перебирает диапазоны, массивы, словари и любой Sequence. Используйте enumerated(), когда нужен индекс. Используйте _ для игнорирования неиспользуемых переменных цикла.
for i in 0..<5 { print(i) }
let fruits = ["apple", "banana"]
for (i, fruit) in fruits.enumerated() {
print("\(i): \(fruit)")
}
let dict = ["a": 1, "b": 2]
for (k, v) in dict { print("\(k)=\(v)") }
for _ in 0..<3 { print("tick") }While и Repeat-While
while проверяет условие перед каждой итерацией; repeat-while проверяет после (как do-while в C). Используйте repeat-while, когда тело должно выполниться хотя бы один раз.
var n = 5
while n > 0 {
print(n)
n -= 1
}
var x = 0
repeat {
x += 1
} while x < 3
print(x) // 3Guard (ранний выход)
guard обеспечивает ранний выход, когда предпосылки не выполнены. Развёрнутые опционалы остаются доступными в остальной части области видимости, уменьшая вложенность. Предпочитайте guard глубокой вложенности if-let.
func greet(_ name: String?) {
guard let n = name, !n.isEmpty else {
print("No name provided")
return
}
print("Hello, \(n)")
// n is unwrapped and available here
}
greet("Alice")
greet(nil)Функции и замыкания
Определение функции
Используйте _ для пропуска меток аргументов ради читаемости. Значения параметров по умолчанию делают параметры необязательными. Функции с одним выражением имеют неявный возврат (Swift 5.9+).
func greet(_ name: String, greeting: String = "Hello") -> String {
return "\(greeting), \(name)!"
}
print(greet("Alice")) // Hello, Alice!
print(greet("Bob", greeting: "Hi")) // Hi, Bob!
func add(_ a: Int, _ b: Int) -> Int { a + b }
print(add(3, 4)) // 7Множественные возвращаемые значения
Кортежи позволяют возвращать несколько именованных значений. Возврат опционального кортежа сигнализир ует о возможной неудаче. Доступ через r.min или r.0 — именованные элементы понятнее.
func minMax(_ nums: [Int]) -> (min: Int, max: Int)? {
guard let first = nums.first else { return nil }
var mn = first, mx = first
for n in nums { mn = min(mn, n); mx = max(mx, n) }
return (mn, mx)
}
if let r = minMax([3, 1, 4, 1, 5]) {
print("min=\(r.min), max=\(r.max)")
}Замыкания
Замыкания — это самодостаточные блоки функциональности. Используйте $0, $1 для сокращённых имён аргументов. Синтаксис завершающего замыкания (опускание метки последнего аргумента) идиоматичен для map/filter/reduce.
let square: (Int) -> Int = { x in x * x }
print(square(5)) // 25
let add: (Int, Int) -> Int = { $0 + $1 }
print(add(3, 4)) // 7
let nums = [1, 2, 3]
let doubled = nums.map { $0 * 2 }
print(doubled) // [2, 4, 6]Escaping и авто-замыкания
@escaping помечает замыкания, сохраняемые или вызываемые после возврата из функции (нужно для асинхронных обратных вызовов). @autoclosure оборачивает выражение в замыкание, откладывая его вычисление (используется в assert).
var handlers: [() -> Void] = []
func saveHandler(_ fn: @escaping () -> Void) {
handlers.append(fn)
}
saveHandler { print("done") }
handlers.first?()
// @autoclosure delays evaluation
func logIfTrue(_ cond: @autoclosure () -> Bool) {
if cond() { print("true") }
}
logIfTrue(2 > 1)Параметры inout
Параметры inout позволяют функциям изменять переменную вызывающего (передача по ссылке). Используйте умеренно — предпочитайте возврат новых значений для ясности. Префикс & отмечает место изменения.
func swap(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
var x = 10, y = 20
swap(&x, &y)
print(x, y) // 20 10Классы и структуры
Структура (тип-значение)
Структуры — типы-значения, копируемые при присваивании. Используйте для простых контейнеров данных. Вычисляемые свойства (distance) вычисляются при доступе. Методы mutating изменяют self.
struct Point {
var x: Double
var y: Double
var distance: Double {
(x * x + y * y).squareRoot()
}
mutating func moveBy(dx: Double, dy: Double) {
x += dx; y += dy
}
}
var p = Point(x: 3, y: 4)
print(p.distance) // 5.0Класс (ссылочный тип)
Классы — ссылочные типы, разделяемые через ссылки, поддерживают наследование и deinit. Используйте, когда нужна идентичность, разделяемое изменяемое состояние или совместимость с Objective-C. В остальном предпочитайте структуры.
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
deinit { print("\(name) deallocated") }
}
let p = Person(name: "Alice", age: 30)
print(p.name) // AliceНаследование и переопределение
Используйте override для переопределения метода суперкласса. Swift использует динамическую диспетчеризацию, поэтому вызывается версия подкласса. Пометьте методы final для предотвращения дальнейшего переопределения ради производительности.
class Animal {
func speak() { print("...") }
}
class Dog: Animal {
override func speak() { print("Woof!") }
}
class Cat: Animal {
override func speak() { print("Meow!") }
}
let pets: [Animal] = [Dog(), Cat()]
for pet in pets { pet.speak() }Свойства (вычисляемые и lazy)
Вычисляемые свойства имеют блоки get/set; set использует newValue по умолчанию. lazy откладывает инициализацию до первого доступа — полезно для дорогих или редко нужных свойств. Должно быть var.
class Circle {
var radius: Double
var area: Double {
get { .pi * radius * radius }
set { radius = (newValue / .pi).squareRoot() }
}
lazy var heavy = loadExpensiveData()
init(radius: Double) { self.radius = radius }
}
func loadExpensiveData() -> String { "data" }Наблюдатели свойств
willSet/didSet наблюдают за изменениями свойств. Используйте didSet для проверки или реакции на изменения (например, ограничение значений, запуск обновлений UI). Наблюдатели не срабатывают во время init.
class Counter {
var count: Int = 0 {
willSet { print("about to set \(newValue)") }
didSet {
print("was \(oldValue), now \(count)")
if count > 10 { count = 10 }
}
}
}
let c = Counter()
c.count = 5
c.count = 20 // clamped to 10Протоколы и расширения
Определение протокола
Протоколы определяют чертёж методов и свойств. Типы соответствуют им, реализуя их. Используйте для абстракции, полиморфизма и слабой связности — похоже на интерфейсы в Java/C#.