Popular Searches
Popular Course Categories
Popular Courses

Integrating a Public REST API into an iOS Application

Integrating a Public REST API into an iOS Application

Networking and REST API Integration

Integrating a Public REST API into an iOS Application

Course: iOS App Development

Topic: Integrating a Public REST API into an iOS Application

Course Link: iOS App Development Training – JustAcademy

Demo Link: Register for a Course Demo

Website: JustAcademy Official Website


1. Introduction

A public REST API allows an application to retrieve or submit information through documented web endpoints. Many public APIs provide data such as weather forecasts, books, geographical information, products, news, or sample user records.

In iOS development, integrating a public REST API helps developers build applications that display real or sample server data instead of relying entirely on hard-coded values. Swift provides the programming language, Apple's URLSession handles network communication, Codable converts JSON into Swift models, and SwiftUI displays the resulting data.

For example, a book discovery app can request a list of books from a public API, decode the returned JSON, and display book titles and authors in a SwiftUI list.

Key concepts

  • Public REST API: A web service that exposes documented endpoints accessible to external clients, sometimes with registration or API keys.
  • Endpoint: A URL identifying a resource or operation.
  • HTTP request: A message sent from the application to the server.
  • JSON response: Structured data commonly returned by web APIs.
  • URLSession: Apple's API for sending requests and receiving network data.
  • Codable: Swift support for encoding and decoding data.
  • SwiftUI: A declarative framework for building user interfaces.

2. Learning Objectives

After completing this topic, learners should be able to:

  • Understand how public REST APIs work.
  • Select an appropriate public API for an iOS project.
  • Read API documentation and identify the required endpoint.
  • Send GET requests using URLSession.
  • Decode JSON responses into Swift model objects.
  • Display API data in SwiftUI lists and detail views.
  • Handle loading indicators, empty responses, and network errors.
  • Understand API keys, rate limits, and usage restrictions.
  • Test public API integration in Xcode.
  • Apply security, accessibility, and performance best practices.

3. What Is a Public REST API?

A public REST API is a web service designed to let external applications access specified resources through HTTP endpoints. The term public does not always mean that every request is anonymous or unrestricted. Some APIs require a developer account, an API key, authentication, or acceptance of specific usage terms.

Examples of public APIs

API example Typical data Possible iOS project
Open Library API Book and author information Book discovery application
JSONPlaceholder Sample posts, users, comments, and other test records Learning and prototype application
REST Countries Country names, capitals, and other country information Country information browser
Public weather APIs Weather conditions and forecasts Weather dashboard

Before choosing an API, check its official documentation, current availability, authentication requirements, response format, rate limits, and terms of use. These details may change over time.

4. Understanding the API Request-Response Cycle

When an iOS app integrates with a public API, it sends an HTTP request and processes the server's response.

  1. The user opens a screen or performs an action.
  2. The application creates a request URL.
  3. URLSession sends the request over the network.
  4. The API server processes the request.
  5. The server returns an HTTP status code, headers, and usually a response body.
  6. The application validates the response.
  7. A JSON decoder converts the data into Swift models.
  8. The application updates its state, and SwiftUI refreshes the interface.

Example: A country browser requests country information from an API. The server returns a JSON array, Swift decodes the array into country objects, and SwiftUI displays each country in a list.

5. Selecting a Public API

Choosing an appropriate API is an important first step. A reliable integration begins with understanding what the service provides and what its usage rules allow.

API selection checklist

  • Purpose: Does the API provide the data required by your project?
  • Documentation: Are endpoint paths, parameters, and response structures clearly documented?
  • HTTPS: Does the service support secure connections?
  • Authentication: Does it require a key, token, or account?
  • Rate limits: How many requests are allowed within a given period?
  • Data format: Does the API return JSON or another format?
  • Availability: Is the service suitable for the intended use and reliability requirements?
  • Terms of use: Are there attribution, caching, or redistribution requirements?

