Top 25 pytań rekrutacyjnych ze Swift dla programistów iOS

Przygotowanie do rozmów rekrutacyjnych na stanowisko iOS: 25 najczęściej zadawanych pytań ze Swift dotyczących optionali, closures, ARC, protokołów, async/await i zaawansowanych wzorców.

Pytania rekrutacyjne ze Swift dla programistów iOS

Rozmowy kwalifikacyjne na stanowisko iOS gruntownie weryfikują zarówno podstawy, jak i zaawansowane zagadnienia języka Swift. Poniższe 25 pytań obejmuje tematy najczęściej poruszane przez rekruterów — od fundamentów języka po nowoczesne wzorce współbieżności.

Jak korzystać z tego przewodnika

Każde pytanie zawiera szczegółową odpowiedź z przykładami kodu. Pytania są uporządkowane według rosnącego poziomu trudności — od podstaw do zagadnień zaawansowanych.

Podstawy Swift

1. Jaka jest różnica między let a var?

let deklaruje stałą, której wartość nie może być zmieniona po inicjalizacji, natomiast var deklaruje zmienną, którą można modyfikować. W przypadku typów referencyjnych (klas) let uniemożliwia ponowne przypisanie referencji, ale nie blokuje modyfikacji samego obiektu.

Constants.swiftswift
// let = constant, immutable value
let maximumAttempts = 3
// maximumAttempts = 5  // ❌ Compile error

// var = variable, mutable value
var currentAttempt = 0
currentAttempt += 1  // ✅ OK

// Careful with reference types
class User {
    var name: String
    init(name: String) { self.name = name }
}

let user = User(name: "Alice")
user.name = "Bob"  // ✅ OK - modifying object, not reference
// user = User(name: "Charlie")  // ❌ Error - reassignment forbidden

Dobra praktyka: domyślnie należy stosować let i przełączać się na var wyłącznie wtedy, gdy mutacja jest niezbędna. Takie podejście sprawia, że kod jest bardziej przewidywalny i łatwiejszy do analizy.

2. Czym są optionale w Swift?

Optionale reprezentują możliwy brak wartości. Optional może zawierać wartość danego typu albo nil. Swift wykorzystuje optionale, aby zagwarantować bezpieczeństwo na etapie kompilacji poprzez wymuszenie jawnej obsługi przypadków, gdy wartość może nie istnieć.

Optionals.swiftswift
// Declaring an optional with ?
var username: String? = nil  // Can hold a String or nil

// Optional binding with if let (safe unwrapping)
if let name = username {
    print("Hello, \(name)")  // Only executes if username != nil
} else {
    print("Anonymous user")
}

// Guard let for early return
func greet(user: String?) {
    guard let name = user else {
        print("No user provided")
        return  // Early exit if nil
    }
    print("Hello, \(name)")  // name is guaranteed non-nil here
}

// Nil-coalescing operator (??) for default value
let displayName = username ?? "Anonymous"

// Force unwrapping (!) - DANGEROUS, avoid this
// let forced = username!  // Crashes if nil

3. Jaka jest różnica między struct a class?

struct to typ wartościowy (kopiowany przy przypisaniu), natomiast class to typ referencyjny (współdzieli tę samą instancję). Ta fundamentalna różnica wpływa na wydajność, zarządzanie pamięcią i zachowanie kodu.

ValueVsReference.swiftswift
// Struct = value type (copy)
struct Point {
    var x: Int
    var y: Int
}

var p1 = Point(x: 10, y: 20)
var p2 = p1      // Independent copy
p2.x = 100       // Only modifies p2
print(p1.x)      // 10 - p1 unchanged

// Class = reference type (shared)
class Rectangle {
    var width: Int
    var height: Int
    init(width: Int, height: Int) {
        self.width = width
        self.height = height
    }
}

let r1 = Rectangle(width: 10, height: 20)
let r2 = r1      // Same shared instance
r2.width = 100   // Modifies the shared instance
print(r1.width)  // 100 - r1 also modified!

