@newpan tech iOS rookie engineer

The user experience engineer of the company mentioned the startup time to us several times before. At that time, we were developing the business, so we didn’t have time to do this. I’ve had time to fix the boot time since I recently shipped the version.

Generally speaking, startup time refers to the time between the moment the user clicks on the APP and the time the user sees the first screen. When we optimized, we divided the startup time into pre-main time and the time from the main function to the first screen rendering.

Why is that? We all know that the entry to our APP is the main function, and our own code will not be executed before main. And once we get into main, our code starts from

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
Copy the code

So obviously, the idea of optimizing these two parts is different.

For convenience, we call the pre-main time t1 time, and the time between main and the first screen rendering is called T2 time.

01. Sharpening a knife does not cut wood

Let’s start with the first part, which is the time between the main function and the rendering of the first interface. Before we get started, let’s hone a tool of our own.

In life, we usually measure time by a timer. To figure out which operations, or code, are time-consuming, we also need a timer. Those of you who have used Profile know that it is a powerful tool to analyze which code is time-consuming. But it’s not flexible enough, so let’s take a look at how we should design this timer.

As shown in the figure above, on the timeline, we timed the code from start, then we timed the code at the first red flag, and then timed the code at the second red flag. Then make a dot at the end and display the results of all the dots. At the same time, we added a label to each time period to distinguish what actions were taken during that time. This way, we can quickly and accurately know who is slowing down the startup.

02. Locate the culprit

The screenshot below shows the time it took for betchat teacher to start optimization. Because it involves the specific business of the company, I have added some information to block it. With our tools, we can locate the elapsed time of any line of code.

We see t2 time there, a total of 6.361 seconds, this is the first interface to didFinishLaunchingWithOptions renders the amount of time. Judging from this result, the optimization of our startup time has reached the point of urgency.

Again carefully analyze the result of the above, t2 time also divided into two parts, didFinishLaunchingWithOptions took 4.010 seconds, the first page rendering time spent 2.531 seconds. Well, it looks like the devil lived in didFinishLaunchingWithOptions this method, in addition, there are many problems in the first page rendering. So let’s expand it out.

02.1.didFinishLaunchingWithOptions

Speaking of the devil lived in didFinishLaunchingWithOptions above, we carefully look at the now didFinishLaunchingWithOptions time-consuming method in the code, there are two lines of code time-consuming for more than one second, And the most time-consuming is a whopping 1.620 seconds.

Actually didFinishLaunchingWithOptions method, we usually have the following logic:

  • Initialize the third-party SDK
  • Configure an environment for running the APP
  • Initialization of some of your own utility classes
  • .

02.2. Render the first page

If our UI architecture looks like this. Then we write this code in the AppDelegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(Start the "@" didFinishLaunchingWithOptions);

    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    BLTabBarController *tabBarVc = [BLTabBarController new];
    self.window.rootViewController = tabBarVc;
    [self.window makeKeyAndVisible];

    NSLog(DidFinishLaunchingWithOptions ran the "@");

    return YES;
    }
Copy the code

And then we’re going to go inside the viewDidLoad method in BLTabBarController and set its viewControllers, And then go into the viewDidLoad method of each viewController and do some more initialization. So what do you think from didFinishLaunchingWithOptions to finally show show viewController viewDidLoad execution order of these methods is what?

Here is a demo I wrote to show the loading order:

2017- 08- 15 10:46:57.860 Demo[1404:325698] didFinishLaunchingWithOptions begin execution2017- 08- 15 10:46:57.862 Demo[1404:325698] start loading viewDidLoad of BLTabBarController2017- 08- 15 10:46:57.874 Demo[1404:325698] didFinishLaunchingWithOptions ran out2017- 08- 15 10:46:57.876 Demo[1404:325698] starts loading the BLViewController's viewDidLoad, and then does a bunch of initializationCopy the code

So the above situation is to make sure that we don’t manipulate the view of BLViewController in BLTabBarController, if we manipulate the view of BLViewController in BLTabBarController, The order of calls would look like this:

2017- 08- 15 11:09:03.661 Demo[1458:349413] didFinishLaunchingWithOptions begin execution2017- 08- 15 11:09:03.663 Demo[1458:349413] start loading viewDidLoad of BLTabBarController2017- 08- 15 11:09:03.664 Demo[1458:349413] starts loading the BLViewController's viewDidLoad, and then does a bunch of initialization2017- 08- 15 11:09:03.676 Demo[1458:349413] didFinishLaunchingWithOptions ran outCopy the code

That’s a terrible thing. Why? Because we usually put interface initialization, network request, data parsing, view rendering, etc. in the viewDidLoad method, so every time we start the APP, before the user sees the first page, we have to process all of these events before we go to the view rendering phase.

03. Solution strategy

Analyses the slow t2 above two factors, they are didFinishLaunchingWithOptions initialization and first page rendering time consuming. For these two different aspects, our optimization ideas are not the same.

03.1.didFinishLaunchingWithOptions

For didFinishLaunchingWithOptions, here the initialization is must carry out, but we can be appropriate depending on the function of the corresponding appropriate delay start time. For our project, I have divided initialization into three types:

  • Logs, statistics, and other events that must be configured first when the APP moves together
  • Events such as project configuration, environment configuration, user information initialization, push, and IM
  • Other SDK and configuration events

For the first category, due to the particularity of this kind of event, so must be started in the first place, still leaves it in a didFinishLaunchingWithOptions started. The second type of events, these functions must be loaded before the user enters the main body of the APP, so we can put it in the second batch, that is, the user has seen the AD page, and then start the countdown of the AD. The third type of event, since it is not required, can be placed in the viewDidAppear method after the first interface has rendered, which has no impact on startup time at all.

Just like that, after this round of optimization, our T2 event dropped from over 6 seconds to over 2 seconds.

03.2. Render the first page

The idea is, the user clicks on the APP, and I load up the AD page as soon as possible. In this way, users will not feel that the startup is slow, and we can load the second batch of startup events in the process of advertising seconds, the user will not feel the load. But that’s not all. When the AD is done, when you cut to the main APP, if you have a bunch of time-consuming actions in a bunch of viewDidLoad methods, you’re still going to get stuck.

So for the first page rendering optimization idea is, at once to show a shell of the UI to the user, then load in viewDidAppear method for data parsing rendering and a series of operations, so that the user has seen interface, won’t feel is to start slowly, at this time of waiting for the data request, becomes So you’re transferring some of that time.

After these two rounds of optimization, our T2 time has changed from more than 6 seconds to less than 0.1 seconds, which is a total reduction of more than 6 seconds of startup time.

03.3. Summarize

To do this, I created a class that is responsible for launching events. Why? If you don’t do this, so after the optimization, then the introduction of a third party, other colleagues can be very intuitive and initialization of a third party on the didFinishLaunchingWithOptions methods, such as time passes, DidFinishLaunchingWithOptions and become overwhelmed, repeat in time and to spend time to do optimization.

Here is the header file for this class:

/** * Note: This class is responsible for loading all didFinishLaunchingWithOptions delay events. * after the introduction of the third party need in didFinishLaunchingWithOptions initialization or we need in your own classes DidFinishLaunchingWithOptions initialization, * want to consider the start time of as little as possible to bring a good user experience, so should be according to the need to reduce didFinishLaunchingWithOptions time-consuming operation. * the first category: Such as log/statistics and so on need to immediately start, still in didFinishLaunchingWithOptions. * the second category: Such as user data needs to be done in advertising display after use, so you need to accompany the advertisement page start, just need to start the code on the startupEventsOnADTimeWithAppDelegate method. * the third class: To live and share the business such as, for example, must be the user can see the main interface after real need to start, so delay to the main interface after the completion of the loading start, just need to code on the startupEventsOnDidAppearAppContent method. * /

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface BLDelayStartupTool : NSObject

/ * * * start with didFinishLaunchingWithOptions start events. * start type is: the log/statistics and so on need to immediately start. * /
+ (void)startupEventsOnAppDidFinishLaunchingWithOptions;

/** * Launch is an event that can be initialized when an AD is displayed
+ (void)startupEventsOnADTime;

/** * Start an event that can be loaded after the first screen is displayed (the user has entered the main screen)
+ (void)startupEventsOnDidAppearAppContent;

@end

NS_ASSUME_NONNULL_END
Copy the code

The following is the.m file, which performs an automatic check. If the boot items are not started after 30 seconds, a warning message will be displayed in the DEBUG environment. It also starts the boot items that are not started.

#import "BLDelayStartupTool.h"

static BOOL _isCalledStartupEventsOnAppDidFinishLaunchingWithOptions = NO;
static BOOL _isCalledStartupEventsOnADTimeWithAppDelegate = NO;
static BOOL _isCalledStartupEventsOnDidAppearAppContent = NO;
const NSTimeInterval kBLDelayStartupEventsToolCheckCallTimeInterval = 30;
@implementation BLDelayStartupTool

+ (void)load {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kBLDelayStartupEventsToolCheckCallTimeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self checkStartupEventsDidLaunched];
    });
}

+ (void)checkStartupEventsDidLaunched {
    NSString *alertString = @ "";
    if(! _isCalledStartupEventsOnAppDidFinishLaunchingWithOptions) { alertString = [alertString stringByAppendingString:@"AppDidFinishLaunching, "];
        [self startupEventsOnAppDidFinishLaunchingWithOptions];
    }
    if(! _isCalledStartupEventsOnADTimeWithAppDelegate) { alertString = [alertString stringByAppendingString:@"ADTime, "];
        [self startupEventsOnADTime];
    }
    if(! _isCalledStartupEventsOnDidAppearAppContent) { alertString = [alertString stringByAppendingString:@"DidAppearAppContent"];
        [self startupEventsOnDidAppearAppContent];
    }
    
    if (alertString.length > 0) {
#if DEBUG
        alertString = [alertString stringByAppendingString:"Delayed startup items are not started, this will cause application crash"];
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@ "note" message:alertString delegate:nil cancelButtonTitle:@ "good" otherButtonTitles:nil];
        [alertView show];
#endif}} + (void)startupEventsOnAppDidFinishLaunchingWithOptions {
    _isCalledStartupEventsOnAppDidFinishLaunchingWithOptions = YES;
}

+ (void)startupEventsOnADTime {
    _isCalledStartupEventsOnADTimeWithAppDelegate = YES;
}

+ (void)startupEventsOnDidAppearAppContent {
    _isCalledStartupEventsOnDidAppearAppContent = YES;
}

@end
Copy the code

04. pre-maintime

Now that t2 time has been taken care of, let’s look at pre-main.

Apple provides support for viewing pre-main. The key is DYLD_PRINT_STATISTICS.

You also need to check the following option:

Then run the project, and Xcode will print the time spent on the console for the pre-main section:

Total pre-main time: 2.2 seconds (100.0%)
 dylib loading time: 1.0 seconds (45.2%)
rebase/binding time: 100.05 milliseconds (4.3%)
    ObjC setup time: 207.21 milliseconds (9.0%)
   initializer time: 946.39 milliseconds (41.3%)
slowest intializers :
            libSystem.B.dylib :   8.54 milliseconds (0.3%)
  libBacktraceRecording.dylib :  46.30 milliseconds (2.0%)
         libglInterpose.dylib : 187.42 milliseconds (8.1%)
                      beiliao : 896.56 milliseconds (39.1%)
Copy the code

But this part is not so easy to deal with, because it is mainly affected by the following aspects:

  • The number of dynamic libraries of the system used, for exampleUIKit.framework
  • The number of third-party frameworks referenced in Cocoapods
  • The number of classes in the project
  • loadThe code executed in the
  • componentization
  • Swift /

There are others, please god supplement. In the previous points, all we could do was scan through all the load methods of the class and not perform time-consuming operations in them. The rest can’t be changed in a short time.

If you want to make a breakthrough in these areas, see the reference article below.

App Startup Time: Past, Present, and Future iOS App startup performance optimization WWDC optimization App startup speed Impact of iOS Dynamic Framework on App startup time Actually measured the optimized App startup time

5. The last

The GitHub address for the clock timer is here BLStopwatch.

NewPan’s collection of articles

The link below is a catalog of all my articles. These articles are in every article that deals with implementationGithubThe address,GithubHave source code on.

Index of NewPan’s collection of articles

If you have a question, in addition to leaving a comment at the end of the article, you can also leave a comment on Weibo@ hope _HKbuyLeave me a message and visit myGithub.