1515#include " flutter/fml/memory/task_runner_checker.h"
1616#include " flutter/fml/trace_event.h"
1717
18+ @interface VSyncClient ()
19+ @property (nonatomic , assign , readonly ) double refreshRate;
20+ @end
21+
1822// When calculating refresh rate diffrence, anything within 0.1 fps is ignored.
1923const static double kRefreshRateDiffToIgnore = 0.1 ;
2024
2731 const fml::TimePoint target_time = recorder->GetVsyncTargetTime ();
2832 FireCallback (start_time, target_time, true );
2933 };
30- client_ =
31- fml::scoped_nsobject{[[VSyncClient alloc ] initWithTaskRunner: task_runners_.GetUITaskRunner ()
32- callback: callback]};
33- max_refresh_rate_ = [DisplayLinkManager displayRefreshRate ];
34+ client_ = [[VSyncClient alloc ] initWithTaskRunner: task_runners_.GetUITaskRunner ()
35+ callback: callback];
36+ max_refresh_rate_ = DisplayLinkManager.displayRefreshRate ;
3437}
3538
3639VsyncWaiterIOS::~VsyncWaiterIOS () {
3740 // This way, we will get no more callbacks from the display link that holds a weak (non-nilling)
3841 // reference to this C++ object.
39- [client_. get () invalidate ];
42+ [client_ invalidate ];
4043}
4144
4245void VsyncWaiterIOS::AwaitVSync () {
43- double new_max_refresh_rate = [ DisplayLinkManager displayRefreshRate ] ;
46+ double new_max_refresh_rate = DisplayLinkManager. displayRefreshRate ;
4447 if (fabs (new_max_refresh_rate - max_refresh_rate_) > kRefreshRateDiffToIgnore ) {
4548 max_refresh_rate_ = new_max_refresh_rate;
46- [client_. get () setMaxRefreshRate: max_refresh_rate_];
49+ [client_ setMaxRefreshRate: max_refresh_rate_];
4750 }
48- [client_. get () await ];
51+ [client_ await ];
4952}
5053
5154// |VariableRefreshRateReporter|
5255double VsyncWaiterIOS::GetRefreshRate () const {
53- return [client_.get () getRefreshRate ];
54- }
55-
56- fml::scoped_nsobject<VSyncClient> VsyncWaiterIOS::GetVsyncClient () const {
57- return client_;
56+ return client_.refreshRate ;
5857}
5958
6059} // namespace flutter
6160
6261@implementation VSyncClient {
63- flutter::VsyncWaiter::Callback callback_;
64- fml::scoped_nsobject<CADisplayLink > display_link_;
65- double current_refresh_rate_;
62+ flutter::VsyncWaiter::Callback _callback;
63+ CADisplayLink * _displayLink;
6664}
6765
6866- (instancetype )initWithTaskRunner : (fml::RefPtr<fml::TaskRunner>)task_runner
6967 callback : (flutter::VsyncWaiter::Callback)callback {
7068 self = [super init ];
7169
7270 if (self) {
73- current_refresh_rate_ = [ DisplayLinkManager displayRefreshRate ] ;
71+ _refreshRate = DisplayLinkManager. displayRefreshRate ;
7472 _allowPauseAfterVsync = YES ;
75- callback_ = std::move (callback);
76- display_link_ = fml::scoped_nsobject<CADisplayLink > {
77- [[CADisplayLink displayLinkWithTarget: self selector: @selector (onDisplayLink: )] retain ]
78- };
79- display_link_.get ().paused = YES ;
80-
81- [self setMaxRefreshRate: [DisplayLinkManager displayRefreshRate ]];
82-
83- task_runner->PostTask ([client = [self retain ]]() {
84- [client->display_link_.get () addToRunLoop: [NSRunLoop currentRunLoop ]
85- forMode: NSRunLoopCommonModes ];
86- [client release ];
73+ _callback = std::move (callback);
74+ _displayLink = [CADisplayLink displayLinkWithTarget: self selector: @selector (onDisplayLink: )];
75+ _displayLink.paused = YES ;
76+
77+ [self setMaxRefreshRate: DisplayLinkManager.displayRefreshRate];
78+
79+ // Strongly retain the the captured link until it is added to the runloop.
80+ CADisplayLink * localDisplayLink = _displayLink;
81+ task_runner->PostTask ([localDisplayLink]() {
82+ [localDisplayLink addToRunLoop: NSRunLoop .currentRunLoop forMode: NSRunLoopCommonModes ];
8783 });
8884 }
8985
@@ -97,19 +93,19 @@ - (void)setMaxRefreshRate:(double)refreshRate {
9793 double maxFrameRate = fmax (refreshRate, 60 );
9894 double minFrameRate = fmax (maxFrameRate / 2 , 60 );
9995 if (@available (iOS 15.0 , *)) {
100- display_link_. get () .preferredFrameRateRange =
96+ _displayLink .preferredFrameRateRange =
10197 CAFrameRateRangeMake (minFrameRate, maxFrameRate, maxFrameRate);
10298 } else {
103- display_link_. get () .preferredFramesPerSecond = maxFrameRate;
99+ _displayLink .preferredFramesPerSecond = maxFrameRate;
104100 }
105101}
106102
107103- (void )await {
108- display_link_. get () .paused = NO ;
104+ _displayLink .paused = NO ;
109105}
110106
111107- (void )pause {
112- display_link_. get () .paused = YES ;
108+ _displayLink .paused = YES ;
113109}
114110
115111- (void )onDisplayLink : (CADisplayLink *)link {
@@ -126,44 +122,33 @@ - (void)onDisplayLink:(CADisplayLink*)link {
126122 std::unique_ptr<flutter::FrameTimingsRecorder> recorder =
127123 std::make_unique<flutter::FrameTimingsRecorder>();
128124
129- current_refresh_rate_ = round (1 / (frame_target_time - frame_start_time).ToSecondsF ());
125+ _refreshRate = round (1 / (frame_target_time - frame_start_time).ToSecondsF ());
130126
131127 recorder->RecordVsync (frame_start_time, frame_target_time);
132128 if (_allowPauseAfterVsync) {
133- display_link_. get () .paused = YES ;
129+ link .paused = YES ;
134130 }
135- callback_ (std::move (recorder));
131+ _callback (std::move (recorder));
136132}
137133
138134- (void )invalidate {
139- [display_link_.get () invalidate ];
140- }
141-
142- - (void )dealloc {
143- [self invalidate ];
144-
145- [super dealloc ];
146- }
147-
148- - (double )getRefreshRate {
149- return current_refresh_rate_;
135+ [_displayLink invalidate ];
136+ _displayLink = nil ; // Break retain cycle.
150137}
151138
152139- (CADisplayLink *)getDisplayLink {
153- return display_link_. get () ;
140+ return _displayLink ;
154141}
155142
156143@end
157144
158145@implementation DisplayLinkManager
159146
160147+ (double )displayRefreshRate {
161- fml::scoped_nsobject<CADisplayLink > display_link = fml::scoped_nsobject<CADisplayLink > {
162- [[CADisplayLink displayLinkWithTarget: [[[DisplayLinkManager alloc ] init ] autorelease ]
163- selector: @selector (onDisplayLink: )] retain ]
164- };
165- display_link.get ().paused = YES ;
166- auto preferredFPS = display_link.get ().preferredFramesPerSecond ;
148+ CADisplayLink * displayLink = [CADisplayLink displayLinkWithTarget: [[[self class ] alloc ] init ]
149+ selector: @selector (onDisplayLink: )];
150+ displayLink.paused = YES ;
151+ auto preferredFPS = displayLink.preferredFramesPerSecond ;
167152
168153 // From Docs:
169154 // The default value for preferredFramesPerSecond is 0. When this value is 0, the preferred
@@ -174,15 +159,15 @@ + (double)displayRefreshRate {
174159 return preferredFPS;
175160 }
176161
177- return [ UIScreen mainScreen ] .maximumFramesPerSecond ;
162+ return UIScreen. mainScreen .maximumFramesPerSecond ;
178163}
179164
180165- (void )onDisplayLink : (CADisplayLink *)link {
181166 // no-op.
182167}
183168
184169+ (BOOL )maxRefreshRateEnabledOnIPhone {
185- return [[[ NSBundle mainBundle ] objectForInfoDictionaryKey: @" CADisableMinimumFrameDurationOnPhone" ]
170+ return [[NSBundle . mainBundle objectForInfoDictionaryKey: @" CADisableMinimumFrameDurationOnPhone" ]
186171 boolValue ];
187172}
188173
0 commit comments