Bo2SS

Bo2SS

1 Basics (Part 1)

The series "Experience the Full Picture of iOS Knowledge System" shares my learning insights from the Geek Time column "iOS Development Master Class." The author, "Dai Ming," has divided the column into four sections: 1) Basics, 2) Application, 3) Principles, 4) Native and Frontend Dance Together.

I have also organized my study notes according to the author's classification. At this stage, I prefer to absorb the essence of the column, and in later stages, I will gradually increase my output to witness my growth.

Today, I will share the first section: Basics.

image

00 | Introduction#

In 2007, Steve Jobs released the first generation iPhone—it redefined many people's perceptions of mobile phones and marked the beginning of the mobile internet era.

In July 2008, at the WWDC Apple Worldwide Developers Conference, Apple announced that the App Store was officially open to the public—this meant that the mobile internet era belonging to developers had truly begun.

Instead of searching for what the next hotspot in mobile is, it is better to calm down and digest the key technologies left by the waves of the past few years. Based on this foundation, understanding various "new technologies" will become second nature.

The author Dai Ming loves sharing and enjoys posting his learning and work experiences on Dai Ming's Blog. He also shares some technical summaries through code on Dai Ming's GitHub.

01 | Build Your Own iOS Development Knowledge System#

For the iOS knowledge system, the author divides it into four major sections: Basics, Principles, Application Development, and Native and Frontend. He also provides a mind map:

image

In general, do not learn everything you see; instead, learn with purpose and systematically for better results, which will allow you to cope with technological updates and iterations with ease.

02 | How to Optimize and Monitor App Startup Speed?#

App Startup Process#

Generally, the app startup process is divided into cold start and hot start.

  • Cold Start: A complete startup process. Before the app is clicked to start, its process is not in the system, and the system needs to create a new process for it to start.
  • Hot Start: The process of opening the app on the surface. When the app's process is still in the system, the user restarts the app.

The slowness perceived by users occurs on the main thread. The cold startup of the app mainly includes three stages:

  1. Before the execution of the main() function;
  2. After the execution of the main() function;
  3. After the first screen rendering is completed.

Before the Execution of the main() Function#

Main Process:

  • Load the executable file, i.e., the collection of the app's .o files;
  • Load dynamic libraries, perform rebase pointer adjustments and bind symbol bindings;
  • Initial processing of Objc runtime, including registration of Objc-related classes, category registration, selector uniqueness checks, etc.;
  • Initialization, including executing the +load() method, calling functions marked with attribute((constructor)), and creating C++ static global variables.

Optimization Points:

  • Reduce the number of dynamic libraries loaded (Apple recommends using a maximum of 6 non-system dynamic libraries);
  • Reduce loading classes or methods that will not be used after startup;
  • The contents in the +load() method can be executed after the first screen rendering is completed, or replaced with the +initialize() method (performing runtime method replacement in a +load() method incurs a 4-millisecond overhead, which accumulates and increasingly affects startup speed);
  • Control the number of C++ global variables.

After the Execution of the main() Function#

Main Process: From the start of the main() function execution to the completion of the first screen rendering-related methods in appDelegate's didFinishLaunchingWithOptions method. For example, the business code for the home page is executed during this stage, including reading and writing configuration files needed for the first screen initialization, reading large data for the first screen list, and performing extensive calculations for first screen rendering.

Optimization Ideas: Identify which are the necessary initialization functions for first screen rendering, which are necessary for app startup, and which only need to be initialized when the corresponding functionality starts being used.

After the First Screen Rendering is Completed#

Main Process: All methods executed after the first screen rendering within the scope of the didFinishLaunchingWithOptions method are completed. This mainly involves initializing other business service modules that are not on the first screen, registering listeners, reading configuration files, etc.

Optimization Points: Prioritize handling methods that will block the main thread, as they will affect user interactions later.

Understanding the work that needs to be completed during the app startup phase allows for targeted functional-level and method-level startup optimizations.

Functional-Level Startup Optimization#

