You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
This lesson covers two essential topics: how Swift handles errors safely using throw/catch, and how Swift's structured concurrency model enables safe asynchronous and parallel code with async/await and actors.
Errors in Swift conform to the Error protocol. Enums are the most common way to define error types:
enum NetworkError: Error {
case badURL
case requestFailed(statusCode: Int)
case decodingFailed
case noData
}
enum ValidationError: Error {
case tooShort(minimum: Int)
case tooLong(maximum: Int)
case invalidFormat(String)
}
Functions that can fail are marked with throws:
func fetchUser(id: Int) throws -> User {
guard id > 0 else {
throw ValidationError.invalidFormat("ID must be positive")
}
guard let data = loadData(for: id) else {
throw NetworkError.noData
}
return try decode(data)
}
Use do-catch to handle thrown errors:
do {
let user = try fetchUser(id: 42)
print("Found user: \(user.name)")
} catch NetworkError.noData {
print("No data available")
} catch NetworkError.requestFailed(let statusCode) {
print("Request failed with status \(statusCode)")
} catch {
print("Unexpected error: \(error)")
}
// try? converts the result to an optional (nil on error)
let user = try? fetchUser(id: 42) // Optional<User>
// try! force unwraps — crashes if an error is thrown
let user = try! fetchUser(id: 42) // User (or crash)
Best Practice: Use
try?when you want to ignore the specific error. Avoidtry!unless you are certain the call cannot fail.
Execute cleanup code regardless of how a scope exits:
func processFile(at path: String) throws {
let file = try openFile(path)
defer {
closeFile(file) // always executed when scope exits
}
let data = try readData(from: file)
try process(data)
// closeFile is called here, even if an error was thrown
}
For cases where you want to handle success and failure explicitly:
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
func fetchData(from url: String) -> Result<Data, NetworkError> {
guard url.hasPrefix("https") else {
return .failure(.badURL)
}
// ... perform fetch
return .success(data)
}
switch fetchData(from: "https://api.example.com") {
case .success(let data):
print("Received \(data.count) bytes")
case .failure(let error):
print("Failed: \(error)")
}
Swift's structured concurrency model uses async and await:
func fetchUserProfile(id: Int) async throws -> UserProfile {
let userData = try await fetchUser(id: id)
let avatar = try await fetchAvatar(for: userData)
return UserProfile(user: userData, avatar: avatar)
}
// From another async context
let profile = try await fetchUserProfile(id: 42)
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.