Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit ae00c28

Browse files
authored
Migrate vsync_waiter_ios to ARC (#52104)
Smart pointers support ARC as of #47612, and the unit tests were migrated in #48162. Migrate `vsync_waiter_ios` from MRC to ARC. Part of flutter/flutter#137801. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 35b7f73 commit ae00c28

File tree

6 files changed

+70
-71
lines changed

6 files changed

+70
-71
lines changed

shell/platform/darwin/ios/BUILD.gn

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ source_set("flutter_framework_source_arc") {
8282
"framework/Source/KeyCodeMap_Internal.h",
8383
"framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h",
8484
"framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.mm",
85+
"framework/Source/vsync_waiter_ios.h",
86+
"framework/Source/vsync_waiter_ios.mm",
8587
]
8688

8789
frameworks = [
@@ -90,8 +92,10 @@ source_set("flutter_framework_source_arc") {
9092
]
9193

9294
deps += [
95+
"//flutter/common:common",
9396
"//flutter/lib/ui",
9497
"//flutter/runtime",
98+
"//flutter/shell/common",
9599
"//flutter/shell/platform/embedder:embedder_as_internal_library",
96100
]
97101
}
@@ -149,8 +153,6 @@ source_set("flutter_framework_source") {
149153
"framework/Source/platform_message_response_darwin.mm",
150154
"framework/Source/profiler_metrics_ios.h",
151155
"framework/Source/profiler_metrics_ios.mm",
152-
"framework/Source/vsync_waiter_ios.h",
153-
"framework/Source/vsync_waiter_ios.mm",
154156
"ios_context.h",
155157
"ios_context.mm",
156158
"ios_context_metal_impeller.h",

shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ - (instancetype)init {
176176
_availableTextures = [[NSMutableSet alloc] init];
177177

178178
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)];
179-
[self setMaxRefreshRate:[DisplayLinkManager displayRefreshRate] forceMax:NO];
179+
[self setMaxRefreshRate:DisplayLinkManager.displayRefreshRate forceMax:NO];
180180
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
181181
[[NSNotificationCenter defaultCenter] addObserver:self
182182
selector:@selector(didEnterBackground:)
@@ -214,7 +214,7 @@ - (void)onDisplayLink:(CADisplayLink*)link {
214214
if (_displayLinkPauseCountdown == 3) {
215215
_displayLink.paused = YES;
216216
if (_displayLinkForcedMaxRate) {
217-
[self setMaxRefreshRate:[DisplayLinkManager displayRefreshRate] forceMax:NO];
217+
[self setMaxRefreshRate:DisplayLinkManager.displayRefreshRate forceMax:NO];
218218
_displayLinkForcedMaxRate = NO;
219219
}
220220
} else {
@@ -395,7 +395,7 @@ - (void)presentOnMainThread:(FlutterTexture*)texture {
395395
_didSetContentsDuringThisDisplayLinkPeriod = YES;
396396
} else if (!_displayLinkForcedMaxRate) {
397397
_displayLinkForcedMaxRate = YES;
398-
[self setMaxRefreshRate:[DisplayLinkManager displayRefreshRate] forceMax:YES];
398+
[self setMaxRefreshRate:DisplayLinkManager.displayRefreshRate forceMax:YES];
399399
}
400400
}
401401

shell/platform/darwin/ios/framework/Source/FlutterViewController.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1321,7 +1321,7 @@ - (void)createTouchRateCorrectionVSyncClientIfNeeded {
13211321
return;
13221322
}
13231323

1324-
double displayRefreshRate = [DisplayLinkManager displayRefreshRate];
1324+
double displayRefreshRate = DisplayLinkManager.displayRefreshRate;
13251325
const double epsilon = 0.1;
13261326
if (displayRefreshRate < 60.0 + epsilon) { // displayRefreshRate <= 60.0
13271327

shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,20 @@ - (void)testAwaitAndPauseWillWorkCorrectly {
127127
XCTAssertTrue(link.isPaused);
128128
}
129129

130+
- (void)testReleasesLinkOnInvalidation {
131+
__weak CADisplayLink* weakLink;
132+
@autoreleasepool {
133+
auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
134+
VSyncClient* vsyncClient = [[VSyncClient alloc]
135+
initWithTaskRunner:thread_task_runner
136+
callback:[](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {}];
137+
138+
weakLink = [vsyncClient getDisplayLink];
139+
XCTAssertNotNil(weakLink);
140+
[vsyncClient invalidate];
141+
}
142+
// VSyncClient has released the CADisplayLink.
143+
XCTAssertNil(weakLink);
144+
}
145+
130146
@end

shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
#include <QuartzCore/CADisplayLink.h>
99

1010
#include "flutter/fml/macros.h"
11-
#include "flutter/fml/memory/weak_ptr.h"
12-
#include "flutter/fml/platform/darwin/scoped_nsobject.h"
1311
#include "flutter/shell/common/variable_refresh_rate_reporter.h"
1412
#include "flutter/shell/common/vsync_waiter.h"
1513

@@ -31,7 +29,7 @@
3129
///
3230
/// @return The refresh rate in frames per second.
3331
///
34-
+ (double)displayRefreshRate;
32+
@property(class, nonatomic, readonly) double displayRefreshRate;
3533

3634
@end
3735

@@ -54,10 +52,11 @@
5452

5553
- (void)pause;
5654

55+
//------------------------------------------------------------------------------
56+
/// @brief Call invalidate before releasing this object to remove from runloops.
57+
///
5758
- (void)invalidate;
5859

59-
- (double)getRefreshRate;
60-
6160
- (void)setMaxRefreshRate:(double)refreshRate;
6261

6362
@end
@@ -73,15 +72,12 @@ class VsyncWaiterIOS final : public VsyncWaiter, public VariableRefreshRateRepor
7372
// |VariableRefreshRateReporter|
7473
double GetRefreshRate() const override;
7574

76-
// Made public for testing.
77-
fml::scoped_nsobject<VSyncClient> GetVsyncClient() const;
78-
7975
// |VsyncWaiter|
8076
// Made public for testing.
8177
void AwaitVSync() override;
8278

8379
private:
84-
fml::scoped_nsobject<VSyncClient> client_;
80+
VSyncClient* client_;
8581
double max_refresh_rate_;
8682

8783
FML_DISALLOW_COPY_AND_ASSIGN(VsyncWaiterIOS);

shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm

Lines changed: 41 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
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.
1923
const static double kRefreshRateDiffToIgnore = 0.1;
2024

@@ -27,63 +31,55 @@
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

3639
VsyncWaiterIOS::~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

4245
void 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|
5255
double 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

Comments
 (0)