For initial learning, a sample API can reduce setup complexity. For a production application, choose a service whose licensing, uptime, security, and data policies fit the project.

6. Understanding API Endpoints and HTTP Methods

An endpoint is a URL that identifies a resource or operation. The HTTP method indicates what the client intends to do.

Method Purpose Example
GET Retrieve resources. Fetch a list of books.
POST Submit data, often to create a resource. Submit a new record.
PUT Replace a resource representation. Replace a profile.
PATCH Partially update a resource. Change a profile field.
DELETE Request removal of a resource. Delete a saved item.

Public sample APIs often provide read-only or simulated operations. Do not assume that every method is supported or that a sample API permanently stores submitted changes.

7. Understanding JSON Responses

JSON (JavaScript Object Notation) is a common format for exchanging structured data. JSON supports objects, arrays, strings, numbers, Boolean values, and null.

Example JSON object

{
  "id": 101,
  "title": "Introduction to Swift",
  "author": "Alex Morgan",
  "published": true
}

Example JSON array

[
  {
    "id": 101,
    "title": "Introduction to Swift",
    "author": "Alex Morgan",
    "published": true
  },
  {
    "id": 102,
    "title": "Learning SwiftUI",
    "author": "Jamie Lee",
    "published": true
  }
]

The JSON structure determines how Swift models should be defined. An object usually maps to a Swift structure, while an array of objects can be decoded into an array of that structure.

8. Creating a Swift Model with Codable

Swift's Codable protocol makes it easier to convert JSON data into strongly typed values. A model should represent the fields returned by the selected endpoint.

import Foundation

struct Book: Codable, Identifiable {
    let id: Int
    let title: String
    let author: String
}

This model assumes the API returns an integer id and string values for title and author. If the actual API uses different names or data types, adapt the model accordingly.

Mapping different JSON keys

Some APIs use snake_case names such as published_year. Swift code commonly uses camelCase, so CodingKeys can map the JSON key to the Swift property.

struct BookDetails: Codable {
    let id: Int
    let publishedYear: Int

    enum CodingKeys: String, CodingKey {
        case id
        case publishedYear = "published_year"
    }
}

When an API returns optional or nullable fields, model those fields appropriately. Required non-optional properties can cause decoding to fail if the server omits them or returns an incompatible value.

9. Sending a GET Request Using URLSession

URLSession is Apple's standard API for retrieving data from web services. The following function sends a GET request to a sample books endpoint.

import Foundation

func fetchBooks() async throws -> [Book] {
    guard let url = URL(
        string: "https://api.example.com/books"
    ) else {
        throw URLError(.badURL)
    }

    let (data, response) = try await URLSession.shared.data(from: url)

    guard let httpResponse = response as? HTTPURLResponse else {
        throw URLError(.badServerResponse)
    }

    guard (200...299).contains(httpResponse.statusCode) else {
        throw URLError(.badServerResponse)
    }

    return try JSONDecoder().decode([Book].self, from: data)
}

Note: api.example.com is a placeholder, not a real books API. Replace it with an endpoint that returns JSON matching the Book model.

Step-by-step explanation

  1. Create a valid URL for the endpoint.
  2. Call URLSession.shared.data(from:) to send the request asynchronously.
  3. Check that the response is an HTTPURLResponse.
  4. Verify that the HTTP status code indicates success.
  5. Decode the returned data with JSONDecoder.
  6. Return the array of books to the caller.

10. Using a Real Public API

For a learning exercise, you can use JSONPlaceholder's sample posts endpoint. It is intended for testing and prototyping, and its response structure differs from the book model above.

Sample endpoint: https://jsonplaceholder.typicode.com/posts

Define the matching model

import Foundation

struct Post: Codable, Identifiable {
    let id: Int
    let userId: Int
    let title: String
    let body: String
}

Fetch posts