Kiedy co stosować:

  • Struct: proste dane, wartości niemutowalne, brak potrzeby dziedziczenia
  • Class: istotna jest tożsamość obiektu, wymagane dziedziczenie, współdzielone zachowanie

4. Jak działa dopasowanie wzorców w switch?

Instrukcja switch w Swift jest potężna i wyczerpująca — musi pokryć wszystkie możliwe przypadki. Obsługuje dopasowanie wzorców na typach, zakresach, krotkach oraz dodatkowe warunki za pomocą where.

PatternMatching.swiftswift
enum NetworkError: Error {
    case timeout
    case serverError(code: Int)
    case noConnection
}

func handleError(_ error: NetworkError) {
    switch error {
    case .timeout:
        print("Request timed out")

    case .serverError(let code) where code >= 500:
        print("Critical server error: \(code)")

    case .serverError(let code):
        print("Server error: \(code)")

    case .noConnection:
        print("No connection")
    }
    // No default needed: all cases covered
}

// Pattern matching on ranges and tuples
let point = (x: 5, y: 10)
switch point {
case (0, 0):
    print("Origin")
case (let x, 0):
    print("On X axis at \(x)")
case (0...10, 0...10):
    print("In the 10x10 square")
default:
    print("Elsewhere")
}

5. Czym są domknięcia (closures) i jaka jest ich składnia?

Domknięcia to samodzielne bloki kodu, które przechwytują i przechowują referencje do zmiennych z otaczającego kontekstu. Stanowią odpowiednik lambd lub funkcji anonimowych znanych z innych języków.

Closures.swiftswift
// Full syntax
let add: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
    return a + b
}

// Shorthand syntax (inferred types, implicit return)
let multiply: (Int, Int) -> Int = { $0 * $1 }

// Trailing closure syntax
let numbers = [3, 1, 4, 1, 5]
let sorted = numbers.sorted { $0 > $1 }  // [5, 4, 3, 1, 1]

// Closure capturing a variable
func makeCounter() -> () -> Int {
    var count = 0  // Captured variable
    return {
        count += 1  // Closure "closes over" count
        return count
    }
}

let counter = makeCounter()
print(counter())  // 1
print(counter())  // 2 - count remembered between calls
Semantyka przechwytywania

Domknięcia domyślnie przechwytują zmienne przez referencję. Aby przechwycić przez wartość, należy użyć listy przechwytywania: { [count] in ... }.

Zarządzanie pamięcią i ARC

6. Jak działa ARC (Automatic Reference Counting)?

ARC automatycznie zarządza pamięcią, zliczając silne referencje do każdej instancji klasy. Gdy licznik spadnie do zera, instancja zostaje zwolniona. W odróżnieniu od garbage collection, ARC jest deterministyczny i przewidywalny.

ARC.swiftswift
class Person {
    let name: String

    init(name: String) {
        self.name = name
        print("\(name) is initialized")
    }

    deinit {
        print("\(name) is deallocated")
    }
}

// Demonstrating lifecycle
var person1: Person? = Person(name: "Alice")  // refCount = 1
var person2 = person1                          // refCount = 2
person1 = nil                                  // refCount = 1 (not deallocated)
person2 = nil                                  // refCount = 0 → deinit called
// Output: "Alice is deallocated"

7. Czym jest cykl silnych referencji i jak mu zapobiegać?

Cykl silnych referencji (retain cycle) występuje, gdy dwa obiekty trzymają silne referencje do siebie nawzajem, uniemożliwiając ich zwolnienie z pamięci. Słowa kluczowe weak i unowned przerywają takie cykle.

RetainCycle.swiftswift
class Department {
    let name: String
    var manager: Employee?  // Strong reference

    init(name: String) { self.name = name }
    deinit { print("Department \(name) deallocated") }
}

class Employee {
    let name: String
    // weak prevents retain cycle - can become nil
    weak var department: Department?

    init(name: String) { self.name = name }
    deinit { print("Employee \(name) deallocated") }
}

// Without weak: retain cycle → memory leak
// With weak: proper deallocation
var dept: Department? = Department(name: "Engineering")
var emp: Employee? = Employee(name: "Bob")
dept?.manager = emp
emp?.department = dept

