Swift — язык программирования от Apple, сочетающий мощь Objective-C с безопасностью современных языков. В отличие от многих других языков, Swift предлагает уникальные конструкции: value types с автоматическим копированием, Actors с изоляцией на уровне компилятора, Property Wrappers для переиспользования логики свойств и Protocol Oriented Programming как альтернативу классическому наследованию. По данным Apple Swift Documentation, эти возможности делают код безопаснее и производительнее.
Главное
В Swift struct — value type, а class — reference type. Это фундаментальное различие определяет поведение при присваивании, передаче в функции и управлении памятью. Struct копируется целиком при каждом присваивании, class передаётся по ссылке — копируется только указатель на объект в heap.
Struct автоматически реализует value semantics — каждый экземпляр независим. Mutation свойства struct возможна только через var, а методы, изменяющие свойства, должны быть помечены mutating. Struct не поддерживает наследование, но может conformиться к протоколам. Swift стандартной библиотеки использует struct для String, Array, Dictionary, Int, Bool — все фундаментальные типы являются value types.
struct User {
let id: Int
var name: String
mutating func updateName(_ newName: String) {
name = newName
}
}
var user1 = User(id: 1, name: "Alice")
var user2 = user1
user2.updateName("Bob")
// user1.name === "Alice", user2.name === "Bob"
// Полная копия — независимые экземпляры
Class хранится в heap и поддерживает ссылочное равенство. Несколько переменных могут ссылаться на один объект, изменение через одну ссылку видно через все остальные. Class поддерживает наследование, позволяет переопределять методы (override), имеет deinit для очистки ресурсов. В Swift class используется реже, чем struct — Apple рекомендует начинать с struct и переходить на class только когда нужны наследование или ссылочная семантика.
class Car {
let model: String
var speed: Int
init(model: String, speed: Int) {
self.model = model
self.speed = speed
}
func accelerate(_ amount: Int) {
speed += amount
}
}
let car1 = Car(model: "Tesla", speed: 0)
let car2 = car1
car2.accelerate(50)
// car1.speed === 50, car2.speed === 50
// Одна и та же ссылка — изменения видны везде
Выбор между struct и class зависит от семантики данных. Для конфигураций, моделей данных и состояний используйте struct — копирование безопаснее и предсказуемее. Для объектов с идентичностью (user session, database connection) и при необходимости наследования используйте class.
Actor — reference type, который защищает своё mutable-состояние от одновременного доступа. Компилятор Swift гарантирует, что к переменным actor можно обращаться только из того же actor context — попытка доступа извне вызывает ошибку компиляции. Это предотвращает data races на уровне языка, без ручных блокировок.
Все свойства и методы actor по умолчанию изолированы. Для вызова метода actor извне требуется await — Swift переключает выполнение в актор и обратно. Actor синхронизирует доступ последовательно: запросы от разных тасок выстраиваются в очередь. Это гарантирует, что внутри actor состояние консистентно. Неизолированные методы (nonisolated) можно вызывать без await, но они не имеют доступа к mutable-свойствам.
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
}
nonisolated func accountInfo() -> String {
return "Банковский счёт"
}
}
let account = BankAccount(initialBalance: 1000)
await account.deposit(amount: 500)
// balance = 1500 — атомарно, без race condition
Actor удобен для менеджеров состояний, кэшей и сетевых сервисов, где несколько тасок конкурентно читают и пишут данные. В отличие от ручной синхронизации через NSLock или DispatchQueue, actor проверяется на уровне компилятора — ошибиться невозможно. Ошибка изоляции проявляется на этапе компиляции, а не в рантайме.
Property Wrapper — аннотация, которая добавляет поведение свойству без изменения его объявления. SwiftUI и Combine предоставляют встроенные wrappers для управления состоянием представлений. Компилятор генерирует код, который обёртывает доступ к свойству, добавляя логику наблюдения, синхронизации или трансформации.
@Published используется в классах, conforming к ObservableObject, для автоматической публикации изменений. Combine подписчики получают уведомления при каждом обновлении. @State — локальное состояние представления SwiftUI. SwiftUI автоматически перерисовывает view при изменении @State. @Binding создаёт двустороннюю связь между родительским и дочерним view без владения данными.
import SwiftUI
import Combine
@main
struct CounterApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct ContentView: View {
@State private var greeting: String = "Hello"
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
CounterDisplay(count: $viewModel.count)
Text(greeting)
Button("Increment", action: viewModel.increment)
}
}
}
struct CounterDisplay: View {
@Binding var count: Int
var body: some View {
Text("Count: \(count)")
}
}
Кастомный Property Wrapper создаётся через @propertyWrapper и структуру с полем wrappedValue. Это позволяет переиспользовать логику: валидацию, нормализацию, UserDefaults-хранение. Property Wrappers значительно сокращают шаблонный код и делают намерение разработчика явным через аннотацию.
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
init(wrappedValue: T, range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct Settings {
@Clamped(range: 0...100) var volume: Int = 50
}
var s = Settings()
s.volume = 150
// s.volume === 100 — значение автоматически ограничено
Generics — мощный механизм Swift для написания гибкого кода без потери типовой безопасности. Generic-функции и типы работают с любым типом, удовлетворяющим ограничениям. Protocol Oriented Programming (POP) — парадигма Swift, где protocol + extension заменяют классическое наследование классов, обеспечивая композицию поведения.
Swift позволяет ограничивать generic-параметры протоколами (where clauses). Protocol с associated type — аналог generic для протоколов: конкретный тип определяется в момент conforming. Это основа для типобезопасных коллекций и алгоритмов. Стандартная библиотека Swift активно использует generics: Array, Dictionary, Optional — все это generic-типы.
protocol Cacheable {
associatedtype Key: Hashable
associatedtype Value
func get(_ key: Key) -> Value?
mutating func set(_ value: Value, for key: Key)
}
struct MemoryCache<K: Hashable, V>: Cacheable {
private var storage: [K: V] = [:]
typealias Key = K
typealias Value = V
func get(_ key: K) -> V? {
return storage[key]
}
mutating func set(_ value: V, for key: K) {
storage[key] = value
}
}
func firstMatch<T: Equatable>(in array: [T], target: T) -> Int? {
for (index, item) in array.enumerated() {
if item == target {
return index
}
}
return nil
}
let numbers = [10, 20, 30, 40]
let index = firstMatch(in: numbers, target: 30)
// index === 2 — один generic-метод для всех Equatable-типов
Protocol extension — ключевой механизм POP. В extension можно задать дефолтную реализацию методов протокола. Тип, conforming к протоколу, получает эту реализацию бесплатно и может переопределить её при необходимости. Это позволяет строить иерархию поведения без наследования классов — альтернатива множественному наследованию, которого в Swift нет.
protocol Drivable {
var speed: Double { get set }
func drive()
}
extension Drivable {
func drive() {
if speed > 0 {
print("Driving at \(speed) km/h")
}
}
}
struct Bicycle: Drivable {
var speed: Double = 15
}
struct Plane: Drivable {
var speed: Double = 900
func drive() {
print("Flying at \(speed) km/h")
}
}
let bike = Bicycle()
bike.drive() // "Driving at 15.0 km/h" — дефолт
let plane = Plane()
plane.drive() // "Flying at 900.0 km/h" — переопределено
POP с extension даёт композицию через множественные протоколы — тип может conformиться к нескольким протоколам, получая их реализации. Это гибче единичного наследования классов, где вся иерархия фиксирована. Swift стандартная библиотека построена на POP: CustomStringConvertible, Equatable, Hashable, Codable — всё это протоколы с extension.
Enum в Swift — значительно более мощная конструкция, чем в других языках. Каждый кейс может иметь associated values — произвольный набор данных любого типа. В сочетании с extension и pattern matching enum становится основой для моделирования состояний, результатов операций и конечных автоматов.
Associated values позволяют каждому кейсу хранить уникальные данные. Например, кейс loading не содержит данных, success содержит результат, failure — ошибку. Pattern matching через switch извлекает associated values и обрабатывает каждый кейс. Компилятор проверяет полноту покрытия (exhaustive switch) — это исключает забытые состояния.
enum NetworkResult<T> {
case loading
case success(T)
case failure(Error)
}
enum MediaFile {
case image(url: URL, width: Int, height: Int)
case video(url: URL, duration: Double)
case audio(url: URL, bitrate: Int)
}
func handle(_ result: NetworkResult<String>) {
switch result {
case .loading:
print("Загрузка...")
case .success(let data):
print("Получено: \(data)")
case .failure(let error):
print("Ошибка: \(error.localizedDescription)")
}
}
let file = MediaFile.video(url: URL(string: "https://example.com/video.mp4")!, duration: 120)
if case .video(let url, let duration) = file {
print("Video \(url) длительностью \(duration)s")
}
Swift позволяет добавлять методы, computed properties и conforming к протоколам через extension для enum. Это не требует изменения оригинального объявления — код остаётся чистым и модульным. Методы в extension могут работать с associated values, вычислять производные данные и реализовывать бизнес-логику.
extension MediaFile {
var fileName: String {
switch self {
case .image(let url, _, _),
.video(let url, _),
.audio(let url, _):
return url.lastPathComponent
}
}
var description: String {
switch self {
case .image(_, let w, let h):
return "Изображение \(w)x\(h)"
case .video(_, let d):
return "Видео \(d)s"
case .audio(_, let b):
return "Аудио \(b) kbps"
}
}
}
extension MediaFile: Equatable {
static func == (lhs: MediaFile, rhs: MediaFile) -> Bool {
return lhs.fileName == rhs.fileName
}
}
let files: [MediaFile] = [file]
files.forEach { print($0.description) }
// "Видео 120.0s"
Enum с associated values + extension — это замена sealed classes из Kotlin или discriminated unions из других языков. Swift использует эту комбинацию для Result (стандартный тип в стандартной библиотеке), Optional (Optional — это enum с .none и .some) и обработки состояний в SwiftUI. Pattern matching гарантирует, что все состояния обработаны — ошибки невозможны в рантайме.
Часто задаваемые вопросы
Struct — value type, копируется при присваивании, не поддерживает наследование. Class — reference type, передаётся по ссылке, поддерживает наследование, deinit и identity check (===). Apple рекомендует начинать с struct и переходить на class только при необходимости ссылочной семантики.
Actor — потокобезопасный reference type. Компилятор запрещает одновременный доступ к mutable-состоянию из разных контекстов. Используйте Actor для менеджеров состояний, кэшей и сервисов, к которым обращаются несколько тасок. Вызов методов actor требует await.
Основные: @State — локальное состояние view; @Binding — связь с данными без владения; @Published — публикация изменений в ObservableObject; @StateObject — владение ObservableObject; @EnvironmentObject — зависимость из окружения. Кастомные wrappers создаются через @propertyWrapper.
POP — парадигма, где protocol + extension задают поведение вместо class inheritance. Тип conformится к нескольким протоколам, получая их реализации бесплатно. Это композиция вместо наследования — гибче и безопаснее. Стандартная библиотека Swift (Equatable, Codable) построена на POP.
Enum в Swift поддерживает associated values — каждый кейс хранит произвольные данные разных типов. В сочетании с pattern matching (switch) это даёт мощную модель для состояний, ошибок и Domain-Driven Design. Optional и Result — стандартные enum Swift с associated values.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.