This article is the first article in APP optimization. Because there are many materials and knowledge points to study to write optimization related articles, the speed of updating the article will be slower recently.

I wonder if you, reading this article, have done any work related to APP performance optimization. If you have done more or have some of your own opinions and accumulation in this area, welcome to comment in the comments section to discuss. If you have not done it, it does not matter, I hope that after reading this article, you can encounter performance optimization work in the future, play an enlightening role, know which aspects to start with research.

In our company’s project, online functions gradually stabilize, and new versions are released steadily once a month or half a month. The next step we need to consider is the optimization of performance. The start up speed is the first impression our project gives to users, which is particularly important. If the start up speed is slow, it will cause serious user loss. Therefore, the optimization of the start up speed will become an indispensable part of our later development work.

First of all, the first step is that we need to know what we did when the APP started, so as to explore feasible optimization methods by taking appropriate medicine.

The APP launched

Basic concept

For cold start and hot start these two terms, I believe you must not be unfamiliar with it.

  • Cold startup: it refers to the startup situation where there is no process in the system before startup and the system needs to create a new process for APP use
  • Hot start: Corresponds to the process of the APP. In the system, the user restarts the process of entering the APP. If the APP process is killed and then restarted immediately, it is also hot start because the process cache is still there

In fact, from the perspective of the user’s perception, it is the process that the user clicks the APP icon on the mobile desktop, the APP startup picture disappears completely, and the first frame rendering of the home page is completed. Therefore, in this paper, we will only expand on the process and optimization of APP cold start.

Let’s start by figuring out what happens when the APP launches.

Start the process

In general, APP startup mainly includes three stages:

  1. Before the main() function
  2. After the main() function executes
  3. After rendering the first screen

APP startup time mainly consists of pre-main and time after main, that is, the total startup time = loading time before main + loading time after main. The closer the startup time is to 400ms, the better it is, and it is best to control within 20s, otherwise the system will think that APP enters an infinite loop, and the application process will be forcibly killed by the system.

Let’s move on and talk about what each of these phases does.

Before the main() function

Before the main() function is executed, the system does a few things:

  1. Load the executables (a collection of.o files for your APP), which are mach-o files, more on this later
  2. Load dynamic link library, rebase pointer adjustment and bind symbol
  3. Runtime initialization, including class registration, category registration, selector uniqueness checking, and so on
  4. The +load() method, attribute((constructor)) modified function call, creates a C++ static global variable

The load time of pre-main can be printed by adding the environment variable DYLD_PRINT_STATISTICSA detailed pre-main time can be printed at run timeAs you can see, the time before the main() method consists of four time-consuming parts: dylib loading, Rebse/Binding,ObjC setup, and Initializer.

Therefore, corresponding to this stage, we can do the following optimization work:

  • Reduce dynamic library loading. Each library has its own dependencies, and the system will recursively load all dependent dynamic libraries, so Apple recommends using as few dynamic libraries as possible
  • Reduce unnecessary classes, classes, and methods
  • The +load() method can be implemented after the first screen rendering is complete, if not necessary, or in the +initialize() method. Because a +load() method costs 4 milliseconds
  • Controls the number of C++ static global variables

So what is Mach-O?

Mach-O

Learning Mach-O is essential to understanding the boot process. If you are unfamiliar with Mach-O until you have been exposed to boot tuning, this article will give you a brief introduction.

In short, Mach-O is a binary format unique to OSX and iOS, the executable file format. The basic structure of a mach-O file is composed of Header,Load Commands and Data:

Using the MachOView tool can help us view the contents of the Mach-O file —–MachOView tools, you can directly click the link to obtain the modified source code compilation, or install the PKG package in the root directory folder. When I installed myself at that time, the downloaded installation package encountered some problems of failure to open and crash, so here is an available installation package, I hope it will be helpful to you.

Here are the components of a Mach-O file.

Header is a structure that can be found in Apple’s open source kernel source

struct mach_header_64 { uint32_t magic; // 64-bit or 32-bit cpu_type_t cpuType; // CPU type, such as ARM or X86 CPU_subtype_t cpusubType; // CPU subtype, such as armV8 uint32_t fileType; // File type uint32_t NCMDS; // Load commands quantity uint32_t sizeofcmds; // Load commands size uint32_t flags; // Tag uint32_t reserved; // Keep the field};Copy the code

As shown in the code above, Contains file information such as magic, CPU type cputype, CPU subtype cpusubtype, filetype filetype, number and size of load commands that describe the logical structure and layout of the file in virtual memory, etc.

Filetype indicates the type of the current Mach-O file. Mach-o includes the following types:

  • OBJECT refers to.o files or.a files;
  • EXECUTE: indicates the file after IPA is unpacked.
  • DYLIB, which means.dylib or.framework files;
  • DYLINKER, for dynamic linker;
  • DSYM refers to a file that stores symbolic information for analyzing flash back information.

Load Commands are used to specify layout and file link properties, symbol table location, dynamic connector path, etc. Load Commands are composed of multiple Load Commands, each of which is also a structure, and the corresponding structure of different Load Commands is different. But the information that all Load commands must have is the instruction type CMD and the instruction size cmdsize.

The details of the Load Commands mapping segments can be viewed in the MachOView, and there is a one-to-one mapping:

Almost all Mach-O files contain three segments: _TEXT, _DATA, and _LINKEDIT