In simple terms, during the phase from the start of the main() function to the completion of the first screen rendering, only handle business related to the first screen, while the initialization, listener registration, and configuration file reading for non-first screen business should be postponed until after the first screen rendering is completed.

Method-Level Startup Optimization#

Check which time-consuming methods are on the main thread before the first screen rendering is completed, and delay or execute unnecessary time-consuming methods asynchronously.

How to monitor method execution time? There are two main methods:

  1. Periodically capture the method call stack on the main thread to calculate the execution time of each method over a period. The Time Profiler included in the Xcode tool suite uses this method. Its characteristic is that the precision is not high, but it is sufficient.

  2. Hook the objc_msgSend method to grasp the execution time of all methods. Its characteristic is that it is very precise but can only target Objective-C methods (for C methods and blocks, libffi's ffi_call can also be used to achieve hooking, but the threshold for writing and maintaining related tools is relatively high).

For the complete code to monitor execution time based on the second method, see GCDFetchFeed (open-source project). Its usage:

Call [SMCallTrace start] where you need to detect execution time, and call stop and save at the end to print out the method call hierarchy and execution time. You can also set the maximum depth and minimum execution time detection to filter out unnecessary information.

Additional objc_msgSend related knowledge:

  • Source code can be found on Apple's open-source website;
  • It is the necessary path for method execution in Objective-C, controlling all Objective-C methods, so hooking it allows you to hook all Objective-C methods;
  • Execution logic: first obtain the information of the class corresponding to the object, then get the method's cache, find the function pointer based on the method's selector, and finally jump to the corresponding function implementation after exception error handling.
  • The reason for using assembly language: 1) It is called most frequently, and performance optimization on it can enhance the performance of the entire app lifecycle, while assembly language allows for atomic-level optimization, achieving extreme optimization; 2) Other languages find it difficult to implement the functionality of jumping to any function pointer with unknown parameters;
  • For how to hook it, you can refer to Facebook's open-source library fishhook—which dynamically rebinds symbols in Mach-O binaries running on iOS.

Reference materials:

Assembly Language Primer — Ruan Yifeng

03 | Introduction to Auto Layout#

Cassowary is the layout algorithm used in Auto Layout.

When using Auto Layout, be sure to make good use of Compression Resistance Priority and Hugging Priority. By setting priorities, layouts become more flexible, requiring less code and being easier to maintain. You can refer to Auto Layout related demos.

After the emergence of advanced responsive layout ideas like Flexbox in the frontend, Apple encapsulated a UIStackView similar to Flexbox based on Auto Layout to improve the usability of responsive layout in iOS development.

PS: Currently, projects generally use third-party libraries encapsulated based on Auto Layout, such as Masonry — related blog.

04 | How to Design Architecture More Reasonably When the Project Grows and the Team Expands?#

Goal: Completely decouple the business, sink common functionalities, and make each business an independent Git repository, allowing each business to generate a Pod library, which can then be integrated together.

When evolving from a simple architecture to a large project architecture, three issues need to be addressed:

  1. How should module granularity be divided? For iOS, which is an object-oriented programming development model, we should first adhere to the SOLID principles.
  2. How to layer? It is recommended not to exceed three layers: the bottom layer can be basic components unrelated to business, such as networking and storage; the middle layer generally consists of common business components, such as accounts, tracking, payments, shopping carts, etc.; the top layer consists of iterative business components, which are updated most frequently.
  3. How to collaborate among multiple teams? Team division should be flexible, avoiding isolation of personnel that leads to uncoordinated work; then, refine functional modules around specific businesses to solve the problem of redundant construction, and on this basis, solidify the refined modules.

The author's vision of a good architecture:

The relationships between components are coordinated but not fixed standards, and the quality of coordination becomes a basic standard for measuring the quality of architecture.

