Introduction
The performance of mobile and desktop applications is a key factor influencing user experience. Users expect applications to run quickly, smoothly, and without delays. For Swift developers creating applications for the Apple ecosystem (iOS, macOS, watchOS, tvOS), performance optimization becomes not just desirable but a mandatory task. In this article, we will look at the main methods for optimizing Swift application performance, from memory management to efficient work with multithreading. You will learn how to avoid common mistakes and make your application as fast as possible.
1. Memory Management and ARC
Swift uses Automatic Reference Counting (ARC) for memory management. However, improper use of ARC can lead to memory leaks and reduced performance. The main problem is retain cycles.
1.1. Avoid Retain Cycles
Retain cycles occur when two objects reference each other, and ARC cannot deallocate their memory. Use weak or unowned to break the cycle.
class Person { let name: String var apartment: Apartment? init(name: String) { self.name = name } deinit { print("\\(name) deallocated") }}
class Apartment { let unit: String weak var tenant: Person? // weak prevents a retain cycle init(unit: String) { self.unit = unit } deinit { print("Apartment \\(unit) deallocated") }}
var john: Person? = Person(name: "John")var unit4A: Apartment? = Apartment(unit: "4A")john?.apartment = unit4Aunit4A?.tenant = johnjohn = nilunit4A = nil// Both objects will be deallocated1.2. Use Value Types (Structs) Instead of Reference Types (Classes)
Structs are stored on the stack, which is faster than heap storage for classes. Use structs for small, immutable data.
struct Point { var x: Double var y: Double}
class PointClass { var x: Double var y: Double init(x: Double, y: Double) { self.x = x self.y = y }}
// Performance testlet count = 1_000_000var start = CFAbsoluteTimeGetCurrent()var points = [Point](repeating: Point(x: 0, y: 0), count: count)for i in 0..<count { points[i].x = Double(i)}print("Structs: \\(CFAbsoluteTimeGetCurrent() - start) seconds")
start = CFAbsoluteTimeGetCurrent()var pointClasses = [PointClass]()for i in 0..<count { let p = PointClass(x: Double(i), y: 0) pointClasses.append(p)}print("Classes: \\(CFAbsoluteTimeGetCurrent() - start) seconds")2. Optimizing Work with Multithreading (GCD)
Grand Central Dispatch (GCD) is a powerful tool for executing tasks in parallel. Improper use can lead to deadlocks and reduced performance.
2.1. Use the Correct Queues
For background tasks, use global queues with Quality of Service (QoS):
.userInteractive— for tasks requiring an immediate response (e.g., animations)..userInitiated— for tasks initiated by the user (e.g., loading data)..utility— for long-running tasks (e.g., file processing)..background— for tasks that do not affect the UI (e.g., synchronization).
DispatchQueue.global(qos: .userInitiated).async { // Heavy task let result = performHeavyCalculation() DispatchQueue.main.async { // Update UI on the main thread self.updateUI(with: result) }}2.2. Avoid Blocking the Main Thread
All operations related to the UI must be performed on the main thread. Move heavy computations and network