Swift
05 / 05

iOS Essentials: Networking, CoreData & UIKit

Swift: iOS Essentials

URLSession & Networking

import Foundation

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

// Generic API client
struct APIClient {
    let baseURL = URL(string: "https://api.example.com")!

    func fetch<T: Decodable>(_ path: String) async throws -> T {
        let url = baseURL.appendingPathComponent(path)
        var request = URLRequest(url: url)
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

        let (data, response) = try await URLSession.shared.data(for: request)

        guard let http = response as? HTTPURLResponse,
              (200...299).contains(http.statusCode) else {
            throw URLError(.badServerResponse)
        }

        return try JSONDecoder().decode(T.self, from: data)
    }

    func post<T: Encodable, R: Decodable>(
        _ path: String,
        body: T
    ) async throws -> R {
        var request = URLRequest(url: baseURL.appendingPathComponent(path))
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try JSONEncoder().encode(body)

        let (data, _) = try await URLSession.shared.data(for: request)
        return try JSONDecoder().decode(R.self, from: data)
    }
}

CoreData

import CoreData

// PersistenceController (boilerplate for CoreData stack)
struct PersistenceController {
    static let shared = PersistenceController()

    let container: NSPersistentContainer

    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "MyApp")
        if inMemory {
            container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores { _, error in
            if let error { fatalError("CoreData error: \(error)") }
        }
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}

// Fetch & save (in SwiftUI view)
struct NoteListView: View {
    @Environment(\.managedObjectContext) private var context
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Note.createdAt, ascending: false)],
        animation: .default
    ) private var notes: FetchedResults<Note>

    var body: some View {
        List(notes) { note in
            Text(note.title ?? "")
        }
        .toolbar {
            Button("Add") { createNote() }
        }
    }

    func createNote() {
        let note = Note(context: context)
        note.id = UUID()
        note.title = "New Note"
        note.createdAt = Date()
        try? context.save()
    }
}

UIKit Essentials

import UIKit

// ViewController lifecycle
class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }

    private func setupUI() {
        view.backgroundColor = .systemBackground
        let label = UILabel()
        label.text = "Hello"
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)

        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])
    }
}

// TableView with diffable data source
class UsersViewController: UIViewController {
    enum Section { case main }

    var dataSource: UITableViewDiffableDataSource<Section, User>!
    let tableView = UITableView()

    func configureDataSource() {
        dataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, user in
            let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
            cell.textLabel?.text = user.name
            return cell
        }
    }

    func applySnapshot(users: [User]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, User>()
        snapshot.appendSections([.main])
        snapshot.appendItems(users)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

App Architecture & Best Practices

  • MVVM: View (SwiftUI/UIKit) → ViewModel (ObservableObject) → Model + Services. Most common SwiftUI pattern.

  • Dependency injection: pass services via init or environment — avoid singletons for testability

  • Combine vs async/await: prefer async/await for new code; Combine still useful for reactive chains (debounce, publishers)

  • Memory management: use [weak self] in closures to prevent retain cycles — especially in delegates and callbacks

  • UserDefaults: for small preferences. CoreData or SwiftData for structured persistent data.

  • SwiftData (iOS 17+): new macro-based ORM — @Model class replaces CoreData .xcdatamodel setup

  • App groups: share data between app and extensions (widgets, share extensions) via shared container

  • Instruments: use for profiling memory leaks (Allocations), CPU usage, and network activity

  • TestFlight: distribute betas; App Store Connect for release management

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free