Start speed optimization

APP Startup Process

The pre – the main stage
  1. Load the executable (a collection of all the.o files in your App).
  2. Load the dynamic link library. (1) Load the dynamic linker DYLD (Dynamic loader, is a library specially used to load the dynamic link library). (2) DyLD recursively loads the dynamic link library dylib on which all applications depend.
  3. Initial processing of the Objc runtime, including registration of Objc related classes, category registration, selector uniqueness checking, and so on.
  4. Initialization, which involves executing the +load() method and creating C++ static global variables.
The main stage

There are two ways. Personally, I think the second one is more effective. First, main ends at didFinishLaunching; Second, main to viewDidAppear of the first ViewController.

  1. Call the main ()
  2. Calls UIApplicationMain ()
  3. Call applicationWillFinishLaunching
  4. The home page initializes UI rendering, data reading and calculation, database reading and storage, etc
Analyze and calculate the time of the four stages in detail
  1. dyld

The Dynamic Link Editor is a library designed to load dynamic link libraries. After the XNU kernel is ready for the program to start, the execution switches from kernel mode to user mode, and dyLD completes the subsequent loading. The system first reads the executable file of App (Mach-O file), obtains the path of DYLD from it, and then loads DYLD. Dyld initialates the runtime environment, enables the cache policy, and loads the dynamic library that the program depends on (including our executable file). The libraries are linked (mainly rebaseing and Binding), and finally the initialization methods for each dependent library are called. In this step, the Runtime is initialized.

Mach-o: The Mach-O file format is the executable file format on OS X and iOS, as we produced during compilation. O files, as well as program executables, dynamic libraries, and so on are Mach-O files.

ImageLoader: a class used to assist in loading a particular executable format. The corresponding instance of the program can be referred to as image(e.g. an executable, Framework library, or bundle).

  1. Rebasing and Binding

ASLR (Address Space Layout Randomization) : Address Space Layout Randomization. Before THE advent of ASLR, programs were loaded at a fixed address, so hackers could know the address of a function in the program, implant malicious code, and modify the address of the function, which brought a lot of risks. ASLR is designed to solve this problem. Every time the program is started, the address will change randomly, so that all code addresses in the program need to be re-calculated and repaired before normal access. Rebasing this step is to adjust the internal pointer of the mirror.

Binding: Points to content outside the mirror.

  1. ObjC setup

The objc_init method called in the last step above is the runtime initialization method, where the main operation is to load the class:

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
Copy the code

_dyld_objc_notify_register(&map_images, load_images, unmap_image); A notification event is registered with dyLD, and when a new image is loaded into memory, the load_images method is triggered. In this method, the class in the corresponding image is loaded and the load method is called.