func fetchPosts() async throws -> [Post] {
    guard let url = URL(
        string: "https://jsonplaceholder.typicode.com/posts"
    ) else {
        throw URLError(.badURL)
    }

    let (data, response) = try await URLSession.shared.data(from: url)

    guard let httpResponse = response as? HTTPURLResponse else {
        throw URLError(.badServerResponse)
    }

    guard (200...299).contains(httpResponse.statusCode) else {
        throw URLError(.badServerResponse)
    }

    return try JSONDecoder().decode([Post].self, from: data)
}

This example uses a public testing endpoint. Its data is useful for learning and prototyping but should not be treated as a guaranteed production data source.

11. Creating a Reusable API Service

As applications grow, networking logic should be separated from views. A service layer centralizes requests and makes it easier to test and maintain the application.

import Foundation

enum APIError: Error {
    case invalidURL
    case invalidResponse
    case httpStatus(Int)
}

struct PostAPIService {
    func fetchPosts() async throws -> [Post] {
        guard let url = URL(
            string: "https://jsonplaceholder.typicode.com/posts"
        ) else {
            throw APIError.invalidURL
        }

        let (data, response) = try await URLSession.shared.data(from: url)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw APIError.invalidResponse
        }

        guard (200...299).contains(httpResponse.statusCode) else {
            throw APIError.httpStatus(httpResponse.statusCode)
        }

        return try JSONDecoder().decode([Post].self, from: data)
    }
}

Advantages of a service layer

  • Reduces duplicated network code.
  • Keeps view code focused on presentation.
  • Centralizes response validation and decoding.
  • Allows consistent error handling.
  • Makes it easier to substitute mock data during testing.

12. Creating a ViewModel for SwiftUI

A view model manages the data and state used by the user interface. It can store API results, track loading status, and expose a user-friendly error message.

import Foundation
import Observation

@MainActor
@Observable
final class PostViewModel {
    private let service = PostAPIService()

    var posts: [Post] = []
    var isLoading = false
    var errorMessage: String?

    func loadPosts() async {
        isLoading = true
        errorMessage = nil

        defer {
            isLoading = false
        }

        do {
            posts = try await service.fetchPosts()
        } catch {
            errorMessage = "Unable to load posts. Please try again."
        }
    }
}

Explanation:

  • @Observable makes the model's observable state available to SwiftUI.
  • @MainActor ensures UI-related state changes occur on the main actor.
  • posts stores the retrieved records.
  • isLoading tracks whether a request is in progress.
  • errorMessage contains a message suitable for display.
  • defer resets the loading state when the method exits.

This approach uses the Observation framework available in modern Apple platform versions. For older deployment targets, an ObservableObject implementation with @Published properties may be more suitable.

13. Displaying Public API Data in SwiftUI

SwiftUI can present API results using List, ForEach, Text, and other view components. A loading indicator and an error message make the interface easier to understand.

import SwiftUI

struct PostListView: View {
    @State private var viewModel = PostViewModel()

    var body: some View {
        NavigationStack {
            Group {
                if viewModel.isLoading && viewModel.posts.isEmpty {
                    ProgressView("Loading posts...")
                } else if let message = viewModel.errorMessage,
                          viewModel.posts.isEmpty {
                    VStack(spacing: 12) {
                        Text(message)
                            .multilineTextAlignment(.center)

                        Button("Try Again") {
                            Task {
                                await viewModel.loadPosts()
                            }
                        }
                    }
                    .padding()
                } else if viewModel.posts.isEmpty {
                    ContentUnavailableView(
                        "No Posts",
                        systemImage: "doc.text",
                        description: Text("No posts are available.")
                    )
                } else {
                    List(viewModel.posts) { post in
                        VStack(alignment: .leading, spacing: 8) {
                            Text(post.title)
                                .font(.headline)

                            Text(post.body)
                                .font(.body)
                                .foregroundStyle(.secondary)
                        }
                        .padding(.vertical, 4)
                    }
                }
            }
            .navigationTitle("Public Posts")
            .task {
                if viewModel.posts.isEmpty {
                    await viewModel.loadPosts()
                }
            }
            .refreshable {
                await viewModel.loadPosts()
            }
        }
    }
}

