Course Lessons
Lesson 4 of 4
Networking and APIs in iOS
Learn to fetch data from REST APIs, handle asynchronous operations with async/await, and integrate web services into your iOS applications.
38 minutes
Networking and APIs in iOS
Modern iOS apps rely on web services and APIs to fetch and update data. Learn to build robust networking layers in your applications.
URLSession
Apple's native networking framework:
- Built-in HTTP client
- Support for all HTTP methods (GET, POST, PUT, DELETE)
- Background downloads and uploads
- Certificate pinning for security
Async/Await
Swift's modern concurrency model makes asynchronous code clean:
func fetchUser() async throws -> User {
let url = URL(string: "https://api.example.com/user")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
RESTful API Integration
Common patterns for working with REST APIs:
- GET: Retrieve data from server
- POST: Create new resources
- PUT/PATCH: Update existing resources
- DELETE: Remove resources
Error Handling
Robust networking requires proper error handling:
- Network connectivity issues
- Server errors (4xx, 5xx status codes)
- Invalid response data
- Timeout scenarios
JSON Decoding
Swift's Codable makes API responses type-safe:
struct ApiResponse: Codable {
let data: [Item]
let success: Bool
let message: String?
}
Best Practices
- Use async/await for cleaner asynchronous code
- Implement proper error handling
- Cache responses when appropriate
- Handle loading and error states in UI
- Use environment-specific API endpoints
- Implement request retry logic
- Add authentication headers securely
- Test with mock data during development
Code Example
import SwiftUI
// Data Models
struct Post: Identifiable, Codable {
let id: Int
let title: String
let body: String
let userId: Int
}
// Network Service
class NetworkService {
static let shared = NetworkService()
private let baseURL = "https://jsonplaceholder.typicode.com"
func fetchPosts() async throws -> [Post] {
guard let url = URL(string: "\(baseURL)/posts") else {
throw NetworkError.invalidURL
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw NetworkError.serverError
}
let posts = try JSONDecoder().decode([Post].self, from: data)
return posts
}
func createPost(title: String, body: String) async throws -> Post {
guard let url = URL(string: "\(baseURL)/posts") else {
throw NetworkError.invalidURL
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let postData = ["title": title, "body": body, "userId": 1] as [String : Any]
request.httpBody = try JSONSerialization.data(withJSONObject: postData)
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(Post.self, from: data)
}
}
enum NetworkError: Error {
case invalidURL
case serverError
case decodingError
}
// ViewModel
class PostsViewModel: ObservableObject {
@Published var posts: [Post] = []
@Published var isLoading = false
@Published var errorMessage: String?
func loadPosts() async {
isLoading = true
errorMessage = nil
do {
posts = try await NetworkService.shared.fetchPosts()
} catch {
errorMessage = "Failed to load posts: \(error.localizedDescription)"
}
isLoading = false
}
}
// SwiftUI View
struct PostsListView: View {
@StateObject private var viewModel = PostsViewModel()
var body: some View {
NavigationView {
Group {
if viewModel.isLoading {
ProgressView("Loading posts...")
} else if let error = viewModel.errorMessage {
VStack {
Text(error)
.foregroundColor(.red)
Button("Retry") {
Task {
await viewModel.loadPosts()
}
}
}
} else {
List(viewModel.posts) { post in
VStack(alignment: .leading, spacing: 8) {
Text(post.title)
.font(.headline)
Text(post.body)
.font(.subheadline)
.foregroundColor(.gray)
.lineLimit(2)
}
}
}
}
.navigationTitle("Posts")
.task {
await viewModel.loadPosts()
}
}
}
}