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 не підтримує успадкування, але може conformity до протоколів. Стандартна бібліотека 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 синхронізує доступ послідовно: запити від різних тасок вишиковуються в чергу. Це гарантує, що всередині 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 = "Привіт"
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
CounterDisplay(count: $viewModel.count)
Text(greeting)
Button("Збільшити", action: viewModel.increment)
}
}
}
struct CounterDisplay: View {
@Binding var count: Int
var body: some View {
Text("Лічильник: \(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("Їдемо зі швидкістю \(speed) км/год")
}
}
}
struct Bicycle: Drivable {
var speed: Double = 15
}
struct Plane: Drivable {
var speed: Double = 900
func drive() {
print("Летимо зі швидкістю \(speed) км/год")
}
}
let bike = Bicycle()
bike.drive() // "Їдемо зі швидкістю 15.0 км/год" — дефолт
let plane = Plane()
plane.drive() // "Летимо зі швидкістю 900.0 км/год" — перевизначено
POP з extension дає композицію через множинні протоколи — тип може conformity до кількох протоколів, отримуючи їх реалізації. Це гнучкіше одиничного успадкування класів, де вся ієрархія фіксована. Стандартна бібліотека 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("Відео \(url) тривалістю \(duration)с")
}
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)с"
case .audio(_, let b):
return "Аудіо \(b) кбіт/с"
}
}
}
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. Тип conformity до кількох протоколів, отримуючи їх реалізації безкоштовно. Це композиція замість успадкування — гнучкіше та безпечніше. Стандартна бібліотека 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.