dept = nil  // ✅ Deallocated thanks to weak
emp = nil   // ✅ Deallocated

8. Jaka jest różnica między weak a unowned?

Oba modyfikatory przerywają cykle silnych referencji, ale różnią się gwarancjami. weak jest opcjonalny i staje się nil, gdy obiekt, do którego się odwołuje, zostanie zwolniony. unowned zakłada, że obiekt zawsze istnieje — jeśli zostanie odczytany po zwolnieniu, program się zawiesi.

WeakVsUnowned.swiftswift
class Customer {
    let name: String
    var card: CreditCard?

    init(name: String) { self.name = name }
}

class CreditCard {
    let number: String
    // unowned because a card always exists with its customer
    unowned let customer: Customer

    init(number: String, customer: Customer) {
        self.number = number
        self.customer = customer
    }
}

// Card cannot exist without customer
let customer = Customer(name: "Alice")
customer.card = CreditCard(number: "1234", customer: customer)
// If customer is deallocated, accessing card.customer would crash

Zasada: domyślnie należy stosować weak. unowned powinno być używane wyłącznie wtedy, gdy czas życia obiektu, do którego się odwołujemy, jest gwarantowany jako równy lub dłuższy.

Gotowy na rozmowy o iOS?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Protokoły i typy generyczne

9. Czym są protokoły w Swift?

Protokół definiuje kontrakt (wymagane właściwości i metody), który muszą implementować zgodne typy. Protokoły umożliwiają polimorfizm i stanowią fundament Protocol-Oriented Programming (POP) w Swift.

Protocols.swiftswift
// Protocol definition
protocol Drawable {
    var color: String { get set }  // Required property (read/write)
    func draw()                     // Required method
}

// Protocol extension with default implementation
extension Drawable {
    func draw() {
        print("Default drawing in \(color)")
    }
}

// Protocol conformance
struct Circle: Drawable {
    var color: String
    var radius: Double

    // draw() inherits default implementation
}

struct Square: Drawable {
    var color: String
    var side: Double

    // Override default implementation
    func draw() {
        print("Square \(color) with side \(side)")
    }
}

// Polymorphic usage
let shapes: [Drawable] = [Circle(color: "red", radius: 5), Square(color: "blue", side: 10)]
shapes.forEach { $0.draw() }

10. Czym jest typ stowarzyszony (associated type)?

Typy stowarzyszone pozwalają protokołom definiować typy generyczne, które zostaną określone przez typy zgodne z protokołem. Ten mechanizm sprawia, że protokoły takie jak Collection są tak elastyczne.

AssociatedTypes.swiftswift
// Protocol with associated type
protocol Container {
    associatedtype Item  // Type defined by conformant
    var items: [Item] { get set }
    mutating func add(_ item: Item)
    func count() -> Int
}

// Implementation with Item = String
struct StringBox: Container {
    typealias Item = String  // Optional, Swift can infer
    var items: [String] = []

    mutating func add(_ item: String) {
        items.append(item)
    }

    func count() -> Int { items.count }
}

// Implementation with Item = Int
struct IntStack: Container {
    var items: [Int] = []

    mutating func add(_ item: Int) {
        items.append(item)
    }

    func count() -> Int { items.count }
}

11. Jak działają typy generyczne?

Typy generyczne pozwalają pisać elastyczny, wielokrotnego użytku kod, który działa z dowolnym typem. Eliminują duplikację kodu, zachowując jednocześnie bezpieczeństwo typów na etapie kompilacji.

Generics.swiftswift
// Generic function
func swap<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

// Type constraint with where
func findIndex<T: Equatable>(of item: T, in array: [T]) -> Int? {
    for (index, element) in array.enumerated() {
        if element == item { return index }  // Equatable required for ==
    }
    return nil
}

// Generic struct
struct Queue<Element> {
    private var elements: [Element] = []

    mutating func enqueue(_ element: Element) {
        elements.append(element)
    }

    mutating func dequeue() -> Element? {
        guard !elements.isEmpty else { return nil }
        return elements.removeFirst()
    }
}

