-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
60: Practice Exercise: Building a Generic Result-Based Networking Layer
Up until now, we've talked about generics and the Result type in isolation. They're powerful tools, but they really shine when you combine them to solve a recurring architectural problem. The most common one? Networking. Most apps spend a huge amount of time fetching JSON and converting it into models. If you write a separate fetch function for every single model, you're just wasting your time.
Let's build a generic network manager that can handle any Decodable type. I want a system where I can say, "Go fetch this URL and give me back a User," or "Go fetch this other URL and give me a list of Posts," all using the same piece of logic.
Defining the Failure States
Before we touch the networking code, we need to define what can actually go wrong. Using a generic Error is lazy and makes it a nightmare for the UI layer to show a helpful message. I prefer a dedicated enum.
enum NetworkError: Error {
case badURL
case requestFailed(Error)
case invalidResponse
case decodingError(Error)
}
The Generic Fetch Logic
Here is the core of the operation. I'm using a generic type T that must conform to Decodable. This tells Swift, "I don't know exactly what we're downloading yet, but I promise it's something that can be initialized from JSON."
func fetchResource<T: Decodable>(from urlString: String, completion: @escaping (Result<T, NetworkError>) -> Void) {
guard let url = URL(string: urlString) else {
completion(.failure(.badURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(.requestFailed(error)))
return
}
guard let data = data else {
completion(.failure(.invalidResponse))
return
}
do {
let decodedData = try JSONDecoder().decode(T.self, from: data)
completion(.success(decodedData))
} catch {
completion(.failure(.decodingError(error)))
}
}.resume()
}
Correcting the "Silent Failure" Mistake
I'll be honest: I've written the code above a hundred times and shipped it to production, only to realize later it had a major flaw. Look closely at the URLSession block. I'm checking if there's an error and if data exists, but I'm completely ignoring the response object.
In the real world, if a server returns a 404 (Not Found) or a 500 (Internal Server Error), URLSession does not return an error. It returns a valid response with a status code and often some HTML body explaining the error. My code above would take that 404 HTML page, try to decode it as JSON, and fail with a decodingError. That's misleading. The decoding didn't fail; the request did.
Let's fix that by casting the response to HTTPURLResponse and checking the status code:
// ... inside dataTask ...
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
completion(.failure(.invalidResponse))
return
}
guard let data = data else {
completion(.failure(.invalidResponse))
return
}
// ... proceed to decode ...
Wiring it up to a Model
Now that we have a robust, generic engine, using it is incredibly clean. We just define our model and tell the function what we expect back.
struct User: Decodable {
let id: Int
let name: String
let email: String
}
// Notice how we explicitly tell Swift we expect a 'User' here
fetchResource(from: "https://jsonplaceholder.typicode.com/users/1") { (result: Result<User, NetworkError>>) in
switch result {
case .success(let user):
print("Fetched user: \(user.name)")
case .failure(let error):
print("Error occurred: \(error)")
}
}
The beauty here is that if I want to fetch a Post or a Comment, I don't have to write any new networking logic. I just create the struct and call the same fetchResource function. We've moved the complexity into one place and left the call site clean.
📋 Practical Task
Exercise: Implementing a Generic List Fetcher for a Movie API
You are tasked with extending the networking layer to handle arrays of data. Create a small program that does the following:
- Define a
Moviestruct that conforms toDecodable(include at leasttitleandyear). - Reuse the
fetchResourcefunction from the lesson. - Call
fetchResourceusing the URL"https://jsonplaceholder.typicode.com/posts"(since we don't have a real movie API handy, pretend the "posts" endpoint returns a list of movies). - Crucial: Ensure the
Resulttype you expect in the completion handler is an array of movies ([Movie]) rather than a single movie. - Print the title of the first movie in the returned list upon success, or the specific
NetworkErrorupon failure.
There are no comments for now.