Die 25 wichtigsten Swift-Interviewfragen für iOS-Entwickler
Vorbereitung auf iOS-Vorstellungsgespräche mit den 25 häufigsten Swift-Fragen: Optionals, Closures, ARC, Protokolle, async/await und fortgeschrittene Muster.

iOS-Vorstellungsgespräche prüfen Swift-Grundlagen und fortgeschrittene Konzepte gründlich ab. Diese 25 Fragen decken die Themen ab, die von Personalverantwortlichen am häufigsten gestellt werden — von Sprachgrundlagen bis hin zu modernen Concurrency-Mustern.
Jede Frage enthält eine ausführliche Antwort mit Codebeispielen. Die Fragen sind nach zunehmendem Schwierigkeitsgrad geordnet, von Grundlagen bis zu fortgeschrittenen Konzepten.
Swift-Grundlagen
1. Was ist der Unterschied zwischen let und var?
let deklariert eine Konstante, deren Wert nach der Initialisierung nicht mehr geändert werden kann, während var eine veränderbare Variable deklariert. Bei Referenztypen (Klassen) verhindert let die Neuzuweisung der Referenz, nicht aber die Änderung des Objekts selbst.
// 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 forbiddenBest Practice: Standardmäßig let verwenden und nur dann auf var wechseln, wenn eine Mutation erforderlich ist. Das macht den Code vorhersehbarer und leichter verständlich.
2. Was sind Optionals in Swift?
Optionals repräsentieren das mögliche Fehlen eines Werts. Ein Optional kann entweder einen Wert des angegebenen Typs oder nil enthalten. Swift nutzt Optionals, um Sicherheit zur Kompilierzeit zu gewährleisten, indem die explizite Behandlung von Fällen erzwungen wird, in denen ein Wert fehlen könnte.
// 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 nil3. Was ist der Unterschied zwischen struct und class?
struct sind Werttypen (werden bei Zuweisung kopiert), während class Referenztypen sind (teilen dieselbe Instanz). Dieser grundlegende Unterschied beeinflusst Leistung, Speicherverwaltung und Codeverhalten.
// 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!Wann welches verwenden:
- Struct: Einfache Daten, unveränderliche Werte, keine Vererbung nötig
- Class: Identität ist wichtig, Vererbung erforderlich, gemeinsames Verhalten
4. Wie funktioniert Pattern Matching mit switch?
Swifts switch-Anweisung ist leistungsstark und exhaustiv: Alle möglichen Fälle müssen abgedeckt sein. Sie unterstützt Pattern Matching auf Typen, Bereiche, Tupel und zusätzliche Bedingungen mit where.
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. Closures und ihre Syntax erklärt
Closures sind eigenständige Codeblöcke, die Referenzen auf Variablen aus ihrem umgebenden Kontext erfassen und speichern. Sie entsprechen Lambdas oder anonymen Funktionen in anderen Sprachen.
// 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 callsClosures erfassen Variablen standardmäßig per Referenz. Für die Erfassung per Wert wird eine Capture-Liste verwendet: { [count] in ... }.
Speicherverwaltung und ARC
6. Wie funktioniert ARC (Automatic Reference Counting)?
ARC verwaltet den Speicher automatisch, indem starke Referenzen auf jede Klasseninstanz gezählt werden. Wenn der Zähler auf null fällt, wird die Instanz freigegeben. Im Gegensatz zur Garbage Collection ist ARC deterministisch und vorhersehbar.
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. Was ist ein Retain Cycle und wie kann man ihn verhindern?
Ein Retain Cycle entsteht, wenn zwei Objekte starke Referenzen aufeinander halten, was deren Freigabe verhindert. Die Schlüsselwörter weak und unowned durchbrechen diese Zyklen.
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 // ✅ Deallocated8. Was ist der Unterschied zwischen weak und unowned?
Beide durchbrechen Retain Cycles, jedoch mit unterschiedlichen Garantien. weak ist optional und wird zu nil, wenn das referenzierte Objekt freigegeben wird. unowned geht davon aus, dass das Objekt immer existiert und stürzt ab, wenn nach der Freigabe darauf zugegriffen wird.
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 crashFaustregel: Standardmäßig weak verwenden. unowned nur dann einsetzen, wenn die Lebensdauer des referenzierten Objekts garantiert gleich lang oder länger ist.
Bereit für deine iOS-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
Protokolle und Generics
9. Protokolle in Swift erklärt
Protokolle definieren einen Vertrag (erforderliche Eigenschaften und Methoden), den konforme Typen implementieren müssen. Sie ermöglichen Polymorphie und sind die Grundlage der protokollorientierten Programmierung (POP) in Swift.
// 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. Was ist ein Associated Type?
Associated Types ermöglichen es Protokollen, generische Typen zu definieren, die von konformen Typen spezifiziert werden. Dieser Mechanismus macht Protokolle wie Collection so flexibel.
// 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. Wie funktionieren Generics?
Generics ermöglichen das Schreiben von flexiblem, wiederverwendbarem Code, der mit jedem Typ funktioniert. Sie vermeiden Code-Duplizierung bei gleichzeitiger Beibehaltung der Typsicherheit zur Kompilierzeit.
// 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. Das Codable-Protokoll erklärt
Codable (ein Alias für Encodable & Decodable) ermöglicht die automatische Serialisierung von Swift-Typen in und aus Formaten wie JSON. Der Compiler generiert die Implementierung, wenn alle Eigenschaften selbst Codable sind.
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)Concurrency und async/await
13. Wie funktioniert async/await in Swift?
async/await vereinfacht asynchronen Code, indem nicht-blockierende Operationen sequenziell geschrieben werden können. Eine async-Funktion kann unterbrochen werden, ohne den Thread zu blockieren, sodass andere Aufgaben ausgeführt werden können.
// 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. Was ist ein Actor?
Actors sind Referenztypen, die ihren internen Zustand vor gleichzeitigem Zugriff schützen. Sie garantieren, dass nur eine Aufgabe gleichzeitig auf ihre veränderbaren Eigenschaften zugreifen kann, wodurch Data Races eliminiert werden.
// 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. Task und TaskGroup erklärt
Task erstellt eine Einheit asynchroner Arbeit. TaskGroup ermöglicht das parallele Ausführen mehrerer Aufgaben und das Sammeln ihrer Ergebnisse.
// 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. Wie funktioniert @MainActor?
@MainActor stellt sicher, dass Code auf dem Hauptthread ausgeführt wird. Dies ist unverzichtbar für UI-Updates, die immer auf dem Hauptthread erfolgen müssen.
// 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"
}
}Muster und Architektur
17. Das Delegate-Muster erklärt
Das Delegate-Muster ermöglicht es einem Objekt, bestimmte Verantwortlichkeiten an ein anderes Objekt zu delegieren. Es ist allgegenwärtig in UIKit (UITableViewDelegate, UITextFieldDelegate usw.).
// 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. Was ist das MVVM-Muster?
MVVM (Model-View-ViewModel) trennt die Präsentationslogik von der View. Das ViewModel stellt beobachtbare Daten bereit, die die View anzeigt, ohne Details der View zu kennen.
// 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() }
}
}Bereit für deine iOS-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
19. Dependency Injection erklärt
Dependency Injection bedeutet, die Abhängigkeiten eines Objekts von außen bereitzustellen, anstatt sie intern zu erstellen. Dies verbessert die Testbarkeit und Entkopplung.
// 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. Wie implementiert man das Singleton-Muster?
Singleton stellt sicher, dass eine Klasse nur eine einzige, global zugängliche Instanz hat. In Swift wird dafür eine statische Eigenschaft mit einem privaten Initialisierer verwendet.
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)Singletons erzeugen globalen Zustand, der das Testen und die Entkopplung erschwert. Wo möglich, sollte Dependency Injection bevorzugt werden.
Fortgeschrittene Konzepte
21. @escaping bei Closures erklärt
Eine Closure ist @escaping, wenn sie nach dem Rückkehren der Funktion, die sie empfängt, aufgerufen werden kann. Dies ist häufig bei asynchronen Callbacks und bei der Speicherung von Closures der Fall.
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. Was ist @propertyWrapper?
Property Wrappers kapseln die Speicher- und Zugriffslogik einer Eigenschaft. Sie ermöglichen die Wiederverwendung von Mustern wie Validierung, Logging oder Persistenz.
// 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) // true23. Result Builder erklärt
Result Builder ermöglichen den Aufbau komplexer Werte mit deklarativer Syntax. Dieser Mechanismus steht hinter der DSL-Syntax von SwiftUI.
// 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. Wie funktionieren some und Opaque Types?
some deklariert einen opaken Typ: Der genaue Typ ist dem Compiler bekannt, bleibt dem Aufrufer jedoch verborgen. Dies ist unverzichtbar für Protokolle mit Associated Types und ermöglicht Optimierungen.
// 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. Swift-Makros erklärt
Makros (Swift 5.9+) generieren Code zur Kompilierzeit. Sie reduzieren Boilerplate-Code bei gleichzeitiger Beibehaltung von Typsicherheit und Debugbarkeit.
// 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
}In Xcode kann per Rechtsklick auf ein Makro und "Expand Macro" der generierte Code angezeigt werden. Das ist nützlich zum Verstehen und Debuggen.
Fazit
Diese 25 Fragen decken die Grundlagen ab, die Swift-Entwickler beherrschen müssen, um in iOS-Vorstellungsgesprächen zu bestehen. Von der Speicherverwaltung mit ARC bis zu modernen Concurrency-Mustern fügt sich jedes Konzept in das Swift-Ökosystem ein.
Checkliste zur Wiederholung
- Optionals und ihre verschiedenen Unwrapping-Methoden beherrschen
- Den Unterschied zwischen Wert- und Referenztypen verstehen
- Retain Cycles mit weak/unowned erkennen und auflösen können
- async/await und Actors für Concurrency einsetzen
- Delegate-, MVVM- und Dependency-Injection-Muster implementieren
- Protokolle, Generics und Codable kennen
- Fortgeschrittene Konzepte verstehen: Property Wrappers, Result Builder, Makros
Weiterführende Ressourcen
Für vertieftes Lernen bleibt die offizielle Swift-Dokumentation die maßgebliche Referenz. Regelmäßiges Üben mit eigenen Projekten und Programmieraufgaben hilft, diese Konzepte zu festigen.
Fang an zu üben!
Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.
Tags
Teilen
Verwandte Artikel

SwiftUI: Moderne Interfaces fuer iOS entwickeln
Umfassende Anleitung zur Erstellung moderner Benutzeroberflaechen mit SwiftUI: deklarative Syntax, Komponenten, Animationen und Best Practices fuer iOS 18.

SQL fuer Datenanalysten: Fensterfunktionen, CTEs und fortgeschrittene Abfragen
Umfassender Leitfaden zu SQL-Fensterfunktionen (ROW_NUMBER, RANK, LAG, LEAD, NTILE), Common Table Expressions und fortgeschrittenen Abfragemustern wie Gaps-and-Islands. Mit vollstaendigen Codebeispielen fuer technische Interviews und die taegliche Analysearbeit.

Top 25 Data-Analytics-Interviewfragen 2026 – Mit SQL, Python und Praxisbeispielen
Die 25 häufigsten Interviewfragen für Data-Analyst-Positionen in 2026. Mit SQL-Abfragen, Python-Code, Statistikgrundlagen und Verhaltenstipps.