This view assumes the Post, PostAPIService, and PostViewModel types defined in the earlier examples. ContentUnavailableView is available on newer Apple platform versions; for older deployment targets, create a custom empty-state view.

14. Managing Loading, Success, Empty, and Error States

API calls can take time, return no records, or fail. A good interface communicates these states instead of leaving users with a blank screen.

State Meaning Recommended interface
Loading The request is in progress. Progress indicator or placeholder rows.
Success Data was received and processed. Display the results.
Empty The request succeeded but returned no records. Show an informative empty state.
Error The request or decoding failed. Show a friendly message and retry option.
Refreshing Existing data is being updated. Keep existing results visible when practical.

For a better user experience, preserve existing data when a refresh fails, if appropriate. This prevents a temporary network issue from unnecessarily removing useful content from the screen.

15. Working with Query Parameters

Query parameters are commonly used to search, filter, sort, or paginate API results. Use URLComponents and URLQueryItem to construct the URL safely.

import Foundation

func makePostsURL(userID: Int) throws -> URL {
    guard var components = URLComponents(
        string: "https://jsonplaceholder.typicode.com/posts"
    ) else {
        throw URLError(.badURL)
    }

    components.queryItems = [
        URLQueryItem(
            name: "userId",
            value: String(userID)
        )
    ]

    guard let url = components.url else {
        throw URLError(.badURL)
    }

    return url
}

The resulting URL requests posts associated with a specific user, using the query format supported by this sample API.

16. Sending POST Requests to a Public API

Some public testing APIs allow clients to submit sample data. A POST request commonly includes a JSON body and a Content-Type header.

import Foundation

struct NewPost: Codable {
    let title: String
    let body: String
    let userId: Int
}

func createPost() async throws {
    guard let url = URL(
        string: "https://jsonplaceholder.typicode.com/posts"
    ) else {
        throw URLError(.badURL)
    }

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue(
        "application/json; charset=UTF-8",
        forHTTPHeaderField: "Content-Type"
    )
    request.setValue(
        "application/json",
        forHTTPHeaderField: "Accept"
    )

    let newPost = NewPost(
        title: "Learning iOS Networking",
        body: "Practicing public REST API integration.",
        userId: 1
    )

    request.httpBody = try JSONEncoder().encode(newPost)

    let (_, 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 APIError.httpStatus(httpResponse.statusCode)
    }
}

Testing APIs may simulate create, update, and delete operations without permanently changing server data. Verify the service's documented behavior before relying on write operations.

17. Understanding HTTP Status Codes

HTTP status codes indicate how a server handled a request. Checking them is necessary because a completed network request does not automatically mean that the server operation succeeded.

Status code Meaning Possible application behavior
200 OK Request succeeded. Process the response body if expected.
201 Created A resource was created. Use returned resource information when available.
204 No Content Request succeeded without a response body. Do not attempt to decode a required JSON body.
400 Bad Request The request is invalid. Check parameters and submitted data.
401 Unauthorized Authentication is missing or invalid. Follow the API's authentication flow.
403 Forbidden The server refuses the operation. Check access permissions.
404 Not Found The endpoint or resource was not found. Verify the URL and resource identifier.
429 Too Many Requests A rate limit was exceeded. Respect the API's retry guidance.
500 Internal Server Error The server encountered an error. Show a useful message and retry only when appropriate.
503 Service Unavailable The service is temporarily unavailable. Consider a controlled retry.

18. API Keys and Authentication