load_images(const char *path __unused, const struct mach_header *mh) { // Return without taking locks if there are no +load methods here. if (! hasLoadMethods((const headerType *)mh)) return; recursive_mutex_locker_t lock(loadMethodLock); // Discover load methods { rwlock_writer_t lock2(runtimeLock); prepare_load_methods((const headerType *)mh); } // Call +load methods (without runtimeLock - re-entrant) call_load_methods(); } /*********************************************************************** * call_load_methods * Call all pending class and category +load methods. * Class +load methods are called superclass-first. * Category +load methods are not called until after the parent class's +load. * * This method must be RE-ENTRANT, because a +load could trigger * more image mapping. In addition, the superclass-first ordering * must be preserved in the face of re-entrant calls. Therefore, * only the OUTERMOST call of this function will do anything, and * that call will handle all loadable classes, even those generated * while it was running. * * The sequence below preserves +load ordering in the face of * image loading during a +load, and make sure that no * +load method is forgotten because it was added during * a +load call. * Sequence: * 1. Repeatedly call class +loads until there aren't any more * 2. Call category +loads ONCE. * 3. Run more +loads if: * (a) there are more classes to load, OR * (b) there are some potential category +loads that have * still never been attempted. * Category +loads are only run  once to ensure "parent class first" * ordering, even if a category +load triggers a new loadable class * and a new loadable category attached to that class. * * Locking: loadMethodLock must be held by the caller * All other locks must not be held. **********************************************************************/ void call_load_methods(void) { static bool loading = NO; bool more_categories; loadMethodLock.assertLocked(); // Re-entrant calls do nothing; the outermost call will finish the job. if (loading) return; loading = YES; void *pool = objc_autoreleasePoolPush(); do { // 1. Repeatedly call class +loads until there aren't any more while (loadable_classes_used > 0) { call_class_loads(); } // 2. Call category +loads ONCE more_categories = call_category_loads(); // 3. Run more +loads if there are classes OR more untried categories } while (loadable_classes_used > 0 || more_categories); objc_autoreleasePoolPop(pool); loading = NO; }Copy the code

If there is an inherited class, the load method of the parent class is called first and then the subclass’s load method is called, but [super load] cannot be called in the load. And then finally, call the category load method. So in this step, all the loads are going to be called.

  1. C++ initializer

In this step, clang’s __attribute__((constructor) constructor is called if we use it in our code.

The article recommended

IOS app startup -> Dyld load -> Runtime initialization

Start-up time monitoring

Xcode gets the pre-main time

To test the startup time, Xcode simply sets the environment variable DYLD_PRINT_STATISTICS to 1 in Edit scheme -> Run -> Arguments to see how much time was spent in the stages prior to main. To print a more detailed elapsed time, set DYLD_PRINT_STATISTICS_DETAILS to 1. Settings:Print time screenshot:

As can be seen from the figure above, it is mainly divided into the following parts:

  • dylib loading time
  • rebase/binding time
  • ObjC setup time
  • initializer time
Obtain the pre-main time online

How do we get the load time before Main without Xcode?

The main capture of the developer’s control of the startup segment. The Init section shown in the figure handles the initializer and ObjC Load methods for C++ static objects.

Idea: Through the hook key function call, calculate the performance data. Collect statistics on the time of each load function and the total time of all load functions, and report them for statistical analysis.

Startup time optimization

The points we can optimize for the time before calling main() are:
  1. Reduce unnecessary framework because dynamic linking is time consuming
  2. The Check framework should be set tooptionalrequiredIf the framework exists on all versions of iOS currently supported by the App, set it to Required, otherwiseoptionalBecause theoptionalThere will be some additional tests
  3. Merge or delete some OC classes. For cleaning up classes that are not used in the project, use the AppCode code check function
  4. Remove static variables that are useless
  5. Cut not invoked or abandoned method stackoverflow.com/questions/3… Developer.Apple.com/library/ios…
  6. Will not have to be in+loadMethod to do things deferred to+initialize
  7. Try not to useC++Virtual functions (creating virtual function tables has overhead)
The load time after the main() call
  • Analysis of the

    After main() is called, the main job of the App is to initialize the necessary services, display the home page content, and so on. And our optimization is also around how to quickly show the home page to carry out.

    The App is usuallyAppDelegateIn the class didFinishLaunchingWithOptions:At the end of the current runloop, call CA::Transaction::commit to render the view.

    Rendering a view involves three main stages:
    1. Preparation stage here is mainly the decoding of pictures
    2. All UIViews on the first page of the layout stagelayoutSubViewsrun
    3. Draw all UIViews on the first page of the phasedrawRect:run

Add in the startup of the necessary services after startup, and the creation and reading of the necessary data, and these are the areas where we can try to optimize

  • Points that can be optimized after the main() function call:
    1. Instead of using storyboards and xibs, load the home page view directly with the code
    2. NSUserDefaults actually produces a plist file in the Library folder. If the file is too large to read into memory at one time, it may take a long time to evaluate the impact. If it takes a long time, it needs to be split (consider old overwrite installation compatibility issues).
    3. Each time printing in NSLog mode will implicitly create a Calendar, so it is necessary to delete the log entered by each business party at startup, or output the log only for the internal test version
    4. Check whether all network requests sent during application startup can be unified in asynchronous threads
Specific optimization point
  1. rightdidFinishLaunchingIn the function to consider whether mining can be lazy loading or lazy loading
  2. Delete redundant codes for offline services
  3. Some services irrelevant to UI display, such as microblog authentication expiration check, picture maximum cache space Settings, do lazy loading
  4. After launching, display the flash screen advertisement picture and initialize the list page of the home page. When the advertisement display is completed, the list page will be rendered. After this optimization, the total startup time after main() has reached the expected effect through the verification of the data collected after going online.
  5. didFinishLaunchingWithOptions:
    • Comb through the binary/tripartite libraries and find libraries that can be lazy loaded, for example in the viewDidAppear method of the home controller.
    • Sort out the business logic and delay the execution of the logic that can be delayed. Check for new versions, register for push notifications, and so on.
    • Avoid complex/redundant calculations. Avoid doing too much on the home controller’s viewDidLoad and viewWillAppear, they’re done, they’re not displayed on the home controller,
    • Views that can be created lazily should be created lazily/loaded lazily.
    • Adopt a better performing API.
    • The home page controller is built in a pure code manner.
The article recommended

IOS startup speed optimization iOS performance optimization: The actual use of Instruments

Installation package optimization

App installation package (IPA file) is composed of resources (images + documents) and executable files (binary files), and installation package slimming is also carried out from these two parts.

1. Resource file optimization (mainly picture resources)

  • Use the software LSUnusedResource to find images that aren’t used in your project and then delete them, not necessarily accurately, Some [UIImage imageNamed:[NSString stringWithFormat:@”icon_%d”,index]] images that are used in this way will also be listed in unused images.
  • Compressed image resources (use ImageOptim to compress the image size, some large background images into.jpg format)
  • Using assets.xcassets to manage images can also reduce the size of the installation package

2. Document resource optimization

  • Delete unnecessary documents
  • Optimize and simplify document content

3. Code optimization

  • Check for redundant code (delete useless classes, methods, third-party libraries, readme files)
  • Pay attention to the usual development habits, discarded modules as soon as possible
  • Code refactoring: Code refactoring is the abstract encapsulation of duplicate code in one or more classes to make the code look cleaner and more reusable.
  • Don’t introduce a library just because you have to do it in a small way

4. Optimization of Xcode compilation options:

  1. Configure compilation options

Generate Debug Symbols set to NO. This configuration should allow you to subtract less than half the size. Notice that if this is set to NO it will not stop at the breakpoint; • Build Settings->Optimization Level has several options for compiler optimizations. The release should use the Fastest, Smalllest[-os] option to enable all optimizations that do not increase the code size. And keep the executable as small as possible. Strip Debug Symbols During Copy and Symbols Hidden by Default should be set to yes in the release version to remove unnecessary debugging Symbols. The Symbols Hidden by Default defines all Symbols as “private extern”, reducing the size. 4. Strip Linked Product: Set DEBUG to NO and RELEASE to YES to reduce app size in RELEASE mode; 5. Compiler optimization to remove exception support. Enable C++ Exceptions, Enable Objective-c Exceptions set to NO, Other C Flags add -fno-exceptions

Generate Debug Symbols: This setting defaults to YES for both Debug and RELEASE. Debug symbols are generated at compile time. In Xcode, when the Generate Debug Symbols option was set to YES, each source file was compiled with -g and -gmodules. But other processes such as linking remain the same. When Generate Debug Symbols is set to YES, the resulting.o file will be larger and the resulting executable will also be larger. Breakpoints set in Xcode will not break when Generate Debug Symbols is set to NO. However, if you print [NSThread callStackSymbols], you can still see the class and method names. Strip Linked Product: Set to NO, breakpoints set in Xcode will not break. Configuration Description

Reference article:

Dry goods | headlines today iOS installation package size optimization