Swift to język programowania Apple, łączący moc Objective-C z bezpieczeństwem nowoczesnych języków. W przeciwieństwie do wielu innych języków, Swift oferuje unikalne konstrukcje: typy wartości z automatycznym kopiowaniem, Actor z izolacją na poziomie kompilatora, Property Wrappers do ponownego wykorzystania logiki właściwości i Programowanie Zorientowane Protokołowo jako alternatywę dla klasycznego dziedziczenia. Według Dokumentacji Apple Swift, te funkcje sprawiają, że kod jest bezpieczniejszy i wydajniejszy.
Najważniejsze
W Swift, struct to typ wartości, a class to typ referencyjny. Ta fundamentalna różnica określa zachowanie podczas przypisania, przekazywania do funkcji i zarządzania pamięcią. Struct jest kopiowany w całości przy każdym przypisaniu, class jest przekazywany przez referencję — kopiowany jest tylko wskaźnik do obiektu na stercie.
Struct automatycznie implementuje semantykę wartości — każda instancja jest niezależna. Mutacja właściwości struct jest możliwa tylko przez var, a metody modyfikujące właściwości muszą być oznaczone jako mutating. Struct nie obsługuje dziedziczenia, ale może dostosowywać się do protokołów. Standardowa biblioteka Swift używa struct dla String, Array, Dictionary, Int, Bool — wszystkie podstawowe typy są typami wartości.
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"
// Pełna kopia — niezależne instancje
Class jest przechowywana na stercie i obsługuje równość referencyjną. Wiele zmiennych może odnosić się do tego samego obiektu, zmiany przez jedną referencję są widoczne przez wszystkie inne. Class obsługuje dziedziczenie, pozwala na nadpisywanie metod (override), ma deinit do czyszczenia zasobów. W Swift, class jest używana rzadziej niż struct — Apple zaleca zaczynanie od struct i przechodzenie do class tylko gdy potrzebne jest dziedziczenie lub semantyka referencyjna.
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
// Ta sama referencja — zmiany widoczne wszędzie
Wybór między struct a class zależy od semantyki danych. Dla konfiguracji, modeli danych i stanów używaj struct — kopiowanie jest bezpieczniejsze i bardziej przewidywalne. Dla obiektów z tożsamością (sesja użytkownika, połączenie z bazą danych) i gdy potrzebne jest dziedziczenie, używaj class.
Actor to typ referencyjny, który chroni swój mutowalny stan przed jednoczesnym dostępem. Kompilator Swift gwarantuje, że zmienne actor są dostępne tylko z tego samego kontekstu actor — każdy zewnętrzny dostęp powoduje błąd kompilacji. Zapobiega to wyścigom danych na poziomie języka, bez ręcznych blokad.
Wszystkie właściwości i metody actor są domyślnie izolowane. Wywołanie metody actor z zewnątrz wymaga await — Swift przełącza wykonanie do actor i z powrotem. Actor synchronizuje dostęp sekwencyjnie: żądania z różnych zadań są kolejkowane. Gwarantuje to spójny stan wewnątrz actor. Nieizolowane metody (nonisolated) mogą być wywoływane bez await, ale nie mają dostępu do mutowalnych właściwości.
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 "Konto Bankowe"
}
}
let account = BankAccount(initialBalance: 1000)
await account.deposit(amount: 500)
// balance = 1500 — atomowo, bez wyścigu
Actor jest przydatny dla menedżerów stanu, pamięci podręcznej i usług sieciowych, gdzie wiele zadań jednocześnie czyta i zapisuje dane. W przeciwieństwie do ręcznej synchronizacji przez NSLock lub DispatchQueue, actor jest sprawdzany na etapie kompilacji — popełnienie błędu jest niemożliwe. Błędy izolacji pojawiają się na etapie kompilacji, nie w czasie wykonywania.
Property Wrapper to adnotacja, która dodaje zachowanie do właściwości bez zmiany jej deklaracji. SwiftUI i Combine dostarczają wbudowane wrappery do zarządzania stanem widoków. Kompilator generuje kod, który opakowuje dostęp do właściwości, dodając logikę obserwacji, synchronizacji lub transformacji.
@Published jest używane w klasach zgodnych z ObservableObject do automatycznego publikowania zmian. Subskrybenci Combine otrzymują powiadomienia przy każdej aktualizacji. @State to lokalny stan widoku SwiftUI. SwiftUI automatycznie przerysowuje widok, gdy @State się zmieni. @Binding tworzy dwukierunkowe połączenie między widokiem rodzicielskim a potomnym bez posiadania danych.
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 = "Cześć"
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
CounterDisplay(count: $viewModel.count)
Text(greeting)
Button("Zwiększ", action: viewModel.increment)
}
}
}
struct CounterDisplay: View {
@Binding var count: Int
var body: some View {
Text("Licznik: \(count)")
}
}
Niestandardowe Property Wrappers są tworzone za pomocą @propertyWrapper i struktury z polem wrappedValue. Pozwala to na ponowne wykorzystanie logiki: walidacji, normalizacji, przechowywania w UserDefaults. Property Wrappers znacznie redukują kod boilerplate i czynią intencję programisty jawną poprzez adnotację.
@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 — wartość automatycznie ograniczona
Generics to potężny mechanizm Swift do pisania elastycznego kodu bez utraty bezpieczeństwa typów. Funkcje i typy generyczne działają z każdym typem spełniającym ograniczenia. Programowanie Zorientowane Protokołowo (POP) to paradygmat Swift, w którym protocol + extension zastępują klasyczne dziedziczenie klas, zapewniając kompozycję zachowania.
Swift pozwala ograniczać parametry generyczne protokołami (klauzule where). Protokół z typem stowarzyszonym to odpowiednik generyczny dla protokołów: konkretny typ jest określany w momencie dostosowania. To podstawa dla bezpiecznych typowo kolekcji i algorytmów. Standardowa biblioteka Swift aktywnie używa generics: Array, Dictionary, Optional — wszystkie są typami generycznymi.
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 — jedna generyczna metoda dla wszystkich typów Equatable
Rozszerzenie protokołu to kluczowy mechanizm POP. W rozszerzeniu można dostarczyć domyślną implementację metod protokołu. Typy dostosowujące się do protokołu otrzymują tę implementację za darmo i mogą ją nadpisać w razie potrzeby. Pozwala to budować hierarchie zachowań bez dziedziczenia klas — alternatywa dla wielodziedziczenia, które nie istnieje w Swift.
protocol Drivable {
var speed: Double { get set }
func drive()
}
extension Drivable {
func drive() {
if speed > 0 {
print("Jadę z prędkością \(speed) km/h")
}
}
}
struct Bicycle: Drivable {
var speed: Double = 15
}
struct Plane: Drivable {
var speed: Double = 900
func drive() {
print("Lecę z prędkością \(speed) km/h")
}
}
let bike = Bicycle()
bike.drive() // "Jadę z prędkością 15.0 km/h" — domyślna
let plane = Plane()
plane.drive() // "Lecę z prędkością 900.0 km/h" — nadpisana
POP z rozszerzeniem umożliwia kompozycję przez wiele protokołów — typ może dostosowywać się do wielu protokołów, otrzymując ich implementacje. Jest to bardziej elastyczne niż pojedyncze dziedziczenie klas, gdzie cała hierarchia jest stała. Standardowa biblioteka Swift jest zbudowana na POP: CustomStringConvertible, Equatable, Hashable, Codable — wszystkie to protokoły z rozszerzeniami.
Enum w Swift to znacznie potężniejsza konstrukcja niż w innych językach. Każdy przypadek może mieć wartości stowarzyszone — dowolny zestaw danych dowolnego typu. W połączeniu z extension i dopasowywaniem wzorców, enum staje się podstawą do modelowania stanów, wyników operacji i maszyn stanów skończonych.
Wartości stowarzyszone pozwalają każdemu przypadkowi przechowywać unikalne dane. Na przykład, przypadek loading nie zawiera danych, success zawiera wynik, failure zawiera błąd. Dopasowywanie wzorców przez switch wyodrębnia wartości stowarzyszone i obsługuje każdy przypadek. Kompilator sprawdza wyczerpanie (exhaustive switch) — eliminuje to zapomniane stany.
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("Ładowanie...")
case .success(let data):
print("Otrzymano: \(data)")
case .failure(let error):
print("Błąd: \(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("Wideo \(url) czas trwania \(duration)s")
}
Swift pozwala dodawać metody, właściwości obliczane i zgodność z protokołami przez extension dla enum. Nie wymaga to zmiany oryginalnej deklaracji — kod pozostaje czysty i modułowy. Metody w extension mogą pracować z wartościami stowarzyszonymi, obliczać dane pochodne i implementować logikę biznesową.
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 "Obraz \(w)x\(h)"
case .video(_, let d):
return "Wideo \(d)s"
case .audio(_, let b):
return "Audio \(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) }
// "Wideo 120.0s"
Enum z wartościami stowarzyszonymi + extension to zamiennik klas zapieczętowanych z Kotlina lub unii dyskryminowanych z innych języków. Swift używa tej kombinacji dla Result (standardowy typ w bibliotece standardowej), Optional (Optional to enum z .none i .some) i obsługi stanów w SwiftUI. Dopasowywanie wzorców gwarantuje obsługę wszystkich stanów — błędy w czasie wykonywania są niemożliwe.
Często Zadawane Pytania
Struct — typ wartości, kopiowany przy przypisaniu, nie obsługuje dziedziczenia. Class — typ referencyjny, przekazywany przez referencję, obsługuje dziedziczenie, deinit i sprawdzanie tożsamości (===). Apple zaleca zaczynanie od struct i przechodzenie do class tylko gdy potrzebna jest semantyka referencyjna.
Actor — bezpieczny wątkowo typ referencyjny. Kompilator zabrania jednoczesnego dostępu do mutowalnego stanu z różnych kontekstów. Używaj Actor do menedżerów stanu, pamięci podręcznej i usług dostępnych przez wiele zadań. Wywoływanie metod actor wymaga await.
Główne: @State — lokalny stan widoku; @Binding — połączenie z danymi bez posiadania; @Published — publikowanie zmian w ObservableObject; @StateObject — posiadanie ObservableObject; @EnvironmentObject — zależność ze środowiska. Niestandardowe wrappery tworzy się przez @propertyWrapper.
POP — paradygmat, w którym protocol + extension definiują zachowanie zamiast dziedziczenia klas. Typ dostosowuje się do wielu protokołów, otrzymując ich implementacje za darmo. To kompozycja zamiast dziedziczenia — bardziej elastyczna i bezpieczniejsza. Standardowa biblioteka Swift (Equatable, Codable) jest zbudowana na POP.
Enum w Swift obsługuje wartości stowarzyszone — każdy przypadek może przechowywać dowolne dane różnych typów. W połączeniu z dopasowywaniem wzorców (switch) daje to potężny model dla stanów, błędów i projektowania sterowanego domeną. Optional i Result to standardowe enumy Swift z wartościami stowarzyszonymi.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.