AppPreflight Logo
AppPreflight
loading
Back to Guides

iOS App Performance Optimization: Speed, Battery, and Memory Management

AppPreflight Team
2026-06-04
8 min read

iOS App Performance Optimization: Speed, Battery, and Memory Management

Publish Date: 2026-05-10
Last Updated: 2026-05-10
Author: AppPreflight Team

Overview

App performance directly impacts user retention and App Store rankings. Users uninstall slow, battery-draining apps. Apple also rejects apps with significant performance issues. This guide covers essential optimization techniques to ensure your app meets Apple's standards and provides excellent user experience.

1. Launch Time Optimization

Why Launch Time Matters

  • Users expect apps to launch in < 2-3 seconds
  • Long launch times cause negative reviews
  • App rejection if launch time > 20 seconds
  • Early app termination if slow startup detected

Measuring Launch Time

Xcode Profiler Method

// Add to AppDelegate
import os

fileprivate let log = OSLog(subsystem: "com.app", category: "Lifecycle")

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        os_log("App launch started", log: log, type: .info)

        // Your initialization code

        os_log("App launch completed", log: log, type: .info)
        return true
    }
}

Optimization Strategies

Lazy Initialization

// ❌ SLOW - Load everything on app start
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        DatabaseManager.shared.loadAllData()  // Slow
        ImageCache.shared.preloadImages()      // Slow
        AnalyticsManager.shared.sync()         // Slow
        return true
    }
}

// ✅ FAST - Load on demand
class DatabaseManager {
    static let shared = DatabaseManager()

    lazy var allData = {
        // Load data only when first accessed
        return loadAllData()
    }()
}

Defer Network Calls

// ❌ SLOW - Network call on app start
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    fetchUserPreferences()  // Blocks UI
    return true
}

// ✅ FAST - Network call after UI ready
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        fetchUserPreferences()  // Non-blocking
    }
    return true
}

Optimize App Startup Code

// ❌ SLOW - Complex initialization
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // Initialize 50 objects
        setupDatabaseConnections()      // 1s
        setupThirdPartyServices()       // 2s
        loadUserProfiles()              // 3s
        syncAnalytics()                 // 1s

        return true  // Total: 7s
    }
}

// ✅ FAST - Prioritized initialization
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    // Only essential for immediate UI
    setupTheme()                    // 0.1s
    setupRootViewController()       // 0.2s

    // Non-blocking background tasks
    DispatchQueue.global(qos: .utility).async {
        setupDatabaseConnections()
        setupThirdPartyServices()
        loadUserProfiles()
        syncAnalytics()
    }

    return true  // Total: 0.3s + background tasks
}

2. Memory Management

Memory Leak Detection

Using Instruments

1. Xcode → Product → Profile → Allocations
2. Watch for steadily increasing memory
3. Check for un-deallocated objects

Common Memory Leak Causes

// ❌ LEAK - Strong reference cycle
class APIManager {
    weak var delegate: APIDelegate?

    func fetchData() {
        let url = URL(string: "https://api.example.com/data")!
        URLSession.shared.dataTask(with: url) { [strong self] data, response, error in
            // self retains closure, closure retains self = leak
            self?.delegate?.didFetchData(data)
        }.resume()
    }
}

// ✅ CORRECT - Use [weak self]
func fetchData() {
    let url = URL(string: "https://api.example.com/data")!
    URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
        guard let self = self else { return }
        self.delegate?.didFetchData(data)
    }.resume()
}

ImageView Memory Optimization

// ❌ INEFFICIENT - Loads full resolution image
let image = UIImage(named: "largeImage.jpg")
imageView.image = image  // May use 10MB+ RAM

// ✅ EFFICIENT - Loads optimized image
func loadOptimizedImage(named: String, for size: CGSize) -> UIImage? {
    guard let originalImage = UIImage(named: named) else { return nil }

    let scale = UIScreen.main.scale
    let scaledSize = CGSize(
        width: size.width * scale,
        height: size.height * scale
    )

    UIGraphicsBeginImageContextWithOptions(scaledSize, false, scale)
    originalImage.draw(in: CGRect(origin: .zero, size: scaledSize))
    let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return resizedImage
}

Memory Profiling Best Practices

class MemoryMonitor {
    static func printMemoryUsage() {
        var info = task_vm_info_data_t()
        var count = mach_msg_type_number_t(MemoryLayout<task_vm_info>.size)/4

        let kerr = withUnsafeMutablePointer(to: &info) {
            $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
                task_info(
                    mach_task_self_,
                    task_flavor_t(TASK_VM_INFO),
                    $0,
                    &count
                )
            }
        }

        if kerr == KERN_SUCCESS {
            let usedMemory = Double(info.phys_footprint) / 1024 / 1024
            print("Memory Used: \(usedMemory) MB")
        }
    }
}

3. CPU Performance & Scrolling Optimization

Smooth Scrolling (60 FPS)

Avoid Blocking Main Thread

// ❌ SLOW - Blocks UI during scroll
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

    // Heavy computation on main thread
    let complexData = performHeavyComputation(indexPath.row)
    cell.configure(with: complexData)

    return cell
}