Not every public API is available without authentication. Some services require an API key, while others use bearer tokens or a more complete authorization flow.

  • Read the provider's authentication documentation.
  • Use HTTPS when sending credentials or tokens.
  • Never commit private credentials to source control.
  • Do not assume that an API key embedded in a mobile application can remain secret; application binaries can be inspected.
  • Use server-side mediation for sensitive secrets or privileged operations when appropriate.
  • Store user-specific sensitive tokens securely, commonly using the iOS Keychain.
  • Restrict credentials according to the provider's available controls.
  • Never log access tokens, passwords, or other sensitive information.

Follow the provider's terms and security recommendations. A public endpoint should not be used to bypass authentication or access restrictions.

19. Rate Limits and API Usage Policies

API providers may limit the number of requests an application can make. Exceeding a limit can cause temporary failures or access restrictions.

Best practices

  • Read the documented request limits.
  • Avoid sending repeated requests when existing data can be reused.
  • Cache responses when the provider permits it.
  • Use pagination for large datasets if available.
  • Debounce live-search requests.
  • Respect server-provided retry instructions, including Retry-After when present.
  • Use exponential backoff for suitable transient failures.
  • Do not retry indefinitely.

When an API returns HTTP 429, the application should reduce request frequency and follow the provider's documented retry policy.

20. Handling Network and Decoding Errors

API integration can fail because of connectivity problems, timeouts, server errors, malformed JSON, or unexpected data types.

Error type Possible cause Recommended response
Invalid URL Malformed endpoint. Validate URL construction.
Connection failure Network unavailable or server unreachable. Show a helpful message and allow retry.
Timeout The response takes too long. Review timeout settings and retry carefully.
HTTP error The server returns a non-success status. Handle the status and any documented error body.
Decoding error The response does not match the model. Inspect the JSON and correct the model mapping.
Rate limit Too many requests. Slow down and respect retry guidance.
TLS error Certificate or connection security problem. Resolve the underlying security configuration issue.

Example of calling the API safely

Task {
    do {
        let posts = try await PostAPIService().fetchPosts()
        print("Retrieved \(posts.count) posts")
    } catch {
        print("Request failed: \(error.localizedDescription)")
    }
}

In production, avoid exposing internal server details directly to users. Present clear messages and keep diagnostic information in appropriately secured logs.

21. Caching and Offline Considerations

Network access is not always reliable. Thoughtful caching and offline behavior can make an application more useful when a connection is slow or unavailable.

  • Respect HTTP caching headers and the provider's caching policies.
  • Cache appropriate data locally when permitted.
  • Display previously loaded content when it remains useful.
  • Provide a refresh action to retrieve updated information.
  • Show a clear offline state when fresh data cannot be retrieved.
  • Avoid storing sensitive data without a clear requirement and suitable protection.
  • Keep cached data consistent with its expiration and freshness rules.

Do not assume every public API permits unrestricted storage or redistribution. Review its terms before caching data for long periods or making it available offline.

22. Accessibility and User Experience

An API-powered interface should remain understandable and usable while data is loading, unavailable, or being refreshed.

  • Use meaningful labels for buttons such as Retry and Refresh.
  • Support Dynamic Type and readable text sizes.
  • Provide accessible descriptions for important icons.
  • Ensure error messages explain what the user can do next.
  • Use loading indicators without unnecessarily blocking unrelated interactions.
  • Keep existing data visible during refreshes when appropriate.
  • Use clear empty-state messages instead of blank screens.
  • Test layouts with longer text and accessibility settings.

23. Testing Public API Integration

Testing verifies that the application handles both normal and unexpected responses correctly.

Testing checklist

  • Test a valid successful response.
  • Test an empty result set.
  • Test an invalid endpoint.
  • Test a non-success HTTP status code.
  • Test malformed JSON and unexpected field types.
  • Test offline and timeout conditions.
  • Test rate-limit responses when possible.
  • Verify that loading indicators stop after success or failure.
  • Verify that the SwiftUI list updates after data is loaded.
  • Use mock responses or a custom URL protocol for deterministic unit tests.

Tests should not depend unnecessarily on a live public service. Network availability, rate limits, and response data may change, so mocked responses are useful for repeatable automated tests.

