Swift specifics in mobile development: what it is, key features and how it works

Author: IT Sectr Published: 2026-06-25 Reading time: 17 min

Swift is Apple's programming language that combines the power of Objective-C with the safety of modern languages. Unlike many other languages, Swift offers unique constructs: value types with automatic copying, Actors with compiler-level isolation, Property Wrappers for reusing property logic, and Protocol Oriented Programming as an alternative to classical inheritance. According to Apple Swift Documentation, these features make code safer and more performant.

Key Takeaways

  • Struct — value type, copied on assignment, does not support inheritance. Class — reference type, passed by reference, supports inheritance and deinit.
  • Actor — thread-safe reference type with compiler-level isolation. The compiler prevents concurrent access to mutable state from different tasks.
  • Property Wrappers — @Published, @State, @Binding encapsulate property logic in SwiftUI and Combine, eliminating boilerplate code.
  • Generics allow writing type-safe functions and structures without binding to a specific type. Protocol Oriented Programming uses extension + protocol instead of class hierarchy.
  • Enum with associated values — a powerful tool for modeling states. Each case can store an arbitrary set of data of different types.

Struct vs Class in Swift: value types and reference types

In Swift, struct is a value type, while class is a reference type. This fundamental difference determines behavior during assignment, passing to functions, and memory management. Struct is copied entirely on each assignment, class is passed by reference — only the pointer to the heap object is copied.

Value Semantics of Struct

Struct automatically implements value semantics — each instance is independent. Mutation of a struct property is only possible through var, and methods that modify properties must be marked mutating. Struct does not support inheritance but can conform to protocols. Swift's standard library uses struct for String, Array, Dictionary, Int, Bool — all fundamental types are value types.

swift
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"
// Full copy — independent instances

Reference Semantics of Class

Class is stored in the heap and supports reference equality. Multiple variables can reference the same object, changes through one reference are visible through all others. Class supports inheritance, allows method overriding (override), has deinit for resource cleanup. In Swift, class is used less frequently than struct — Apple recommends starting with struct and switching to class only when inheritance or reference semantics are needed.

swift
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
// Same reference — changes visible everywhere

The choice between struct and class depends on data semantics. For configurations, data models, and states use struct — copying is safer and more predictable. For objects with identity (user session, database connection) and when inheritance is needed, use class.

Actor — thread safety in Swift

Actor is a reference type that protects its mutable state from concurrent access. The Swift compiler guarantees that actor variables can only be accessed from within the same actor context — any external access causes a compilation error. This prevents data races at the language level without manual locks.

Actor Isolation

All actor properties and methods are isolated by default. Calling an actor method from outside requires await — Swift switches execution to the actor and back. Actor synchronizes access sequentially: requests from different tasks are queued. This ensures consistent state inside the actor. Non-isolated methods (nonisolated) can be called without await but cannot access mutable properties.

swift
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 "Bank Account"
    }
}

let account = BankAccount(initialBalance: 1000)
await account.deposit(amount: 500)
// balance = 1500 — atomic, no race condition

Actor is useful for state managers, caches, and network services where multiple tasks concurrently read and write data. Unlike manual synchronization via NSLock or DispatchQueue, actor is checked at compile time — it is impossible to make a mistake. Isolation errors appear at compile time, not at runtime.

Property Wrappers: @Published, @State, @Binding

Property Wrapper is an annotation that adds behavior to a property without changing its declaration. SwiftUI and Combine provide built-in wrappers for managing view state. The compiler generates code that wraps property access, adding observation, synchronization, or transformation logic.

@Published and @State

@Published is used in classes conforming to ObservableObject to automatically publish changes. Combine subscribers receive notifications on each update. @State is local SwiftUI view state. SwiftUI automatically redraws the view when @State changes. @Binding creates a two-way connection between a parent and child view without owning the data.

swift
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)")
    }
}

Custom Property Wrappers are created using @propertyWrapper and a struct with a wrappedValue field. This allows reusing logic: validation, normalization, UserDefaults storage. Property Wrappers significantly reduce boilerplate code and make developer intent explicit through annotation.

swift
@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 — value automatically clamped

Generics and Protocol Oriented Programming

Generics is a powerful Swift mechanism for writing flexible code without losing type safety. Generic functions and types work with any type that meets constraints. Protocol Oriented Programming (POP) is a Swift paradigm where protocol + extension replace classical class inheritance, providing behavior composition.

Generic Constraints and Associated Types