// ✅ FAST - Async background work
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

    DispatchQueue.global(qos: .userInteractive).async { [weak cell] in
        let complexData = self.performHeavyComputation(indexPath.row)

        DispatchQueue.main.async {
            cell?.configure(with: complexData)
        }
    }

    return cell
}

Optimize Cell Rendering

// ❌ SLOW - Complex layer effects
class SlowTableViewCell: UITableViewCell {
    override func layoutSubviews() {
        super.layoutSubviews()

        // Complex shadow effects
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOpacity = 0.5
        layer.shadowOffset = CGSize(width: 0, height: 10)
        layer.shadowRadius = 10
        layer.cornerRadius = 10
        layer.masksToBounds = false

        // Rasterization
        layer.shouldRasterize = true
    }
}

// ✅ FAST - Optimized rendering
class FastTableViewCell: UITableViewCell {
    override func awakeFromNib() {
        super.awakeFromNib()

        // Set shadow once, not in layoutSubviews
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOpacity = 0.5
        layer.shadowOffset = CGSize(width: 0, height: 10)
        layer.shadowRadius = 10
        layer.cornerRadius = 10
        layer.masksToBounds = false

        // Use pre-rasterized images instead of runtime rasterization
    }
}

Use CADisplayLink for Animations

class SmoothAnimationController {
    var displayLink: CADisplayLink?

    func startAnimation() {
        displayLink = CADisplayLink(
            target: self,
            selector: #selector(updateAnimation)
        )
        displayLink?.add(to: .main, forMode: .common)
    }

    @objc func updateAnimation() {
        // Called 60 times per second for smooth animation
        updateView()
    }

    func stopAnimation() {
        displayLink?.invalidate()
        displayLink = nil
    }
}

4. Battery Optimization

Background Activity Management

Use Background Tasks Wisely

import BackgroundTasks

// Request background task
func scheduleBackgroundWork() {
    let request = BGProcessingTaskRequest(identifier: "com.app.sync")
    request.requiresNetworkConnectivity = true
    request.requiresExternalPower = false

    try? BGTaskScheduler.shared.submit(request)
}

// Handle background task
func handleBackgroundSync(_ task: BGProcessingTask) {
    let queue = OperationQueue()
    queue.maxConcurrentOperationCount = 1

    let operation = SyncOperation()
    operation.completionBlock = {
        task.setTaskCompleted(success: !operation.isCancelled)
    }

    task.expirationHandler = {
        queue.cancelAllOperations()
    }

    queue.addOperation(operation)
}

Optimize Location Services

// ❌ HIGH BATTERY DRAIN - Always active
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()

// ✅ LOW BATTERY DRAIN - Use appropriate accuracy
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.startUpdatingLocation()

Network Request Optimization

// ❌ INEFFICIENT - Constant network requests
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in
    fetchUserData()  // Every 5 seconds
}

// ✅ EFFICIENT - Batch requests
Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in
    fetchUserData()      // Every 60 seconds
    fetchNotifications() // Batched together
}

5. Bundle Size Optimization

Reduce App Download Size

Strip Unused Code

Build Settings:
• Strip Linked Product: YES
• Strip Style: All Symbols
• Dead Code Stripping: YES

Image Optimization

// ❌ 2MB PNG image at 1x resolution only
let image = UIImage(named: "background.png")

// ✅ Asset Catalog with automatic optimization
// Xcode auto-generates @1x, @2x, @3x
// Xcode auto-compresses for Apple Devices

Remove Unused Dependencies

# Analyze dependencies
pod install

# Check what you actually use
# Remove pods that are not being used

Code Stripping for Frameworks

Build Settings:
• Bitcode Enabled: YES
• Other Linker Flags: -dead_strip

6. Performance Monitoring & Testing

Implement Performance Metrics

class PerformanceMonitor {
    static func measureExecutionTime(_ block: @escaping () -> Void) {
        let startTime = Date()
        block()
        let elapsed = Date().timeIntervalSince(startTime)
        print("Execution time: \(elapsed)s")
    }
}

// Usage
PerformanceMonitor.measureExecutionTime {
    fetchUserData()
}

Test on Real Devices

Do NOT rely only on simulator:

  • Simulator has unlimited RAM
  • Simulator has no battery drain
  • Simulator uses Mac's CPU (not ARM)

Test on:

  • iPhone 12 (older device with less RAM)
  • iPhone 15 Pro (latest device)
  • Multiple iOS versions
  • Poor network conditions (use Network Link Conditioner)

Performance Optimization Checklist

  • App launches in < 3 seconds
  • Scrolling maintains 60 FPS
  • No memory leaks detected
  • Memory usage < 100MB typical load
  • Background activities don't drain battery excessively
  • Images are optimized and properly sized
  • Network requests are batched
  • Main thread not blocked by heavy computation
  • App respects low power mode
  • Bundle size < 100MB
  • No unused dependencies
  • Tested on older iOS devices

Next Steps

  1. Profile your app with Xcode Instruments
  2. Identify memory leaks and performance bottlenecks
  3. Implement optimizations from this guide
  4. Test on real devices
  5. Use AppPreflight Pre-Review Tool for compliance check
  6. Monitor performance metrics in production

Great performance = Happy users + Better App Store ranking + Fewer crashes.


Was this guide helpful?