Repository: airbnb/lottie-ios
File: Tests/PerformanceTests.swift (Swift)

Code before:

  // MARK: Private

  private let simpleAnimation = Animation.named(
    "loading_dots_1",
    bundle: .module,
    subdirectory: "Samples/LottieFiles")!

  private let complexAnimation = Animation.named(
    "LottieLogo2",
    bundle: .module,
    subdirectory: "Samples")!

  /// Compares initializing the given animation with both the Main Thread and Core Animation engine,
  /// and returns the ratio of how much slower the Core Animation is than the Main Thread engine
  private func compareEngineSetupPerformance(for animation: Animation, iterations: Int) -> Double {
    let mainThreadEnginePerformance = measurePerformance {
      for _ in 0 ..< iterations {
        setupAnimationView(with: animation, configuration: .init(renderingEngine: .mainThread))
      }
    }

    print("Main thread engine took \(mainThreadEnginePerformance) seconds")

    let coreAnimationEnginePerformance = measurePerformance {
      for _ in 0 ..< iterations {
        setupAnimationView(with: animation, configuration: .init(renderingEngine: .coreAnimation))
      }
    }

    print("Core Animation engine took \(coreAnimationEnginePerformance) seconds")

    let ratio = coreAnimationEnginePerformance / mainThreadEnginePerformance
    print("Core Animation engine is \(ratio)x slower than the Main Thread engine")
    return ratio
  }

  /// Compares performance of scrubbing the given animation with both the Main Thread and Core Animation engine,
  /// and returns the ratio of how much slower the Core Animation is than the Main Thread engine
  private func compareEngineScrubbingPerformance(for animation: Animation, iterations: Int) -> Double {
    let mainThreadAnimationView = setupAnimationView(with: animation, configuration: .init(renderingEngine: .mainThread))
    let mainThreadEnginePerformance = measurePerformance {
      for i in 0 ..< iterations {
        mainThreadAnimationView.currentProgress = Double(i) / Double(iterations)

        // Since the main thread engine only re-renders in `display()`, which is normally called by Core Animation,
        // we have to invoke it manually. This triggers the same code path that would happen when scrubbing in
        // a real use case.
        mainThreadAnimationView.animationLayer!.display()
      }
    }

Review comment (suggestion):
```suggestion
      for _ in 0..<iterations {
```

Code after:
    let ratio = compareEngineScrubbingPerformance(for: complexAnimation, iterations: 2000)
    XCTAssertEqual(ratio, 0.0005, accuracy: 0.00035)
  }

  override func setUp() {
    TestHelpers.performanceTestsAreRunning = true
  }

  override func tearDown() {
    TestHelpers.performanceTestsAreRunning = false
  }

  // MARK: Private

  private let simpleAnimation = Animation.named(
    "loading_dots_1",
    bundle: .module,
    subdirectory: "Samples/LottieFiles")!

  private let complexAnimation = Animation.named(
    "LottieLogo2",
    bundle: .module,
    subdirectory: "Samples")!

  /// Compares initializing the given animation with both the Main Thread and Core Animation engine,
  /// and returns the ratio of how much slower the Core Animation is than the Main Thread engine
  private func compareEngineSetupPerformance(for animation: Animation, iterations: Int) -> Double {
    let mainThreadEnginePerformance = measurePerformance {
      for _ in 0..<iterations {
        // Each animation setup needs to be wrapped in its own `CATransaction`
        // in order for the layers to be deallocated immediately. Otherwise
        // the layers aren't deallocated until the end of the test run,
        // which causes memory usage to grow unbounded.
        CATransaction.begin()
        setupAnimationView(with: animation, configuration: .init(renderingEngine: .mainThread))
        CATransaction.commit()
      }
    }

    print("Main thread engine took \(mainThreadEnginePerformance) seconds")

    let coreAnimationEnginePerformance = measurePerformance {
      for _ in 0..<iterations {
        CATransaction.begin()
        setupAnimationView(with: animation, configuration: .init(renderingEngine: .coreAnimation))
        CATransaction.commit()
      }
    }

    print("Core Animation engine took \(coreAnimationEnginePerformance) seconds")