Swift allows constraining generic parameters with protocols (where clauses). Protocol with associated type is the generic equivalent for protocols: the concrete type is determined at conforming time. This is the foundation for type-safe collections and algorithms. Swift's standard library actively uses generics: Array, Dictionary, Optional — all are generic types.

swift
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 — one generic method for all Equatable types

Protocol Extension and Default Implementations

Protocol extension is a key POP mechanism. An extension can provide default implementations for protocol methods. Types conforming to the protocol get this implementation for free and can override it if needed. This allows building behavior hierarchies without class inheritance — an alternative to multiple inheritance, which is not available in Swift.

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" — default

let plane = Plane()
plane.drive() // "Flying at 900.0 km/h" — overridden

POP with extension enables composition through multiple protocols — a type can conform to several protocols, getting their implementations. This is more flexible than single class inheritance where the entire hierarchy is fixed. Swift's standard library is built on POP: CustomStringConvertible, Equatable, Hashable, Codable — all are protocols with extensions.

Enum with associated values and Extension

Enum in Swift is a significantly more powerful construct than in other languages. Each case can have associated values — an arbitrary set of data of any type. Combined with extension and pattern matching, enum becomes the foundation for modeling states, operation results, and finite state machines.

Associated Values and Pattern Matching

Associated values allow each case to store unique data. For example, the loading case contains no data, success contains the result, failure contains an error. Pattern matching via switch extracts associated values and handles each case. The compiler checks exhaustiveness (exhaustive switch) — this eliminates forgotten states.

swift
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("Loading...")
    case .success(let data):
        print("Received: \(data)")
    case .failure(let error):
        print("Error: \(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 \(duration)s")
}

Extension for Enum

Swift allows adding methods, computed properties, and protocol conformance via extensions for enums. This does not require changing the original declaration — code remains clean and modular. Methods in extensions can work with associated values, compute derived data, and implement business logic.

swift
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 "Image \(w)x\(h)"
        case .video(_, let d):
            return "Video \(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) }
// "Video 120.0s"

Enum with associated values + extension is the replacement for sealed classes from Kotlin or discriminated unions from other languages. Swift uses this combination for Result (a standard type in the standard library), Optional (Optional is an enum with .none and .some) and state handling in SwiftUI. Pattern matching guarantees all states are handled — runtime errors are impossible.

Frequently Asked Questions

How does struct differ from class in Swift?

Struct — value type, copied on assignment, does not support inheritance. Class — reference type, passed by reference, supports inheritance, deinit and identity check (===). Apple recommends starting with struct and switching to class only when reference semantics are needed.

What is Actor in Swift and when to use it?

Actor — thread-safe reference type. The compiler prevents concurrent access to mutable state from different contexts. Use Actor for state managers, caches, and services accessed by multiple tasks. Calling actor methods requires await.

Which Property Wrappers are used in SwiftUI?

Main ones: @State — local view state; @Binding — data connection without ownership; @Published — publishes changes in ObservableObject; @StateObject — owns ObservableObject; @EnvironmentObject — dependency from environment. Custom wrappers are created via @propertyWrapper.

What is Protocol Oriented Programming?

POP — a paradigm where protocol + extension define behavior instead of class inheritance. A type conforms to multiple protocols, getting their implementations for free. This is composition over inheritance — more flexible and safer. Swift's standard library (Equatable, Codable) is built on POP.

How is enum in Swift different from enum in other languages?

Enum in Swift supports associated values — each case can store arbitrary data of different types. Combined with pattern matching (switch), this provides a powerful model for states, errors, and Domain-Driven Design. Optional and Result are standard Swift enums with associated values.

Summary

  • Struct vs Class — fundamental difference: value types are copied, reference types are passed by reference. Struct does not support inheritance, class does. Apple recommends struct by default.
  • Actor — compiler-level thread safety. Isolated mutable state, access via await, protection from data races without manual locks.
  • Property Wrappers — @Published, @State, @Binding and custom via @propertyWrapper. Reduce boilerplate code and make intent explicit through annotation.
  • Generics — type-safe functions and types with where constraints. Protocol + associated type for abstractions. Swift's standard library is entirely built on generics.
  • Protocol Oriented Programming — behavior composition via protocol extension. Default implementations allow code reuse without class inheritance.
  • Enum with associated values — a powerful model for states. Each case stores unique data. Pattern matching guarantees all states are handled.
  • Extension — adds methods, computed properties and protocol conformance for any types, including enums. Code remains modular and clean without changing the original declaration.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project