Sending GET and POST Requests Using URLSession
Course: iOS App Development
Topic: Sending GET and POST Requests Using URLSession
Course Link: iOS App Development Training
Demo Link: Register for a Course Demo
Website: JustAcademy
1. Introduction
Modern iOS applications communicate with remote servers to retrieve information, submit forms, authenticate users, and synchronize data. For example, a shopping app retrieves products, a news app downloads articles, and a registration screen sends user information to a backend server.
In Swift, Apple's URLSession API provides functionality for sending HTTP requests and receiving server responses. Two of the most commonly used HTTP methods are GET and POST.
- GET: Retrieves information from a server.
- POST: Sends information to a server for processing or resource creation.
URLSession works with URLRequest, HTTPURLResponse, JSONEncoder, and JSONDecoder to support networking operations in iOS applications.
2. Learning Objectives
- Understand HTTP requests and responses.
- Learn the difference between GET and POST methods.
- Configure requests using URLRequest.
- Send asynchronous requests using URLSession.
- Retrieve JSON data and decode it into Swift models.
- Encode Swift objects into JSON for POST requests.
- Handle HTTP status codes and networking errors.
- Use async/await and completion handlers.
- Apply networking security and debugging best practices.
- Build a practical API communication feature for an iOS app.
3. Understanding HTTP and REST APIs
HTTP stands for Hypertext Transfer Protocol. It defines how clients and servers communicate. An iOS app acts as the client, while a remote server receives requests and returns responses.
A REST API commonly exposes resources through URLs and uses HTTP methods to identify the intended operation.
| HTTP Method |
Purpose |
Example |
| GET |
Retrieve data |
Fetch a list of users. |
| POST |
Submit data or create a resource |
Create a new user. |
| PUT |
Replace a resource |
Replace a profile record. |
| PATCH |
Update selected fields |
Change a user's display name. |
| DELETE |
Delete a resource |
Remove a saved record. |
4. What Is URLSession?
URLSession is a Foundation networking API that coordinates data transfers between an application and a remote server. It supports HTTP and HTTPS requests, response handling, file transfers, and asynchronous operations.
Apple provides a shared session for common networking tasks. Custom sessions can be configured when an application needs specific connection, caching, delegate, or background-transfer behavior.
| Component |
Description |
URL |
Represents an endpoint address. |
URLRequest |
Defines the URL, HTTP method, headers, and optional body. |
URLSession |
Executes networking tasks. |
URLSession.shared |
Provides a shared session for straightforward requests. |
URLResponse |
Contains general response information. |
HTTPURLResponse |
Provides HTTP status codes and response headers. |
JSONEncoder |
Converts Swift values into JSON data. |
JSONDecoder |
Converts JSON data into Swift models. |
Reference: Apple Developer Documentation — URLSession.
5. Understanding GET Requests
A GET request retrieves data from a server without intending to change its state. It is commonly used to fetch user profiles, product lists, articles, weather information, and other resources.
Step 1: Import Foundation
import Foundation
The Foundation framework provides URLSession, URLRequest, URL, JSONDecoder, and other networking-related types.
Step 2: Create a Model
Suppose an API returns an article with an identifier, title, and body. Define a Swift model that conforms to Decodable.
struct Article: Decodable {
let id: Int
let title: String
let body: String
}
Step 3: Send a GET Request
The following example retrieves articles from a public sample API.
import Foundation
func fetchArticles() async throws -> [Article] {
guard let url = URL(
string: "https://jsonplaceholder.typicode.com/posts"
) else {
throw URLError(.badURL)
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(
"application/json",
forHTTPHeaderField: "Accept"
)
let (data, response) = try await URLSession.shared.data(
for: request
)
guard let httpResponse = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
guard (200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode([Article].self, from: data)
}
How the GET Request Works
- The URL identifies the API endpoint.
- URLRequest defines the request configuration.
- The HTTP method is set to GET.
- The Accept header indicates that JSON is the preferred response format.
- URLSession sends the request asynchronously.
- The HTTP response status is checked.
- JSONDecoder converts the received data into an array of Article objects.
6. Using Query Parameters in GET Requests
Query parameters provide additional information to an API, such as search terms, filtering options, sorting rules, and pagination values.
import Foundation
func createSearchURL(query: String, page: Int) throws -> URL {
guard var components = URLComponents(
string: "https://api.example.com/articles"
) else {
throw URLError(.badURL)
}
components.queryItems = [
URLQueryItem(name: "q", value: query),
URLQueryItem(name: "page", value: String(page)),
URLQueryItem(name: "limit", value: "20")
]
guard let url = components.url else {
throw URLError(.badURL)
}
return url
}
Using URLComponents and URLQueryItem is preferable to manually concatenating query strings because the APIs handle URL encoding.
Common Uses of Query Parameters
- Search for products or articles.
- Filter results by category or status.
- Request a particular page of results.
- Sort information by name, date, or price.
Do not put passwords or sensitive access tokens in URLs. Query strings may be recorded in logs or other diagnostic systems.
7. Understanding POST Requests
A POST request sends information to a server. It is often used for registration forms, creating records, submitting feedback, sending comments, or performing server-side operations.
Depending on the API, a POST request may contain JSON, form-encoded values, or another supported payload format. Always check the backend API documentation before choosing the body format.
Step 1: Create a Request Model
struct NewArticle: Encodable {
let title: String
let body: String
let userId: Int
}
The Encodable protocol allows JSONEncoder to convert the model into a JSON payload.
Step 2: Configure the POST Request
import Foundation
func createArticle() async throws -> Article {
guard let url = URL(
string: "https://jsonplaceholder.typicode.com/posts"
) else {
throw URLError(.badURL)
}
let newArticle = NewArticle(
title: "Learning URLSession",
body: "This article was submitted from an iOS application.",
userId: 1
)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(
"application/json",
forHTTPHeaderField: "Content-Type"
)
request.setValue(
"application/json",
forHTTPHeaderField: "Accept"
)
request.httpBody = try JSONEncoder().encode(newArticle)
let (data, response) = try await URLSession.shared.data(
for: request
)
guard let httpResponse = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
guard (200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode(Article.self, from: data)
}
This example uses a public testing API. Such APIs may simulate creation rather than permanently saving submitted data. A production endpoint may return a different response model or no response body.
Important POST Request Properties
| Property |
Purpose |
httpMethod |
Specifies POST as the HTTP method. |
Content-Type |
Identifies the request body format. |
Accept |
Indicates the preferred response format. |
httpBody |
Contains the encoded request data. |
JSONEncoder |
Serializes the Swift model into JSON. |
8. Difference Between GET and POST
| Feature |
GET |
POST |
| Primary purpose |
Retrieve information |
Submit or process information |
| Typical input |
URL path and query parameters |
Often a request body |
| Request body |
Usually not needed |
Commonly used |
| Typical result |
Requested resource or list |
Created resource or operation result |
| Caching |
May be cached according to HTTP rules |
Not generally cached by default |
| Idempotency |
Defined as idempotent by HTTP semantics |
Not necessarily idempotent |
| Example |
Load a user profile |
Submit a registration form |
POST is not automatically secure simply because data is sent in the request body. Use HTTPS and follow appropriate authentication and data-protection practices.
9. Understanding HTTP Status Codes
An HTTP status code describes the outcome of an HTTP request. A network operation can complete successfully while the server still returns an HTTP error, so the status code must be checked separately.
| Status Code |
Meaning |
Handling |
| 200 OK |
Request succeeded |
Process the returned data. |
| 201 Created |
A resource was created |
Read the resource or its identifier. |
| 204 No Content |
Request succeeded without a response body |
Do not decode an empty body as JSON. |
| 400 Bad Request |
Invalid request |
Validate the submitted fields. |
| 401 Unauthorized |
Authentication failed or is missing |
Check credentials or token handling. |
| 403 Forbidden |
Operation is not permitted |
Check permissions. |
| 404 Not Found |
Resource or endpoint was not found |
Verify the URL. |
| 429 Too Many Requests |
Rate limit reached |
Follow server retry guidance. |
| 500 Internal Server Error |
Server-side failure |
Display a suitable message. |
10. Creating a Reusable Network Error Type
A custom error type makes it easier to distinguish HTTP failures from invalid responses and other networking problems.
enum NetworkError: Error {
case invalidURL
case invalidResponse
case httpStatus(Int)
}
func validateResponse(_ response: URLResponse) throws {
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
guard (200...299).contains(httpResponse.statusCode) else {
throw NetworkError.httpStatus(httpResponse.statusCode)
}
}
The validation function can be reused in multiple networking methods to keep error handling consistent.
11. Handling Networking Errors
Requests may fail because of poor connectivity, invalid URLs, server timeouts, authentication problems, or unexpected response formats. Use do-catch blocks to handle thrown errors.
func loadArticlesSafely() async {
do {
let articles = try await fetchArticles()
print("Articles received: \(articles.count)")
} catch {
print("Unable to load articles: \(error.localizedDescription)")
}
}
In a production application, display a user-friendly message such as “Unable to load articles. Please try again.” Avoid exposing raw technical errors to users.
12. Using URLSession with Completion Handlers
Completion handlers are another way to perform asynchronous requests. They are useful when maintaining older code or integrating APIs that use callback-based patterns.
func fetchSingleArticle() {
guard let url = URL(
string: "https://jsonplaceholder.typicode.com/posts/1"
) else {
print("Invalid URL")
return
}
URLSession.shared.dataTask(with: url) {
data, response, error in
if let error = error {
print("Network error: \(error.localizedDescription)")
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode),
let data = data else {
print("Invalid or unsuccessful response")
return
}
do {
let article = try JSONDecoder().decode(
Article.self,
from: data
)
print(article.title)
} catch {
print("Decoding failed: \(error)")
}
}.resume()
}
The resume() method starts the data task. A task created with a completion handler does not begin executing until it is resumed.
13. Updating the User Interface
Network requests should not block the main thread. When displaying results in UIKit, update UI components on the main thread or main actor. In SwiftUI, a view model can manage the fetched data and loading state.
Task {
do {
let articles = try await fetchArticles()
await MainActor.run {
print("Ready to display \(articles.count) articles")
// Update your UI or data source here.
}
} catch {
await MainActor.run {
print("Could not load articles")
// Show a user-friendly error state.
}
}
}
A well-designed interface should include loading, success, empty, and error states. Separate networking logic from UI layout to make the application easier to test and maintain.
14. Adding Authorization Headers
Some APIs require an access token or another authentication mechanism. A bearer-token API commonly expects an Authorization header.
var request = URLRequest(url: endpoint)
request.httpMethod = "GET"
request.setValue(
"Bearer \(accessToken)",
forHTTPHeaderField: "Authorization"
)
Use tokens obtained from a valid authentication flow. Avoid hard-coding production credentials, committing tokens to source control, or logging sensitive information.
15. Configuring Request Timeouts
A custom URLSessionConfiguration lets developers set timeout behavior and other networking options.
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 60
let session = URLSession(configuration: configuration)
These values are examples. Choose appropriate timeouts based on the endpoint, expected response size, and user experience.
16. Common Problems and Troubleshooting
| Problem |
Possible Cause |
Solution |
| Invalid URL |
Malformed endpoint |
Validate the URL and inspect URL construction. |
| Request does not start |
Task was not resumed |
Call resume(). |
| 401 response |
Missing or invalid credentials |
Check authentication requirements. |
| 404 response |
Incorrect endpoint |
Verify the API route and resource identifier. |
| JSON decoding fails |
Model does not match the response |
Inspect the JSON structure and adjust the model. |
| Empty response body |
Endpoint returns no content |
Handle 204 responses without decoding JSON. |
| POST body rejected |
Incorrect content type or missing fields |
Check the API contract and payload. |
| App Transport Security error |
Insecure connection or TLS configuration issue |
Use a properly configured HTTPS endpoint. |
| UI does not refresh |
UI state is not updated correctly |
Update UI state on the main actor. |
17. Best Practices
- Prefer HTTPS for network communication.
- Validate URLs before sending requests.
- Use URLComponents for query parameters.
- Use Codable models for JSON serialization and deserialization.
- Check HTTP status codes instead of assuming every completed request succeeded.
- Separate networking code into a service or repository layer.
- Use async/await for readable asynchronous code where suitable.
- Provide clear loading, empty, and error states.
- Protect tokens and sensitive user information.
- Retry requests only when appropriate, particularly for POST operations.
- Test offline conditions, timeouts, invalid payloads, and server failures.
- Follow the backend documentation for headers, authentication, and response formats.
18. Practical Project: Fetch and Submit Articles
Build a small iOS application that retrieves articles from an API and submits a new article through a form.
Project Requirements
- Create an Article model that conforms to Decodable.
- Create a NewArticle model that conforms to Encodable.
- Implement a GET method to retrieve articles.
- Display articles in a UITableView or SwiftUI List.
- Add text fields for a new article's title and body.
- Validate required fields before submission.
- Implement a POST request that sends JSON.
- Show a loading indicator while the request is in progress.
- Display success or failure feedback.
- Refresh the displayed list after creation if the backend supports it.
Suggested Project Structure
IOSNetworkingApp/
├── Models/
│ ├── Article.swift
│ └── NewArticle.swift
├── Services/
│ └── ArticleService.swift
├── ViewModels/
│ └── ArticleViewModel.swift
└── Views/
├── ArticleListView.swift
└── CreateArticleView.swift
19. Practice Exercises
- Fetch a single article using its identifier.
- Retrieve a list of users and decode the JSON response.
- Add search parameters to a GET request.
- Submit a JSON payload using POST.
- Display a suitable message for a 404 response.
- Handle an endpoint that returns 204 No Content.
- Add an authorization header using a test token.
- Move networking code into a reusable service.
- Test the app without an internet connection.
- Write unit tests for response validation and JSON decoding.
20. Interview Questions and Answers
Q1. What is URLSession?
URLSession is a Foundation API for performing network data transfers, including sending HTTP requests and receiving responses.
Q2. What is the difference between GET and POST?
GET is primarily used to retrieve information, while POST is commonly used to submit data or request that the server create or process a resource.
Q3. What is URLRequest?
URLRequest defines request information such as the URL, method, headers, and optional body.
Q4. What is JSONDecoder?
JSONDecoder converts JSON data into Swift types that conform to Decodable.
Q5. What is JSONEncoder?
JSONEncoder converts Swift values that conform to Encodable into JSON data.
Q6. What is the Content-Type header?
It identifies the media type of the request body. For JSON payloads, a common value is application/json.
Q7. What is the Accept header?
It indicates the response format the client prefers, such as JSON.
Q8. Why should HTTP status codes be checked?
A network transfer may complete even when the server returns an HTTP error. Status validation helps the app distinguish successful responses from failures.
Q9. What is async/await?
Swift concurrency syntax that makes asynchronous code easier to read. An awaited network operation suspends the current task while waiting for a result rather than blocking the thread.
Q10. Is POST always secure?
No. Use HTTPS, appropriate authentication, and safe handling of sensitive data.
Q11. Why use URLComponents?
URLComponents provides a structured way to construct URLs and add query parameters with appropriate encoding.
Q12. Should POST requests always be retried after failure?
No. The server may have processed the original request even if the response was lost. Follow the API's retry and idempotency rules to avoid duplicate operations.
21. Quick Revision Checklist
- Understand GET and POST request purposes.
- Construct URLs and URLRequest objects correctly.
- Configure HTTP methods and headers.
- Use URLSession to send asynchronous requests.
- Encode request bodies with JSONEncoder when JSON is required.
- Decode responses using JSONDecoder.
- Validate HTTP status codes.
- Handle network, server, and decoding errors.
- Update UI state on the main actor when required.
- Follow HTTPS and credential-security practices.
22. Conclusion
Sending GET and POST requests with URLSession is a fundamental skill in iOS development. GET requests allow applications to retrieve remote information, while POST requests submit data for server-side processing. URLRequest configures each request, JSONEncoder prepares JSON payloads, and JSONDecoder converts responses into Swift models.
Reliable networking requires more than sending a request. Developers must validate HTTP responses, handle errors, protect sensitive information, and present useful feedback to users. Practising these concepts with a test API builds a foundation for developing real-world iOS applications.
23. Additional Learning Resources