  • __TEXT contains Mach headers, executed code, and read-only constants (such as C strings). Read only (r-x).
  • __DATA contains global variables, static variables, and so on. Read-write (RW -).
  • __LINKEDIT contains the “metadata” of the loader, such as the name and address of the function. Read only (r -)

The kernel process

When a Mach-O file is loaded, the kernel forks the process and performs some basic operations on it, such as allocating virtual memory for the process, creating the main thread for the process, and signing code. The entire process can be viewed in the XNU source code.

The kernel has made a series of preparations and the loading of DYLD. After dyLD is started, the next thing will be completed by DYLD. Dyld runs on the program process and the program has the same permissions, and then constitutes the following DYLD work line:

Load dylibs -> Rebase -> Bind -> ObjC -> Initializers

Load dylibs

The dynamic link library loading process is mainly done by DYLD, Apple’s dynamic linker. System to read the first App executable file (Mach – O), obtain dyld path from the inside, and then load the dyld, dyld to initialize the running environment, open caching strategies, loader related dependent libraries (which also contains our executable file), and to link these libraries, and finally call each dependent libraries initialization method, At this step, the Runtime is initialized. After all the dependent libraries have been initialized, the last bit (the program executable) is initialized, at which point the Runtime initializes the class structure for all the classes in the project and then calls all the Load methods. Finally, dyld returns the address of main, main is called, and we arrive at the familiar program entrance.

After dyLD is started, loading instructions of type LC_LOAD_DYLIB will be found on the Mach Header of the executable file, and the required dynamic library will be searched, because each dylib is also a Mach-O image file, which needs to be executed in the same way as the executable file is mapped to memory. Validate Mach headers and so on, and dylib will depend on other Dylibs, so dylibs loading is a recursive process.

Optimizations for this step are:

  1. Reduce non-system library dependencies
  2. Merge non-system libraries

Rebase

Rebase, because the memory address loaded by the Mach-O image file is different from the original address, so dyLD needs Rebase for pointer correction.

Binding

Because the dynamic library is not compiled into the program’s final binary file, but dynamically looks up the address of the calling function at runtime, the process of calling the external symbol to bind is called Binding. For example, we need to use NSObject in objC code, the symbol _OBJC_CLASS_$_NSObject. But this symbol is not in our binary, it is in the system library Foundation.framework, so we need the binding operation to bind the corresponding relationship together

Objc Setup

Objc setup is done primarily in objC_init. After data modification, Objc classes will be registered. If there are classes, methods defined by classes need to be inserted into the list of methods, and each selector must be unique.

initializers

The last step after the above steps is to perform static initialization, such as the Load function, if any C++ initializer constructor will also be executed.

After the main() function executes

After the main () function, it is to point to in the main () function, into the appDelegate didFinishLaunchingWithOptions method first screen render relevant methods perform the complete process.

This process mainly includes:

  1. Log, statistics
  2. Configure an environment for running the APP
  3. The third-party SDK was initialized. Procedure

All sorts of initialization work is done in this phase of the code, resulting in slow rendering. The optimization work we can do here is to sort out the necessary initialization functions for the first screen rendering and APP startup from the perspective of functions, and put the other tasks that need to be initialized only before the corresponding module functions are used in their corresponding appropriate positions.

In addition, the use of instrument analysis can help us in didFinishLaunchingWithOptions time-consuming operation.

After rendering the first screen

At this stage, and we have seen the the home page of the APP, this stage, the thing is some of the other business module initialization, namely appDelegate didFinishLaunchingWithOptions method scope the position of the end.

So this is a low priority, but be aware of actions that block the main thread to prevent the user from doing anything later.

Optimization of work

Each stage above has said the corresponding optimization work can be done, and here is a specific summary.

Optimization at the function level

Maybe your project has a lot of business lines, each line of business is belong to different team to maintenance, various lines of business have all have their own initialization, may be placed in the start stage, it would be a start lag, so each team is needed here from the function level, to tease out the necessary initialization, and unnecessary work, thus optimizing startup speed.

Dynamic library loading aspect

The Link Binary With Libraries should be automatically deleted from the Link Binary With Libraries. The Link Binary With Libraries should be automatically deleted from the Link Binary With Libraries

Method level optimization

After completing the optimization at the function level, the startup speed of APP should have been shortened to a certain extent. Now we will continue to optimize from the method level.

  1. Delete useless code

As mentioned above, a +load() method costs 4ms, so as our project grows and lines of business cross, there may be a lot of unnecessary redundant code in it. We can use AppCode to analyze the project code.

Because AppCode also analyzes the code inside Pods, it doesn’t matter if you try to set it up. It always checks it, and it takes a very, very, very long time. Therefore, a better way is to remove the code of Pods first and simply analyze the code of the project.

After the detection, it is necessary to manually remove the useless code, but note that the detection result is not very accurate, for example, some runtime mapping methods may also be detected, so it is still necessary to manually check again to prevent the error deletion of useful code.

  1. Abstract duplicate code

1, in the iOS code may write many classification method for the same class, due to participate in the development of students is more, method may lead to repeat, but actually to run only one classification method is invoked, it depends on which classification is loaded, after the compilation of the binary, however, two methods should be exist, this will not only increase the app volume, It also increases the startup time, so you should eliminate such repetitive problems;

2, there are many places may be different names, but the function of the same, this is not easy to find, we need to pay attention to in the process of writing code;

3. Or the two functions have similar names and many similar codes, in which case the same code can be extracted.

conclusion

If you really need to do optimization related work, do not blindly, do not rush, each project is different, maybe other people’s plan is not suitable for your project. Therefore, it is recommended to spend more energy to understand, such as the start-up process, the reason for the lag and other principles, this process you will benefit a lot, but also do the first step of the cornerstone of optimization.