var intQueue = Queue<Int>()
intQueue.enqueue(1)
intQueue.enqueue(2)
print(intQueue.dequeue())  // Optional(1)

12. Czym jest protokół Codable?

Codable (alias dla Encodable & Decodable) umożliwia automatyczną serializację typów Swift do formatów takich jak JSON i odwrotnie. Kompilator generuje implementację automatycznie, jeśli wszystkie właściwości same są Codable.

Codable.swiftswift
struct User: Codable {
    let id: Int
    let name: String
    let email: String
    let createdAt: Date

    // CodingKeys to map different JSON names
    enum CodingKeys: String, CodingKey {
        case id
        case name
        case email
        case createdAt = "created_at"  // snake_case → camelCase
    }
}

// JSON decoding
let json = """
{
    "id": 1,
    "name": "Alice",
    "email": "alice@example.com",
    "created_at": "2026-01-15T10:30:00Z"
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

do {
    let user = try decoder.decode(User.self, from: json)
    print(user.name)  // "Alice"
} catch {
    print("Decoding error: \(error)")
}

// Encoding to JSON
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(user)

Współbieżność i async/await

13. Jak działa async/await w Swift?

async/await upraszcza kod asynchroniczny, pozwalając zapisywać operacje nieblokujące w sposób sekwencyjny. Funkcja oznaczona jako async może zostać zawieszona bez blokowania wątku, umożliwiając wykonywanie innych zadań.

AsyncAwait.swiftswift
// Asynchronous function
func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!

    // await suspends execution until response
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let httpResponse = response as? HTTPURLResponse,
          httpResponse.statusCode == 200 else {
        throw NetworkError.invalidResponse
    }

    return try JSONDecoder().decode(User.self, from: data)
}

// Calling from async context
func loadUserProfile() async {
    do {
        let user = try await fetchUser(id: 42)
        print("User: \(user.name)")
    } catch {
        print("Error: \(error)")
    }
}

// Calling from synchronous context with Task
func buttonTapped() {
    Task {
        await loadUserProfile()
    }
}

14. Czym jest Actor?

Aktorzy to typy referencyjne chroniące swój wewnętrzny stan przed jednoczesnym dostępem z wielu wątków. Gwarantują, że tylko jedno zadanie w danym momencie może uzyskać dostęp do ich mutowalnych właściwości, eliminując wyścigi danych.

Actors.swiftswift
// Actor protects its state automatically
actor BankAccount {
    private var balance: Double = 0

    func deposit(_ amount: Double) {
        balance += amount  // Automatic thread-safe access
    }

    func withdraw(_ amount: Double) -> Bool {
        guard balance >= amount else { return false }
        balance -= amount
        return true
    }

    func getBalance() -> Double {
        return balance
    }
}

// Usage - await required to access actor
let account = BankAccount()

Task {
    await account.deposit(100)
    let success = await account.withdraw(30)
    let balance = await account.getBalance()
    print("Balance: \(balance)")  // 70
}

15. Czym są Task i TaskGroup?

Task tworzy jednostkę pracy asynchronicznej. TaskGroup umożliwia uruchomienie wielu zadań równolegle i zebranie ich wyników.

TaskGroup.swiftswift
// Simple Task
let task = Task {
    return await fetchUser(id: 1)
}
let user = try await task.value

// TaskGroup for parallelization
func fetchMultipleUsers(ids: [Int]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        // Launch all requests in parallel
        for id in ids {
            group.addTask {
                try await fetchUser(id: id)
            }
        }

        // Collect results as they complete
        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

// All 3 requests run in parallel
let users = try await fetchMultipleUsers(ids: [1, 2, 3])

16. Jak działa @MainActor?

@MainActor zapewnia, że kod wykonuje się w wątku głównym. Jest to kluczowe dla aktualizacji interfejsu użytkownika, które zawsze muszą odbywać się w wątku głównym.

MainActor.swiftswift
// UI class annotated with @MainActor
@MainActor
class UserViewModel: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false
    @Published var error: String?

    func loadUser() async {
        isLoading = true  // ✅ On main thread automatically

        do {
            // Network operation on background thread
            user = try await fetchUser(id: 42)
        } catch {
            self.error = error.localizedDescription
        }

        isLoading = false  // ✅ Automatic return to main thread
    }
}

// Or for a specific function
func updateUI() async {
    await MainActor.run {
        // This block runs on main thread
        label.text = "Updated"
    }
}

Wzorce i architektura

17. Czym jest wzorzec Delegate?

Wzorzec Delegate pozwala obiektowi delegować określone obowiązki innemu obiektowi. Jest wszechobecny w UIKit (UITableViewDelegate, UITextFieldDelegate itp.).

DelegatePattern.swiftswift
// 1. Define the delegate protocol
protocol DownloadManagerDelegate: AnyObject {
    func downloadDidStart()
    func downloadDidProgress(_ progress: Double)
    func downloadDidComplete(data: Data)
    func downloadDidFail(error: Error)
}

// 2. Class that uses the delegate
class DownloadManager {
    // weak to avoid retain cycles
    weak var delegate: DownloadManagerDelegate?

    func startDownload(url: URL) {
        delegate?.downloadDidStart()

        // Simulated download
        Task {
            for progress in stride(from: 0.0, to: 1.0, by: 0.1) {
                try await Task.sleep(nanoseconds: 100_000_000)
                delegate?.downloadDidProgress(progress)
            }
            delegate?.downloadDidComplete(data: Data())
        }
    }
}

// 3. Class that implements the delegate
class ViewController: UIViewController, DownloadManagerDelegate {
    let manager = DownloadManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        manager.delegate = self  // Register as delegate
    }

    func downloadDidStart() { print("Started") }
    func downloadDidProgress(_ progress: Double) { print("\(progress * 100)%") }
    func downloadDidComplete(data: Data) { print("Complete") }
    func downloadDidFail(error: Error) { print("Error: \(error)") }
}

18. Czym jest wzorzec MVVM?

MVVM (Model-View-ViewModel) oddziela logikę prezentacji od widoku. ViewModel udostępnia obserwowalne dane, które View wyświetla, bez znajomości szczegółów implementacji widoku.

MVVM.swiftswift
// Model
struct Article: Identifiable {
    let id: UUID
    let title: String
    let content: String
    let publishedAt: Date
}

// ViewModel
@MainActor
class ArticleListViewModel: ObservableObject {
    @Published private(set) var articles: [Article] = []
    @Published private(set) var isLoading = false
    @Published var errorMessage: String?

    private let repository: ArticleRepository

    init(repository: ArticleRepository = .shared) {
        self.repository = repository
    }

    func loadArticles() async {
        isLoading = true
        errorMessage = nil

        do {
            articles = try await repository.fetchArticles()
        } catch {
            errorMessage = "Failed to load articles"
        }

        isLoading = false
    }
}

// View (SwiftUI)
struct ArticleListView: View {
    @StateObject private var viewModel = ArticleListViewModel()

    var body: some View {
        Group {
            if viewModel.isLoading {
                ProgressView()
            } else {
                List(viewModel.articles) { article in
                    Text(article.title)
                }
            }
        }
        .task { await viewModel.loadArticles() }
    }
}

Gotowy na rozmowy o iOS?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

19. Na czym polega wstrzykiwanie zależności (Dependency Injection)?

Wstrzykiwanie zależności polega na dostarczaniu zależności obiektowi z zewnątrz zamiast tworzenia ich wewnętrznie. Poprawia to testowalność i zmniejsza sprzężenie między komponentami.

DependencyInjection.swiftswift
// Protocol for abstraction
protocol UserServiceProtocol {
    func fetchUser(id: Int) async throws -> User
}

// Real implementation
class UserService: UserServiceProtocol {
    func fetchUser(id: Int) async throws -> User {
        // Real API call
        let url = URL(string: "https://api.example.com/users/\(id)")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(User.self, from: data)
    }
}

// ViewModel with injection
class ProfileViewModel: ObservableObject {
    private let userService: UserServiceProtocol

    // Constructor injection
    init(userService: UserServiceProtocol = UserService()) {
        self.userService = userService
    }

    func loadProfile(id: Int) async {
        // Uses injected service
    }
}

// Mock for testing
class MockUserService: UserServiceProtocol {
    func fetchUser(id: Int) async throws -> User {
        return User(id: id, name: "Test User", email: "test@test.com")
    }
}

// In tests
let viewModel = ProfileViewModel(userService: MockUserService())

20. Jak zaimplementować wzorzec Singleton?

Singleton zapewnia, że klasa posiada tylko jedną, globalnie dostępną instancję. W Swift realizuje się to za pomocą statycznej właściwości z prywatnym inicjalizatorem.

Singleton.swiftswift
class NetworkManager {
    // Single globally accessible instance
    static let shared = NetworkManager()

    // Private initializer prevents creating other instances
    private init() {
        // Initial configuration
    }

    private let session = URLSession.shared

    func request<T: Decodable>(_ url: URL) async throws -> T {
        let (data, _) = try await session.data(from: url)
        return try JSONDecoder().decode(T.self, from: data)
    }
}

// Usage
let user: User = try await NetworkManager.shared.request(url)
Ostrożnie z Singletonami

Singletony tworzą stan globalny, który utrudnia testowanie i rozdzielanie komponentów. Gdy to możliwe, lepszym rozwiązaniem jest wstrzykiwanie zależności.

Zagadnienia zaawansowane

21. Czym jest @escaping w kontekście domknięć?

Domknięcie jest oznaczone jako @escaping, gdy może zostać wywołane po zakończeniu funkcji, która je przyjmuje. Jest to częste w przypadku asynchronicznych callbacków oraz przechowywania domknięć.

Escaping.swiftswift
class DataLoader {
    // Storage of completion handlers
    private var completionHandlers: [() -> Void] = []

    // @escaping because closure is stored and called later
    func loadData(completion: @escaping () -> Void) {
        completionHandlers.append(completion)

        DispatchQueue.global().async {
            // Async work...
            Thread.sleep(forTimeInterval: 1)

            DispatchQueue.main.async {
                // Closure called after loadData returns
                completion()
            }
        }
    }

    // Non-escaping by default: closure called before return
    func transform(data: Data, using transformer: (Data) -> String) -> String {
        return transformer(data)  // Called immediately
    }
}

// With @escaping, watch for retain cycles
class ViewController {
    var loader = DataLoader()
    var data: String?

    func load() {
        loader.loadData { [weak self] in  // [weak self] avoids retain cycle
            self?.data = "Loaded"
        }
    }
}

22. Czym jest @propertyWrapper?

Property wrappery enkapsulują logikę przechowywania i dostępu do właściwości. Pozwalają na wielokrotne wykorzystanie wzorców takich jak walidacja, logowanie czy persystencja.

PropertyWrapper.swiftswift
// Property wrapper for positive values only
@propertyWrapper
struct Positive {
    private var value: Int = 0

    var wrappedValue: Int {
        get { value }
        set { value = max(0, newValue) }  // Force positive
    }

    // Projected value accessible via $
    var projectedValue: Bool {
        value > 0
    }

    init(wrappedValue: Int) {
        self.wrappedValue = wrappedValue
    }
}

// Usage
struct Player {
    @Positive var score: Int = 0
    @Positive var health: Int = 100
}

var player = Player()
player.score = -50   // Becomes 0 (clamped)
print(player.score)  // 0
print(player.$score) // false (projectedValue)

player.score = 100
print(player.$score) // true

23. Czym są result buildery?

Result buildery umożliwiają budowanie złożonych wartości za pomocą deklaratywnej składni. Ten mechanizm leży u podstaw składni DSL w SwiftUI.

ResultBuilder.swiftswift
// Result builder definition
@resultBuilder
struct StringBuilder {
    static func buildBlock(_ components: String...) -> String {
        components.joined(separator: " ")
    }

    static func buildOptional(_ component: String?) -> String {
        component ?? ""
    }

    static func buildEither(first component: String) -> String {
        component
    }

    static func buildEither(second component: String) -> String {
        component
    }
}

// Function using the builder
func buildGreeting(@StringBuilder _ content: () -> String) -> String {
    content()
}

// Usage with declarative syntax
let greeting = buildGreeting {
    "Hello"
    "and"
    "welcome"
    if Bool.random() {
        "!"
    } else {
        "."
    }
}
print(greeting)  // "Hello and welcome !" or "Hello and welcome ."

24. Jak działają some i typy nieprzezroczyste (opaque types)?

some deklaruje typ nieprzezroczysty: dokładny typ jest znany kompilatorowi, ale ukryty przed wywołującym. Jest to niezbędne dla protokołów z typami stowarzyszonymi i umożliwia optymalizacje.

OpaqueTypes.swiftswift
// Without some: error because Collection has associated type
// func makeCollection() -> Collection { ... }  // ❌ Error

// With some: exact type is hidden but consistent
func makeArray() -> some Collection {
    return [1, 2, 3]  // Always returns the same concrete type
}

// Used in SwiftUI for body
struct ContentView: View {
    var body: some View {  // Exact type inferred but hidden
        VStack {
            Text("Hello")
            Text("World")
        }
    }
}

// Difference with any (existential)
func processAny(_ collection: any Collection) {
    // Can accept different types, runtime overhead
}

func processSome(_ collection: some Collection) {
    // Type fixed at compile time, no overhead
}

25. Czym są makra w Swift?

Makra (Swift 5.9+) generują kod na etapie kompilacji. Redukują powtarzalny kod, zachowując jednocześnie bezpieczeństwo typów i możliwość debugowania.

Macros.swiftswift
// Freestanding macro: generates an expression
let (x, y) = #unwrap(optionalX, optionalY)
// Expands to: guard let x = optionalX, let y = optionalY else { ... }

// Attached macro: modifies a declaration
@Observable  // Macro that generates Observable boilerplate
class UserModel {
    var name: String = ""
    var email: String = ""
}
// Automatically generates @ObservationTracked, ObservationRegistrar, etc.

// Macro for Codable with customization
@Codable
struct Product {
    let id: Int
    @CodableKey("product_name") let name: String  // Renames JSON key
    @CodableIgnored var cache: Data?              // Excludes from coding
}

// Creating a custom macro
@attached(member, names: named(init))
public macro AutoInit() = #externalMacro(module: "MyMacros", type: "AutoInitMacro")

@AutoInit
struct Point {
    let x: Int
    let y: Int
    // init(x: Int, y: Int) generated automatically
}
Debugowanie makr

W Xcode wystarczy kliknąć prawym przyciskiem na makro i wybrać "Expand Macro", aby zobaczyć wygenerowany kod. Przydatne przy analizie i debugowaniu.

Podsumowanie

Te 25 pytań obejmuje fundamenty, które każdy programista Swift powinien opanować, aby odnieść sukces na rozmowach rekrutacyjnych na stanowisko iOS. Od zarządzania pamięcią z ARC po nowoczesne wzorce współbieżności — każda koncepcja jest integralną częścią ekosystemu Swift.

Lista kontrolna do powtórki

  • Opanowanie optionali i ich różnych metod rozpakowywania
  • Zrozumienie różnicy między typami wartościowymi a referencyjnymi
  • Umiejętność identyfikacji i rozwiązywania cykli silnych referencji za pomocą weak/unowned
  • Stosowanie async/await i aktorów do obsługi współbieżności
  • Implementacja wzorców Delegate, MVVM i Dependency Injection
  • Znajomość protokołów, typów generycznych i Codable
  • Zrozumienie zaawansowanych koncepcji: property wrappery, result buildery, makra

Dodatkowe zasoby

Oficjalna dokumentacja Swift pozostaje najlepszym źródłem wiedzy do pogłębiania tych zagadnień. Regularna praktyka z własnymi projektami i ćwiczeniami programistycznymi pomaga utrwalić zdobytą wiedzę.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Tagi

#swift
#ios
#interview
#career
#apple

Udostępnij

Powiązane artykuły