24. Practical Project: Public Posts Browser

Project objective: Build a SwiftUI application that fetches sample posts from JSONPlaceholder and displays them in a list with loading and error handling.

Project requirements

  1. Create a Post model conforming to Codable and Identifiable.
  2. Use URLSession to request the posts endpoint.
  3. Validate the HTTP response and status code.
  4. Decode the JSON response into an array of posts.
  5. Create a view model to manage results and request state.
  6. Display post titles and bodies in a SwiftUI list.
  7. Add a loading indicator and an empty state.
  8. Add a retry button for failed requests.
  9. Implement pull-to-refresh.
  10. Test the application under different network conditions.

Suggested project structure

PublicPostsApp/
├── Models/
│   └── Post.swift
├── Services/
│   └── PostAPIService.swift
├── ViewModels/
│   └── PostViewModel.swift
├── Views/
│   └── PostListView.swift
└── PublicPostsApp.swift

Extension activities

  • Add a search field for post titles.
  • Filter posts by user ID.
  • Create a post detail screen.
  • Add pagination if supported by the chosen API.
  • Persist permitted data locally for offline viewing.
  • Write unit tests for the service and view model.

25. Common Interview Questions and Answers

Q1. What is a public REST API?

It is a web service that exposes documented resources or operations to external clients. It may still require registration, authentication, or an API key.

Q2. What is URLSession?

URLSession is Apple's networking API for sending HTTP requests and receiving data or transferring files.

Q3. What is Codable?

Codable combines encoding and decoding capabilities, allowing Swift types to be converted to and from supported data formats such as JSON.

Q4. Why should API responses be decoded into Swift models?

Models provide strongly typed data, improve code readability, and help catch response mismatches during decoding.

Q5. What is the purpose of a view model?

A view model manages data and state used by the interface, including loading status, results, and error messages.

Q6. Why should HTTP status codes be checked?

A request can complete but return a server error. Status-code validation helps the application distinguish successful responses from unsuccessful ones.

Q7. What is the difference between a public API and an unrestricted API?

A public API is available for external use under its rules, but it may require credentials, enforce rate limits, or restrict particular operations.

Q8. How can API keys be protected in iOS applications?

Do not treat a key embedded in a distributed app as a secret. Use provider restrictions and server-side mediation for sensitive privileged credentials where appropriate.

Q9. How should rate-limit errors be handled?

Reduce request frequency, respect retry guidance, and use controlled backoff instead of repeatedly sending requests.

Q10. How can public API integration be tested?

Test model decoding, status-code handling, loading states, and network failures using unit tests, mocked responses, or a controlled test endpoint.

26. Quick Revision Checklist

  • A public REST API exposes documented endpoints for external applications.
  • Check the endpoint, response format, authentication, and usage policies before integration.
  • Use HTTPS for secure network communication.
  • Use URLSession to perform network requests.
  • Use Codable to decode JSON into Swift models.
  • Validate the HTTP response and status code.
  • Separate network logic into a service layer.
  • Use a view model to manage data and UI state.
  • Show loading, success, empty, and error states.
  • Respect rate limits and retry instructions.
  • Follow authentication, caching, and attribution requirements.
  • Test successful responses and failure scenarios.

27. Conclusion

Integrating a public REST API into an iOS application allows developers to build dynamic applications that retrieve data from remote services. By using URLSession, Codable, Swift concurrency, a reusable API service, and SwiftUI state management, developers can fetch JSON, convert it into Swift models, and present the information in a responsive interface.

A reliable implementation should also validate HTTP status codes, handle network and decoding errors, respect authentication and rate limits, and follow the API provider's terms. Practicing with a sample posts API is an effective way to build the foundation for more advanced iOS networking projects.


Explore the iOS App Development Course: View iOS Training at JustAcademy

Register for a Course Demo: Book a Course Demo

Official Website: Visit JustAcademy

Previous Next
whatsapp