How We Cut iOS Live‑Stream Power Use by Tuning Animation Frame Rates
This article details how the WeChat client team identified excessive GPU usage caused by like‑animation frames in iOS video‑channel live streams and applied iOS 15's preferredFrameRateRange API along with custom animation handling to lower frame rates, reduce power consumption, and maintain user experience.
Background
Power consumption has long been a pain point in app performance optimization, especially for long‑duration live‑stream scenarios. This article presents an unconventional solution to reduce iOS video‑channel live‑stream power usage without affecting the primary user experience.
Problem
Testing revealed that live streams with like animations doubled GPU usage and caused FPS to jump to 60 fps, even though the video stream itself is only 25–30 fps. The extra frames originated from the like animation driving the render loop at a higher rate.
Normally most video‑channel streams run at ≤30 fps, so rendering at that rate is sufficient. The like animation caused the Render Server to submit duplicate frames, inflating GPU load without improving visual quality.
Knowledge Base
This section introduces basic iOS animation concepts.
Animation Types
iOS animations can be classified based on the underlying API:
UIView block animation
Driven by +[UIView animateWithDuration:delay:options:animations:completion:] . All animations inside the block are triggered synchronously.
[UIView animateWithDuration:duration
delay:0
options:option
animations:^{
view.top -= offsetY;
view.left -= offsetX;
}
completion:completion];CAAnimation
Uses the Core Animation API directly.
CABasicAnimation *ani_position = [CABasicAnimation animationWithKeyPath:@"position"];
ani_position.fromValue = @(val.position.from);
ani_position.toValue = @(val.position.to);
[view.layer addAnimation:group forKey:key];Timer
Animations driven by NSTimer/GCD or CADisplayLink . CADisplayLink is synchronized with the screen refresh rate, providing smoother results.
UIViewPropertyAnimator
Introduced in iOS 10, offers more flexible control over animation progress.
[UIViewPropertyAnimator runningPropertyAnimatorWithDuration:duration
delay:0
options:option
animations:^{
view.top -= offsetY;
view.left -= offsetX;
}
completion:completion];Animation Rendering
iOS UI updates and animations go through Core Animation and UIKit, both built on QuartzCore. UI changes are packaged into CA::Transaction and CAAnimation objects and sent to the Render Server, which finally invokes GPU APIs to draw on the screen.
App processes events (touch, display‑link timer, etc.).
App completes layout, image decode, and submits a transaction.
Render Server receives the transaction, accesses bitmap memory, and triggers GPU calls.
GPU finishes rendering and presents the frame.
The final screen update is performed by the Render Server (backboardd process).
In iOS, the Render Server (backboardd) works alongside SpringBoard to handle touch event distribution, animation submission, and UI updates. It can be observed in Time Profiler where backboardd processes image‑submission operations.
Frame‑Rate Reduction
iOS 15 introduced CAAnimation preferredFrameRateRange , which accepts minimum, maximum, and preferred frame rates. Apple recommends several frame‑rate tiers (see Figure 7).
Both CAAnimation and CADisplayLink share the same underlying driver ( CA::Display::DisplayLinkItem). By setting preferredFrameRateRange, the system can limit the number of frames submitted to the Render Server, reducing unnecessary GPU work.
Example: setContentOffset:animated
When [scrollView setContentOffset:animated:] is called, a UIScrollViewAnimation instance is created and registered with the UIUpdateCycle. The cycle decides whether to run at 120 Hz or 60 Hz based on device refresh rate and dynamic frame‑rate settings.
Each UIUpdateSequenceRun queries the animation for the current fraction of time, updates the content offset, and repeats until the target offset is reached.
Optimization Plan
Starting with iOS 15’s preferredFrameRateRange, we refactored four animation scenarios:
4.1 UIView block animation
By wrapping the original +[UIView animateWithDuration:…] call with a UIViewPropertyAnimator that sets the desired frame‑rate range, we can seamlessly lower the frame rate of any block animation.
if (@available(iOS 15.0, *)) {
setFrameRateLevel(level);
[UIViewPropertyAnimator runningPropertyAnimatorWithDuration:duration
delay:delay
options:options
animations:animations
completion:^(UIViewAnimatingPosition finalPosition) {
if (completion) { completion(YES); }
}];
clearFrameRateLevel();
} else {
if (level != MMAnimationFrameRateLevelNone) {
if (level <= MMAnimationFrameRateLevelMedium)
options |= UIViewAnimationOptionPreferredFramesPerSecond30;
}
[self animateWithDuration:duration delay:delay options:options animations:animations completion:completion];
}4.2 UIScrollView animation
Implemented a custom animation using CADisplayLink with a configured preferredFrameRateRange to drive setContentOffset: manually.
- (void)tt_contentOffset:(CGPoint)contentOffset duration:(CFTimeInterval)duration {
self.duration = duration;
self.deltaContentOffset = CGPointMinus(contentOffset, self.scrollView.contentOffset);
if (!self.displayLink) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateContentOffset:)];
if (@available(iOS 15.0, *)) {
self.displayLink.preferredFrameRateRange = CAFrameRateRangeMake(15, 24, 0);
} else {
self.displayLink.preferredFramesPerSecond = 30;
}
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
} else {
self.displayLink.paused = NO;
}
}4.3 NSTimer animation
Replaced NSTimer‑driven animations with CADisplayLink to keep animation callbacks in sync with the display refresh, avoiding extra frame submissions.
Optimization Results
Following Apple’s recommendation to lower FPS when the UI is static, we reduced the live‑stream FPS from 120 fps to 60 fps using UIViewAnimationOptionPreferredFramesPerSecond30 , and further limited block animations to 30–48 fps via UIViewPropertyAnimator . Overall GPU usage dropped 26 %–38 % and frame‑rate fell 16 % while visual quality remained unchanged.
Additional environment variables (e.g., QUARTZCORE_LOG_FILE, CA_PRINT_FRAME_INFO) were discovered to aid debugging.
setenv("QUARTZCORE_LOG_FILE", "stdout", 1) – output QuartzCore logs.
setenv("CA_PRINT_FRAME_INFO", "1", 1) – print frame info.
setenv("CA_LOG_MEMORY_USAGE", "1", 1) – log memory usage.
setenv("CA_PRINT_ANIMATIONS", "1", 1) – print animation details.
setenv("CA_ASSERT_MAIN_THREAD_TRANSACTIONS", "1", 1) – detect UI work on background threads.
Extended Discussion
For high‑frame‑rate streams we apply a subtle, lossless down‑sampling strategy: when the device overheats, we drop the 60 fps stream to 48 fps by uniformly discarding frames on the render side, achieving up to 28 % GPU reduction without perceptible visual impact.
Conclusion
By extending system interfaces and conducting thorough experiments, we implemented a complete UI‑animation frame‑rate tuning solution that:
Rapidly retrofits existing business animations with dynamic frame‑rate control.
Significantly reduces power consumption without affecting user experience.
Alleviates the workload for developers adapting to multiple iOS versions.
The solution has been widely deployed in the video‑channel live‑stream feature, delivering noticeable performance gains.
References
Optimize for variable refresh rate displays
Power down: Improve battery consumption
Explore UI animation hitches and the render loop
Demystify and eliminate hitches in the render phase
Advanced Graphics and Animations for iOS Apps
WeChat Client Technology Team
Official account of the WeChat mobile client development team, sharing development experience, cutting‑edge tech, and little‑known stories across Android, iOS, macOS, Windows Phone, and Windows.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
