Swift Application Performance Optimization for Beginners

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why Performance Matters from Day One



When you start writing in Swift, it's easy to focus only on functionality: "as long as it works." However, experienced developers know: performance optimization is not a final touch, but a foundation that is laid from the first lines of code. A slow application loses users, drains the battery, and gets bad reviews.



In this article, we will break down the key aspects of optimizing Swift applications for iOS and macOS. You will learn how to avoid typical beginner mistakes, learn to work with memory, threads, and profiling. Each section contains practical code examples that you can immediately apply in your projects.



1. Memory Management: ARC and Avoiding Retain Cycles



Swift uses Automatic Reference Counting (ARC) for memory management. This means objects are released when there are no more strong references to them. But if you create a cyclic reference (retain cycle), memory will never be freed — a leak will occur.



Weak and Unowned References

Use weak and unowned to break cycles. This is most often needed in closures and delegates.



class ViewController: UIViewController {    var networkManager: NetworkManager?        func fetchData() {        networkManager?.fetch { [weak self] result in            guard let self = self else { return }            // Handle the result            self.updateUI(with: result)        }    }}


When to use:

  • weak — if the reference can become nil (e.g., delegate).
  • unowned — if you are sure the object will outlive the closure (e.g., parent controller).


Avoid Unnecessary Copies

Value types (structs, enums) are copied upon assignment. For large data, use class or pass by reference using inout.



// Bad: copying a large arrayfunc processLargeArray(_ array: [Int]) {    // array — a copy}

// Good: passing by referencefunc processLargeArray(_ array: inout [Int]) { // array — a reference to the original}


2. Working with Multithreading: GCD and async/await



iOS and macOS are multithreaded systems. If you perform heavy operations (data loading, image processing) on the main thread, the interface "freezes." Use Grand Central Dispatch (GCD) or the modern async/await.



GCD: Basic Patterns

DispatchQueue.global(qos: .userInitiated).async {    // Heavy work in the background    let result = performHeavyCalculation()        DispatchQueue.main.async {        // Update UI on the main thread        self.label.text = "\\(result)"    }}


QoS (Quality of Service):

  • .userInteractive — for UI (almost instant).
  • .userInitiated — for user actions (seconds).
  • .utility — for long-running tasks (progress bars).
  • .background — for background synchronization (not urgent).


Async/await (Swift 5.5+)

A more readable way to work with asynchrony:

func fetchImage(from url: URL) async throws -> UIImage {    let (data, _) = try await URLSession.shared.data(from: url)    guard let image = UIImage(data: data) else {        throw URLError(.badServerResponse)    }    return image}

// Call in TaskTask { do { let image = try await fetchImage(from: url) self.imageView.image = image } catch { print("Download error: \\(er

Blogs

Book Recommendations