Writing performant Swift code is crucial for delivering smooth user experiences. Learn techniques to optimize your iOS applications.
Value Types vs Reference Types
swift
// Struct (Value Type) - Preferred for small data
struct User {
let id: String
let name: String
var age: Int
}
// Class (Reference Type) - Use for identity or large data
class DatabaseConnection {
private let connectionString: String
init(connectionString: String) {
self.connectionString = connectionString
}
}
// Copy-on-Write optimization
let user1 = User(id: "1", name: "John", age: 25)
var user2 = user1 // No copy yet
user2.age = 26 // Copy happens hereMemory Management
Avoid Retain Cycles
swift
// BAD: Retain cycle
class ViewController {
var closure: (() -> Void)?
func setup() {
closure = {
self.doSomething()
}
}
}
// GOOD: Use [weak self]
class ViewController {
var closure: (() -> Void)?
func setup() {
closure = { [weak self] in
self?.doSomething()
}
}
}Lazy Loading
swift
class DataProvider {
// Lazy loaded property
lazy var expensiveData: [Int] = {
print("Computing expensive data...")
return Array(1...1000000).map { $0 * 2 }
}()
// Lazy with computed property
private var _cachedValue: String?
var cachedValue: String {
if let cached = _cachedValue {
return cached
}
let value = computeExpensiveValue()
_cachedValue = value
return value
}
}Collection Optimization
Array Performance
swift
// Pre-allocate capacity
var items: [Int] = []
items.reserveCapacity(1000) // Avoids reallocation
// Use appropriate data structures
// Array: Random access, ordered
let array = [1, 2, 3, 4, 5]
let first = array[0] // O(1)
// Set: Fast lookup, unordered
let set: Set<Int> = [1, 2, 3, 4, 5]
let contains = set.contains(3) // O(1)
// Dictionary: Key-value pairs
let dict: [String: Int] = ["one": 1, "two": 2]
let value = dict["two"] // O(1)String Operations
swift
// BAD: String concatenation in loop
var result = ""
for i in 0..<1000 {
result += "\(i)" // Creates new string each time
}
// GOOD: Use StringBuilder pattern
var result = ""
let parts: [String] = (0..<1000).map { "\($0)" }
result = parts.joined()
// BETTER: Use reserveCapacity
var result = ""
result.reserveCapacity(5000)
for i in 0..<1000 {
result.append("\(i)")
}Algorithmic Optimization
Time Complexity
swift
// O(n²) - Nested loops
func findDuplicates(_ array: [Int]) -> [Int] {
var duplicates: [Int] = []
for i in 0..<array.count {
for j in (i+1)..<array.count {
if array[i] == array[j] {
duplicates.append(array[i])
}
}
}
return duplicates
}
// O(n) - Using Set
func findDuplicatesFast(_ array: [Int]) -> [Int] {
var seen = Set<Int>()
var duplicates: [Int] = []
for item in array {
if seen.contains(item) {
duplicates.append(item)
} else {
seen.insert(item)
}
}
return duplicates
}Concurrency for Performance
swift
class ImageProcessor {
// Process images concurrently
func processImages(_ urls: [URL]) async throws -> [Image] {
try await withThrowingTaskGroup(of: (Int, Image).self) { group in
for (index, url) in urls.enumerated() {
group.addTask {
let image = try await self.downloadAndProcess(url)
return (index, image)
}
}
// Collect results
var results: [(Int, Image)] = []
for try await (index, image) in group {
results.append((index, image))
}
// Sort by original order
return results.sorted { $0.0 < $1.0 }.map { $0.1 }
}
}
private func downloadAndProcess(_ url: URL) async throws -> Image {
// Download and process image
return Image()
}
}Profiling Tools
Instruments
bash
# Launch Instruments
# Product > Profile > Time Profiler
# Check for:
# - High CPU usage
# - Memory leaks
# - Retain cycles
# - Slow operationsMetrics in Code
swift
import os.signpost
let log = OSLog(subsystem: "com.app.myapp", category: "Performance")
func processLargeData() {
os_signpost(.begin, log: log, name: "Data Processing")
defer { os_signpost(.end, log: log, name: "Data Processing") }
// Processing code
}SwiftUI Performance
Lazy Loading
swift
// BAD: Loads all items at once
List {
ForEach(items) { item in
ItemView(item: item)
}
}
// GOOD: Lazy loads items
List {
ForEach(items) { item in
ItemView(item: item)
}
}View Optimization
swift
struct OptimizedView: View {
@State private var items: [Item] = []
var body: some View {
List {
ForEach(items) { item in
ItemCell(item: item)
}
}
.onAppear {
loadItems()
}
}
private func loadItems() {
// Load in batches
Task {
let initial = await fetchItems(count: 20)
items = initial
}
}
}
// Extract to separate view for better performance
struct ItemCell: View {
let item: Item
var body: some View {
HStack {
Text(item.title)
Text(item.description)
}
}
}Caching Strategies
swift
actor CacheManager {
private var storage: [String: Any] = [:]
func get<T>(_ key: String) -> T? {
storage[key] as? T
}
func set<T>(_ value: T, forKey key: String) {
storage[key] = value
}
func clear() {
storage.removeAll()
}
}
// Usage
let cache = CacheManager()
func getCachedData(for key: String) async -> Data? {
if let cached = await cache.get(key) as? Data {
return cached
}
let data = await fetchData(key)
await cache.set(data, forKey: key)
return data
}Best Practices
- 1Profile first: Measure before optimizing
- 2Use value types: Prefer structs for small data
- 3Avoid force unwraps: Use safe optional handling
- 4Lazy loading: Load data only when needed
- 5Caching: Cache expensive computations
- 6Concurrency: Use async/await for I/O operations
- 7Memory: Be mindful of retain cycles
- 8Algorithms: Choose appropriate data structures
Performance optimization is an ongoing process. Always measure the impact of your optimizations!