In practice, there are generally two architectural design schemes:

  1. Protocol-based: Adopting a protocol-based programming approach, using protocols to define specifications at the compilation level, achieving distributed management and maintenance of components. This method also adheres to the dependency inversion principle and is a good practice in object-oriented programming.

  2. Mediator: Using a mediator to uniformly manage the calling relationships between components throughout the app's lifecycle.

When considering architectural design, we still need to achieve decoupling at the same level in functional logic and component division, with clear dependencies between upper and lower layers. Such a structure allows upper components to be easily pluggable and lower components to be more stable. The mediator architecture pattern is more conducive to maintaining this structure, as the mediator's ease of control and extensibility allows the overall architecture to remain robust and vibrant over the long term. Thus, the mediator architecture is the author's vision of a good architecture.

Case Sharing:

ArchitectureDemo — GitHub, which adds support for middleware, state machines, observers, and factory patterns based on the mediator architecture. Additionally, it supports chain calls in usage.

Reference Materials:

Discussion on iOS Application Architecture - Introduction — Casatwy

05 | Linker: How Are Symbols Bound to Addresses?#

Learning with Questions: In the projects I participated in, why do some compile quickly while others are slow; after compilation, why do some start quickly while others are slow?

This article discusses the linker, whose main function is to bind symbols to addresses. Let's start with the compiler—

Why Use a Compiler for iOS Development#

The code written for iOS is first compiled into machine code using a compiler, which is then executed directly on the CPU.

The reason for not using an interpreter to run code is that Apple wants the execution efficiency of programs to be higher and the running speed to be faster.

In contrast, an interpreter can execute code at runtime, which has dynamic characteristics, allowing the program's logic to change at any time by updating the code after the program runs.

Currently, the compiler used by Apple is LLVM, which has improved compilation speed by three times compared to the GCC used before Xcode 5. At the same time, Apple has also led the development of LLVM, allowing it to optimize more for Apple's hardware.

LLVM is essentially a collection of compiler toolchain technologies: the compiler compiles each file, generating Mach-O (executable files); the linker (the lld project in LLVM) merges multiple Mach-O files in the project into one.

Compilation Process:

  1. First, after you write the code, LLVM preprocesses your code, such as embedding macros in the corresponding positions.
  2. Then, LLVM performs lexical and syntax analysis on the code, generating an AST (Abstract Syntax Tree, which is structurally more concise than code and faster to traverse).
  3. Finally, the AST generates IR (Intermediate Representation), which is a language closer to machine code, differing in that it is platform-independent. Through IR, multiple machine codes suitable for different platforms can be generated. For the iOS system, the executable file generated from IR is Mach-O.

What Does the Linker Do at Compile Time?#

The linker's role is to complete tasks such as binding variables, function names, and their addresses. The symbols mentioned at the beginning can be understood as variable names and function names.

Moreover, when the linker organizes the function symbol calling relationships, it clarifies which functions have not been called and automatically removes them. In this process, the linker uses the main function as the source, follows each reference, and marks it as live. After following through, those functions that are not marked as live are useless functions. The linker can then enable the automatic removal of unused code by turning on the Dead code stripping switch (this switch is enabled by default).

Dynamic Library Linking#

The linked shared libraries are divided into static libraries and dynamic libraries:

  • Static libraries are linked at compile time and need to be linked into your Mach-O file. If updates are needed, recompilation is required, and they cannot be dynamically loaded or updated;
  • Dynamic libraries are linked at runtime, and dynamic loading can be achieved using dyld.

Using dyld to load dynamic libraries can be done in two ways: 1) binding during program startup, 2) binding when a symbol is first used. To reduce startup time, most dynamic libraries use the second method.

Common Tools: nm tool - to view the symbol table, otool tool - to find the libraries required for symbols.

I hope that through this series "Experience the Full Picture of iOS Knowledge System," the subsequent in-depth exploration will rely on yourself, so don't just scratch the surface!


Poll: What do you mainly do during the Spring Festival holiday?

A. Visit relatives

B. Travel

C. Organize gatherings

D. Watch dramas

E. Read

F. Code

G. Others

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.