Skip to content

Commit 1c78e47

Browse files
sis0k0Alexander Vakrilov
authored and
Alexander Vakrilov
committed
feat(ios): fire onDisplayed event when first frame is ready to be displayed (NativeScript#5344)
* feat: add a 'profiling: lifecycle' to track startup times * feat: log when displayed event fires * feat(ios): fire onDisplayed event when first frame is ready to be displayed
1 parent 5bae124 commit 1c78e47

File tree

4 files changed

+92
-14
lines changed

4 files changed

+92
-14
lines changed

tns-core-modules/application/application-common.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
require("globals");
33

44
import { Observable, EventData } from "../data/observable";
5+
import {
6+
trace as profilingTrace,
7+
time,
8+
uptime,
9+
level as profilingLevel,
10+
} from "../profiling";
511

612
const events = new Observable();
713
let launched = false;
@@ -11,6 +17,15 @@ function setLaunched() {
1117
}
1218
events.on("launch", setLaunched);
1319

20+
if (profilingLevel() > 0) {
21+
events.on("displayed", () => {
22+
const duration = uptime();
23+
const end = time();
24+
const start = end - duration;
25+
profilingTrace(`Displayed in ${duration.toFixed(2)}ms`, start, end);
26+
});
27+
}
28+
1429
export function hasLaunched(): boolean {
1530
return launched;
1631
}

tns-core-modules/application/application.ios.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,12 @@ import { ios as iosView, View } from "../ui/core/view";
2020
import { Frame, NavigationEntry } from "../ui/frame";
2121
import { ios } from "../ui/utils";
2222
import * as utils from "../utils/utils";
23-
import { profile } from "../profiling";
23+
import { profile, level as profilingLevel, Level } from "../profiling";
2424

2525
class Responder extends UIResponder {
2626
//
2727
}
2828

29-
let displayedOnce = false;
30-
3129
class NotificationObserver extends NSObject {
3230
private _onReceiveCallback: (notification: NSNotification) => void;
3331

@@ -46,6 +44,24 @@ class NotificationObserver extends NSObject {
4644
};
4745
}
4846

47+
let displayedOnce = false;
48+
let displayedLinkTarget;
49+
let displayedLink;
50+
class CADisplayLinkTarget extends NSObject {
51+
onDisplayed(link: CADisplayLink) {
52+
link.invalidate();
53+
const ios = utils.ios.getter(UIApplication, UIApplication.sharedApplication);
54+
const object = iosApp;
55+
displayedOnce = true;
56+
notify(<ApplicationEventData>{ eventName: displayedEvent, object, ios });
57+
displayedLinkTarget = null;
58+
displayedLink = null;
59+
}
60+
public static ObjCExposedMethods = {
61+
"onDisplayed": { returns: interop.types.void, params: [CADisplayLink] }
62+
}
63+
}
64+
4965
class IOSApplication implements IOSApplicationDefinition {
5066
private _delegate: typeof UIApplicationDelegate;
5167
private _currentOrientation = utils.ios.getter(UIDevice, UIDevice.currentDevice).orientation;
@@ -101,6 +117,13 @@ class IOSApplication implements IOSApplicationDefinition {
101117

102118
@profile
103119
private didFinishLaunchingWithOptions(notification: NSNotification) {
120+
if (!displayedOnce && profilingLevel() >= Level.lifecycle) {
121+
displayedLinkTarget = CADisplayLinkTarget.new();
122+
displayedLink = CADisplayLink.displayLinkWithTargetSelector(displayedLinkTarget, "onDisplayed");
123+
displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, NSDefaultRunLoopMode);
124+
displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, UITrackingRunLoopMode);
125+
}
126+
104127
this._window = UIWindow.alloc().initWithFrame(utils.ios.getter(UIScreen, UIScreen.mainScreen).bounds);
105128
// TODO: Expose Window module so that it can we styled from XML & CSS
106129
this._window.backgroundColor = utils.ios.getter(UIColor, UIColor.whiteColor);
@@ -126,11 +149,6 @@ class IOSApplication implements IOSApplicationDefinition {
126149
if (rootView && !rootView.isLoaded) {
127150
rootView.callLoaded();
128151
}
129-
130-
if (!displayedOnce) {
131-
notify(<ApplicationEventData>{ eventName: displayedEvent, object, ios });
132-
displayedOnce = true;
133-
}
134152
}
135153

136154
private didEnterBackground(notification: NSNotification) {

tns-core-modules/profiling/profiling.d.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,25 @@ interface TimerInfo {
1111

1212
/**
1313
* Profiling mode to use.
14-
* - `counters` Accumulates method call counts and times until dumpProfiles is called and then prints agregated statistic in the console. This is the default.
14+
* - `counters` Accumulates method call counts and times until dumpProfiles is called and then prints aggregated statistic in the console. This is the default.
1515
* - `timeline` Outputs method names along start/end timestamps in the console on the go.
16+
* - `lifecycle` Outputs basic non-verbose times for startup, navigation, etc.
1617
*/
17-
type InstrumentationMode = "counters" | "timeline";
18+
type InstrumentationMode = "counters" | "timeline" | "lifecycle";
19+
20+
/**
21+
* Logging levels in order of verbosity.
22+
*/
23+
export enum Level {
24+
none,
25+
lifecycle,
26+
timeline,
27+
}
28+
29+
/**
30+
* Get the current logging level.
31+
*/
32+
export function level(): Level;
1833

1934
/**
2035
* Enables profiling.
@@ -32,8 +47,9 @@ type InstrumentationMode = "counters" | "timeline";
3247
* ```
3348
*
3449
* @param type Profiling mode to use.
35-
* - "counters" - Accumulates method call counts and times until dumpProfiles is called and then prints agregated statistic in the console. This is the default.
50+
* - "counters" - Accumulates method call counts and times until dumpProfiles is called and then prints aggregated statistic in the console. This is the default.
3651
* - "timeline" - Outputs method names along start/end timestamps in the console on the go.
52+
* - "lifecycle" - Outputs basic non-verbose times for startup, navigation, etc.
3753
*/
3854
export declare function enable(type?: InstrumentationMode): void;
3955

@@ -132,4 +148,14 @@ export function uptime(): number;
132148
/**
133149
* Logs important messages. Contrary to console.log's behavior, the profiling log should output even for release builds.
134150
*/
135-
export function log(message: string): void;
151+
export function log(message: string): void;
152+
153+
/**
154+
* Manually output profiling messages. The `@profile` decorator is useful when measuring times that function calls take on the stack
155+
* but when measuring times between longer periods (startup times, times between the navigatingTo - navigatedTo events etc.)
156+
* you can call this method and provide manually the times to be logged.
157+
* @param message A string message
158+
* @param start The start time (see `time()`)
159+
* @param end The end time (see `time()`)
160+
*/
161+
export function trace(message: string, start: number, end: number): void;

tns-core-modules/profiling/profiling.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ export function uptime() {
1010
export function log(message: string): void {
1111
if ((<any>global).__nslog) {
1212
(<any>global).__nslog("CONSOLE LOG: " + message);
13-
} else {
14-
console.log(message);
1513
}
14+
console.log(message);
1615
}
1716

1817
interface TimerInfo extends TimerInfoDefinition {
@@ -130,12 +129,24 @@ const enum MemberType {
130129
Instance
131130
}
132131

132+
export enum Level {
133+
none,
134+
lifecycle,
135+
timeline,
136+
}
137+
let tracingLevel: Level = Level.none;
138+
133139
let profileFunctionFactory: <F extends Function>(fn: F, name: string, type?: MemberType) => F;
134140
export function enable(mode: InstrumentationMode = "counters") {
135141
profileFunctionFactory = mode && {
136142
counters: countersProfileFunctionFactory,
137143
timeline: timelineProfileFunctionFactory
138144
}[mode];
145+
146+
tracingLevel = {
147+
lifecycle: Level.lifecycle,
148+
timeline: Level.timeline,
149+
}[mode] || Level.none;
139150
}
140151

141152
try {
@@ -294,3 +305,11 @@ export function stopCPUProfile(name: string) {
294305
__stopCPUProfiler(name);
295306
}
296307
}
308+
309+
export function level(): Level {
310+
return tracingLevel;
311+
}
312+
313+
export function trace(message: string, start: number, end: number): void {
314+
log(`Timeline: Modules: ${message} (${start}ms. - ${end}ms.)`);
315+
}

0 commit comments

Comments
 (0)