Something that happens after ⌘ + R
As a coder, my daily work is either solving bugs or writing bugs. Some things are not necessarily good for writing bugs, but good for solving bugs.
To a project, when you press ⌘ + R to the main interface to be shown, have you ever wondered what is going on in the process? These primitive things do not help us coding directly, so we can do coding whether we know it or not. But a coder’s job is not only coding, but also debugging. Knowing these things will help us sort out some of the problems.
According to stages, this process can be roughly divided into three stages: compilation stage, APP startup stage and layer rendering stage. These three processes are described in detail below.
Compilation phase
Those who have learned the principle of compilation should know that compilation is mainly divided into four processes: preprocessing, compilation, assembly and linking. The following is roughly in accordance with this approach. IOS compilation process, using CLang as the front end, LLVM as the back end to complete. You use Clang to handle the first stages and LLVM to handle the later stages.
1. The preprocessing
Also known as precompilation, it does some text replacement work. Handle instructions beginning with #, such as:
- Expansion of macro definition (#define)
- Expand header files (#include, #import)
- Handling conditional compilation instructions (#if,#else,#endif)
For example, we define the following macro in our code:
#define APP_VERSION "V1.0.0"
int main(int argc, char * argv[]) {
char *version = APP_VERSION;
printf("app version is %s",version);
}
Copy the code
The pre-processing results of macro expansion using Clang-e main.m are as follows:
int main(int argc, char * argv[]) {
char *version = "V1.0.0";
printf("version is %s",version);
return 0;
}
Copy the code
Macros have a lot of pitfalls, try to use other ways instead.
2. Lexical analysis
After the pretreatment, the lexical analyzer (also called scanner) will scan the source code in.m from left to right, identify all kinds of words and keywords according to the lexical rules of the language, and generate the corresponding word attribute words. For example, the following code:
#define APP_VERSION "V1.0.0"
int main(int argc, char * argv[]) {
char *version = APP_VERSION;
printf("version is %s",version);
return 0;
}
Copy the code
After pre-processing, clang-xclang-dump-tokens main.m was used for scanning analysis. The results are as follows:
int 'int' [StartOfLine] Loc=<main.m:14:1> identifier 'main' [LeadingSpace] Loc=<main.m:14:5> l_paren '(' Loc=<main.m:14:9> int 'int' Loc=<main.m:14:10> identifier 'argc' [LeadingSpace] Loc=<main.m:14:14> comma ',' Loc=<main.m:14:18> char 'char' [LeadingSpace] Loc=<main.m:14:20> star '*' [LeadingSpace] Loc=<main.m:14:25> identifier 'argv' [LeadingSpace] Loc=<main.m:14:27> l_square '[' Loc=<main.m:14:31> r_square ']' Loc=<main.m:14:32> r_paren ')' Loc=<main.m:14:33> l_brace '{' [LeadingSpace] Loc=<main.m:14:35> char 'char' [StartOfLine] [LeadingSpace] Loc=<main.m:18:5> star '*' [LeadingSpace] Loc=<main.m:18:10> identifier 'version' Loc=<main.m:18:11> equal '=' [LeadingSpace] Loc=<main.m:18:19> string_literal '"V1.0.0"' [LeadingSpace] Loc=<main.m:18:21 <Spelling=main.m:12:21>> semi '; ' Loc=<main.m:18:32> identifier 'printf' [StartOfLine] [LeadingSpace] Loc=<main.m:19:5> l_paren '(' Loc=<main.m:19:11> string_literal '"version is %s"' Loc=<main.m:19:12> comma ',' Loc=<main.m:19:27> identifier 'version' Loc=<main.m:19:28> r_paren ')' Loc=<main.m:19:35> semi '; ' Loc=<main.m:19:36> return 'return' [StartOfLine] [LeadingSpace] Loc=<main.m:20:5> numeric_constant '0' [LeadingSpace] Loc=<main.m:20:12> semi '; ' Loc=<main.m:20:13> r_brace '}' [StartOfLine] Loc=<main.m:21:1> eof '' Loc=<main.m:21:2>Copy the code
As you can see from the above, each word or character is marked with the number of columns and lines so that clang can quickly locate errors in the code if it encounters problems during compilation.
3. Grammatical analysis
Next comes the parsing. Through this stage, the results derived from the previous stage are parsed into an abstract syntax tree — AST. Suppose our source code looks like this and has been preprocessed:
#define APP_VERSION "V1.0.0"
int main(int argc, char * argv[]) {
char *version = APP_VERSION;
printf("version is %s",version);
return 0;
}
Copy the code
After running the clang command clang-xclang-ast – dump-fsyntax-only mian. M, the syntax tree is as follows:
. FunctionDecl 0x7ffe55884228 <main.m:14:1, line:21:1> line:14:5 main 'int (int, char **)' |-ParmVarDecl 0x7ffe55884028 <col:10, col:14> col:14 argc 'int' |-ParmVarDecl 0x7ffe55884110 <col:20, col:32> col:27 argv 'char **':'char **' `-CompoundStmt 0x7ffe55884568 <col:35, line:21:1> |-DeclStmt 0x7ffe55884390 <line:18:5, col:32> | `-VarDecl 0x7ffe558842e8 <col:5, line:12:21> line:18:11 used version 'char *' cinit | `-ImplicitCastExpr 0x7ffe55884378 <line:12:21> 'char *' < ArrayToPointerDecay > | ` - StringLiteral zero x7ffe55884348 < col: 21 > 'char [7]' lvalue "V1.0.0 | - x7ffe558844b0 CallExpr 0" <line:19:5, col:35> 'int' | |-ImplicitCastExpr 0x7ffe55884498 <col:5> 'int (*)(const char *, ...) ' <FunctionToPointerDecay> | | `-DeclRefExpr 0x7ffe558843a8 <col:5> 'int (const char *, ...) ' Function 0x7ffe55088570 'printf' 'int (const char *, ...) ' | |-ImplicitCastExpr 0x7ffe55884500 <col:12> 'const char *' <BitCast> | | `-ImplicitCastExpr 0x7ffe558844e8 <col:12> 'char *' <ArrayToPointerDecay> | | `-StringLiteral 0x7ffe55884408 <col:12> 'char [14]' lvalue "version is %s" | `-ImplicitCastExpr 0x7ffe55884518 <col:28> 'char *' <LValueToRValue> | `-DeclRefExpr 0x7ffe55884440 <col:28> 'char *' lvalue Var 0x7ffe558842e8 'version' 'char *' `-ReturnStmt 0x7ffe55884550 <line:20:5, col:12> `-IntegerLiteral 0x7ffe55884530 <col:12> 'int' 0Copy the code
Each node in the abstract syntax tree is also marked with a specific location in the source code for easy problem location. There is a lot of knowledge about abstract syntax trees that I won’t explain in detail here.
Static analysis
After converting the source code into an abstract syntax tree, the compiler can parse the tree. Static analysis improves code quality by checking for errors such as methods that are called but not defined, variables that are defined but not used, etc. You can also use Xcode’s own static analysis tool (Product -> Analyze) or some third-party static analysis tools (such as Facebook’s Infer) to perform in-depth analysis.
Sometimes the compiler’s built-in static analysis does not meet our daily development needs. So we can use scripts to customize a set of analysis solutions and put them into an integrated environment. Each time the code is committed, the script is triggered for static analysis, warning if an error occurs, and the code fails to be committed. Too high development quality in turn.
If you are interested, take a look at the Clang static analysis source code to see which syntax is statically analyzed.
5. Generate code and optimize
After preprocessing and analysis using Clang, LLVM code is generated. Same code as before:
#define APP_VERSION "V1.0.0"
int main(int argc, char * argv[]) {
char *version = APP_VERSION;
printf("version is %s",version);
return 0;
}
Copy the code
Clang -o3 -s -emit-llvm main.m -o main.ll: clang -o3 -s -emit-llvm main.m -o main.ll
; ModuleID = 'main.m' source_filename = "main.m" target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-apple-macosx10.13.0" @. STR = private unnamed_addr constant [7 x i8] c"V1.0.0\00", align 1 @.str.1 = private unnamed_addr constant [14 x i8] c"version is %s\00", align 1 ; Function Attrs: Nounwind SSP uwtable define i32@main (i32, i8** nocapture readnone) local_unnamed_addr #0 { %3 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str.1, i64 0, i64 0), i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i64 0, i64 0)) ret i32 0 } ; Function Attrs: nounwind declare i32 @printf(i8* nocapture readonly, ...) local_unnamed_addr #1 attributes #0 = { nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "Target - the features" = "+ cx16, + FXSR, + MMX and sse +, + sse2, + sse3, + sse4.1, + ssse3, + x87" "unsafe - the fp - math" = "false" "use-soft-float"="false" } attributes #1 = { nounwind "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "Target - the features" = "+ cx16, + FXSR, + MMX and sse +, + sse2, + sse3, + sse4.1, + ssse3, + x87" "unsafe - the fp - math" = "false" "use-soft-float"="false" } ! llvm.module.flags = ! {! 0,! 1,! 2,! 3,! 4,! 5}! llvm.ident = ! {! 6}! 0 =! {i32 1, !" Objective-C Version", i32 2} ! 1 =! {i32 1, !" Objective-C Image Info Version", i32 0} ! 2 =! {i32 1, !" Objective-C Image Info Section", !" __DATA, __objc_imageinfo, regular, no_dead_strip"} ! 3 =! {i32 4, !" Objective-C Garbage Collection", i32 0} ! 4 =! {i32 1, !" Objective-C Class Properties", i32 64} ! 5 =! {i32 1, !" PIC Level", i32 2} ! 6 =! {!" Apple LLVM Version 9.0.0 (clang-900.0.39.2)"}Copy the code
If you look at the main method, it doesn’t matter if you don’t understand it, I don’t understand it either. Just understand the process.
LLVM will then compile and optimize the code, such as optimization for global variables, loop optimization, tail recursion optimization, etc., which I don’t know too much about, so I can’t talk too much about. If you want to know more about it, you can take a look at this article: LLVM full time Optimization.
Finally, output assembly code.
6. Assembly
At this stage, the assembler converts readable assembly code into machine code. The end product is the object file ending in.o.
For the following part of the code:
#define APP_VERSION "V1.0.0"
int main(int argc, char * argv[]) {
char *version = APP_VERSION;
printf("version is %s",version);
return 0;
}
Copy the code
We can use the clang command clang -c main.m to generate the target file mian. I’m not going to write what I opened, because it’s binary and I can’t read it.
Links to 7.
This phase links the object file generated in the previous phase with the referenced static library to generate the executable file.
We can use the clang command clang main.m to generate the executable a.out (a.out by default). Then use the file a.out command to check its type:
a.out: Mach-O 64-bit executable x86_64
Copy the code
You can see that executables are of the Mach-O type, and executables on both MAC OS and iOS platforms are of this type. Because I’m using an emulator, the processor instruction set is X86_64.
The compilation phase is now complete.
8. A complete build in Xcode
Finally, let’s take a look at the Build log in Xcode to walk through the process. Open Xcode’s Log Navigator and select Build to see the Log for this Build:
The log is segmented by target. In the current project, three libraries YYCache, YYImage and AFNetworking are introduced through Pod. Besides, there is a PODs-test and target of the project itself. The log format is the same between each target, so we only analyze one target. This analysis is only for the project itself target, also known as Test. It looks like this:
It looks very messy. After finishing it, I will repeat the following process:
- Compile information is written to secondary files to create the compiled file schema (test.app).
- Process packaging information.
- Execute the CocoaPods pre-compile script. For example, here
Check Pods Manifest.lock
. - Compile various.m files (.h files are not involved).
- Link the required framework.
- Compile ImageAssets.
- Compile the Storyboard and related files.
- Process the info.plist file.
- Link Storyboards.
- Execute CocoaPods related scripts, which you can view in Build Phases.
- Create the.app file.
- Sign the.app file
Here we’ll elaborate on step 4. We select the log of one of the files viewController.m for analysis:
To sort out the log information:
1. CompileC /... /Test.build/Objects-normal/x86_64/ViewController.o Test/ViewController.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler 2.cd /Users/zhoubo/Test
3. export LANG=en_US.US-ASCII
export PATH="/Applications/Xcode.app/Contents/Developer/.. /sbin"4. clang -x objective-c -arch x86_64 -fmessage-length=0... -fobjc-arc... -Wno-missing-field-initializers... -DDEBUG=1... -isysroot ... / iphonesimulator11.2. SDK -I ONE PATH -f ONE PATH -c /.. /ViewController.m -o /.. /ViewController.oCopy the code
The corresponding explanations are as follows:
- The starting point of a task is expressed in log.
- The corresponding working directory is displayed.
- Perform Settings on the LANG and PATH environment variables.
-
Clang command start:
-x: objective-c-arch x86_64: the processor instruction set is x86_64-fobjC-arc: a series of instructions-fAt the beginning, specify that this file uses the ARC environment. You can set ARC support for each file via Build Phases. -wno-mission-field-initializers: a series of directives starting with -w, warning options for compilation, which can be customised with -ddebug =1: some that start with -d, meaning pre-compiled macros. -isysroot ... / iphonesimulator11.2. SDK: The iOS SDK version used during compilation. -i: writes the compilation information to a file. -f: the framework required during the linking process. -C: compiles the fileCopy the code
9. About dSYM files
Every time we compile, we generate a dSYM file. In this file, the hexadecimal function address mapping table is stored. In the binary that the APP executes, methods are called by address. When crash occurs, address mapping can be performed through dSYM files to find the specific function call stack.
App Startup Phase
In the previous phase, the final product was an executable file in the format of Mach-O. For this stage, I’ll start with this file and describe the APP startup process in detail.
1. Process overview
This process is divided into several stages, a simple comb, can make the brain has a clear brain circuit, not more and more confused.
- System preparation.
- Load dyLD into App process (DYLD).
- Load Dylibs required by the App.
- Rebase & Bind.
- Objc setup.
- Initializers.
- Mian ().
An official flow chart:
2. Concept explanation
Before going through the whole process, let’s explain two concepts: Mach-O files and dyLD.
.Mach-O
Mach-o is a file format used in iOS, MacOS, WatchOS and other Apple operating systems. This file format can be used for the following files:
- Executable files
- Dylib dynamic libraries
- Bundles cannot be connected to dynamic libraries and can only be loaded via dlopen()
- Image here means one of various Executable, Dylib, or Bundle, as described below.
- A collection of Framework dynamic libraries and corresponding header and resource files.
The mach-o file format is as follows:
- Header, which contains the CPU architecture of the file, such as x86, ARM7, ARM64, etc.
- Load Commands, which contains the organization structure of files and how they are laid out in virtual memory.
- Data, which contains each segment required for Load Commands, and each segment contains multiple sections. When running an executable, the virtual memory system maps segments to the process’s address space.
Create executable file (a.out); use size to view the segment content of this executable.
xcrun size -x -l -m a.out
Copy the code
The following results can be obtained:
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0x43 (addr 0x100000f30 offset 3888)
Section __stubs: 0x6 (addr 0x100000f74 offset 3956)
Section __stub_helper: 0x1a (addr 0x100000f7c offset 3964)
Section __cstring: 0x15 (addr 0x100000f96 offset 3990)
Section __unwind_info: 0x48 (addr 0x100000fac offset 4012)
total 0xc0
Segment __DATA: 0x1000 (vmaddr 0x100001000 fileoff 4096)
Section __nl_symbol_ptr: 0x10 (addr 0x100001000 offset 4096)
Section __la_symbol_ptr: 0x8 (addr 0x100001010 offset 4112)
Section __objc_imageinfo: 0x8 (addr 0x100001018 offset 4120)
total 0x20
Segment __LINKEDIT: 0x1000 (vmaddr 0x100002000 fileoff 8192)
total 0x100003000
Copy the code
To make a long story short:
Segment __PAGEZERO
. The size is 4GB. The first 4GB of the process address space is mapped as unreadable, unwritable, and unexecutable.Segment __TEXT
. Contains executable code, mapped read-only and executable.Segment __DATA
. Contains data that will be changed, mapped in both read-write and non-executable ways.Segment __LINKEDIT
. Contains metadata for methods and variables, code signatures, and more.
dyld
Dynamic loader. It is open source and you can read the source code if you are interested. Dyld1 is outdated. Don’t try to understand it. Dyld2 is most commonly used at present. WWDC2017 Apple launched dyLD3, currently only available on iOS App, should be popular later. At the end of this phase, dyLD3 will be described in more detail than is described here.
Let’s start with a formal introduction to the startup process.
3. System preparation
After clicking APP, the system did a lot of things in the process of loading dyLD dynamic loader, which can be roughly divided into the following stages:
Most of you haven’t studied this in depth, and NEITHER have I. So I try to make things as simple as possible and try to explain these processes in the simplest way possible.
- After clicking APP, the system will create a process. Then use the
load_init_program
Function loads the system initialization process. And then call it inside the methodload_init_program_at_path
. throughload_init_program_at_path
The method call__mac_execve
. __mac_execve
The function starts a new process and task, calledexec_activate_image
.exec_activate_image
Function distributes memory mapping functions in binary format.Mach-O
The file will beexec_mach_imgact
To deal with.- in
exec_mach_imgact
Function, will detectMach-O
Header, parse its architecture and other information, whether the file is legal, etc.; First copyMach-O
File into memory; Then copyMach-O
File into memory; Then dyLD related treatment; Finally, release resources. load_machfile
Function is responsible forMach-O
File loading related work. Allocate executable memory for the current task. loadingMach-O
The load Command section of the Base data section execution, prevent overflow vulnerability attacks, set ASLR, etc.; The last toexec_mach_imgact
Return the result.parse_machfile
According to theload_command
Information to select different functions to load data. The one that’s used isswitch-case
Statement, and the types handled areLC_LOAD_DYLINKER
,LC_ENCRYPTION_INFO_64
And so on.- In the previous step, there was a case of
LC_LOAD_DYLINKER
. Enter this case three times and existdylinker_command
Order, will be laterperformload_dylinker()
Loading dyld.
4. Load dyLD into the App process
In the source of dyld, there is a dyLDstartup. s file. This file defines different boot methods for different CPU architectures. The __dyLD_START method is executed, then the dyLDbootstrap ::start() method is called, and finally the dyld::_main() method in dyld.cppp is called. Part of the code is as follows:
__dyld_start:
pushq $0 # push a zero for debugger end of frames marker
movq %rsp,%rbp # pointer to base of kernel frame
andq $- 16,%rsp # force SSE alignment
# call dyldbootstrap::start(app_mh, argc, argv, slide)
movq 8(%rbp),%rdi # param1 = mh into %rdi
movl 16(%rbp),%esi # param2 = argc into %esi
leaq 24(%rbp),%rdx # param3 = &argv[0] into %rdx
movq __dyld_start_static(%rip), %r8
leaq __dyld_start(%rip), %rcx
subq %r8, %rcx # param4 = slide into %rcx
call __ZN13dyldbootstrap5startEPK12macho_headeriPPKcl
# clean up stack and jump to result
movq %rbp,%rsp # restore the unaligned stack pointer
addq $16,%rsp # remove the mh argument, and debugger end frame marker
movq $0,%rbp # restore ebp back to zero
jmp *%rax # jump to the entry point
Copy the code
The _main() method contains the startup process of the App, and finally returns the address of the main method of the App. The code is omitted here, and only the process is marked:
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[])
{
// Setting up context, initializing necessary parameters, parsing environment variables, etc.try {
// instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
sMainExecutable->setNeverUnload();
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.processIsRestricted = sProcessIsRestricted;
// load shared cache
checkSharedRegionDisable();
#if DYLD_SHARED_CACHE_SUPPORT
if( gLinkContext.sharedRegionMode ! = ImageLoader::kDontUseSharedRegion ) mapSharedCache();#endif
// load any inserted libraries
if( sEnv.DYLD_INSERT_LIBRARIES ! =NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib ! =NULL; ++lib) loadInsertedDylib(*lib); }...// link main executable
gLinkContext.linkingMainExecutable = true;
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, ImageLoader::RPathChain(NULL.NULL));
gLinkContext.linkingMainExecutable = false;
if ( sMainExecutable->forceFlat() ) {
gLinkContext.bindFlat = true;
gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
}
// get main address
result = (uintptr_t)sMainExecutable->getMain(); .return result;
}
Copy the code
5. Load the dynamic library required by the App
As mentioned above, an image is actually a type of Mach-O file that includes Executable, Dylib, or Bundle. In section dyld: : _main () function can be seen, dyld will by calling instantiateFromLoadedImage choose imageLoader load corresponding to the executable file.
Then through mapSharedCache () function/System/Library/Caches/com. Apple. Dyld/dyld_shared_cache_arm64 Shared dynamic Library loaded into memory, This is also the dynamic library sharing mechanism implemented by different apps. The shared dynamic library in the virtual memory of different apps will map the same physical memory through the vm_map of the system, so as to realize the shared dynamic library.
The loadInsertedDylib() function is then called to load the dynamic libraries in the environment variable DYLD_INSERT_LIBRARIES. The loadInsertedDylib dynamic library doesn’t do much work. The main work is to call the load function. Dlopen also calls the load function to load the dynamic library.
The link() function is then called to recursively link to the library that the program depends on. Generally, an App relies on about 100-400 dynamic libraries. You can run the otool -l Test command to view the dynamic library required by the Test project as follows:
The/usr/lib/libsqlite3. Dylib (compatibility version 9.0.0, Current version 274.6.0) /usr/lib/libz.1.dylib (compatibility version 1.0.0, The current version 1.2.11)/System/Library/Frameworks/Accelerate framework/Accelerate (compatibility version 1.0.0, The current version 4.0.0)/System/Library/Frameworks/AssetsLibrary framework/AssetsLibrary (compatibility version 1.0.0, The current version 1.0.0)/System/Library/Frameworks/CoreFoundation framework/CoreFoundation (compatibility version 150.0.0, The current version 1450.14.0)/System/Library/Frameworks/CoreGraphics framework/CoreGraphics (compatibility version 64.0.0, The current version 1129.2.1)/System/Library/Frameworks/ImageIO framework/ImageIO (compatibility version 1.0.0, The current version 0.0.0)/System/Library/Frameworks/MobileCoreServices framework/MobileCoreServices (compatibility version 1.0.0, The current version 822.19.0)/System/Library/Frameworks/QuartzCore framework/QuartzCore (compatibility version 1.2.0, The current version 1.11.0)/System/Library/Frameworks/Security. The framework/Security (compatibility version 1.0.0, The current version 58286.32.2)/System/Library/Frameworks/SystemConfiguration framework/SystemConfiguration (compatibility Version 1.0.0, current version 963.30.1)/System/Library/Frameworks/UIKit framework/UIKit (compatibility version 1.0.0, The current version 3698.33.6)/System/Library/Frameworks/Foundation. The framework/Foundation (compatibility version 300.0.0, /usr/lib/libobjc.a. dylib (Compatibility version 1.0.0, /usr/lib/libsystem. dylib (Compatibility version 1.0.0, current version 1252.0.0)Copy the code
Third-party libraries in CocoaPods are usually loaded as static libraries, so using otool -l [filename] you won’t see the libraries in Pod. But if you add use_frameworks to your Podfile! , that is, when loaded in dynamic library mode, you will see, as shown above.
Finally, get the address of the application’s main function and return.
6.Rebase & Bind
These two processes occur not after the _main() method returns above, but rather in the “Link main executable” step of the previous section.
In order to ensure the application security, Apple uses two technologies: ASLR (Address Space Layout Randomization) and Code Sign.
ASLR stands for “address space layout randomization”. When the App starts, the application is mapped to a logical address space. If the address is fixed, it is easy to calculate the function address based on the address + offset, which is attacked. ASLR makes this address random, preventing an attacker from directly locating the attack code.
A Code sign is a Code signature. Apple uses two layers of asymmetric encryption to ensure secure App installation. When Code Sign is done, it is encrypted for each page so that when dyLD is loaded, it can be verified independently for each page.
Because the address is random using ASLR, you need to add an offset to the true method address. Call a method whose address may belong to a Mach-O file or to another Mach-O file.
Rebase fixes the internal symbolic address, that is, the pointer to the resource inside the current Mach-O file, by adding an offset.
Bind fixes the external symbolic address, that is, the pointer to the external Mach-O file. This process requires querying the symbol table to point to other Mach-O files, which can be time-consuming.
Here’s one of the official images:
In short, the address is wrong when loading the dynamic library in the previous step.
At this point, mach-O loading is done, and it’s time for iOS.
7.Objc Setup
Objc is a dynamic language, and this step mainly loads Runtime stuff. I will mainly do the following things:
- Register the related classes in the global table.
- Register the methods in Category and Protocol with the corresponding classes.
- Make sure that the Selector is unique.
This step deals with custom classes and methods. The Runtime initialization of most system classes is already done in Rebase and Bind.
8.Initializers
This step does some class initialization. This is a recursive process that initializes the dependent dynamic libraries and then initializes your own custom classes. The main things to do are:
- Call the Objc class
+[load]
Methods. - Call C/C++ marked as
__attribute__(constructor)
Methods. - C++ static global ratio variable creation of non-primitive types.
Swift has eliminated the +load method and officially recommends using the Initialize method to reduce App startup time.
9.Main
Finally, we arrive at the main() method.
C-based programs typically start with the main() method, which iOS automatically creates for you. The code is simple:
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil.NSStringFromClass([AppDelegate class])); }}Copy the code
The UIApplicationMain method used here is declared as follows:
UIKIT_EXTERN int UIApplicationMain(int argc, char * _Nonnull * _Null_unspecified argv, NSString * _Nullable principalClassName, NSString * _Nullable delegateClassName);
Copy the code
- Argc and argv are passed directly to UIApplicationMain for processing.
- PrincipalClassName Specifies the class name of the application. This class must be
UIApplication
Type or a subclass of it. If nil, it is usedUIApplication
Class. - DelegateClassName, specifies the application proxy class. This class must be followed
UIApplicationDelegate
The agreement. - UIApplicationMain is created based on principalClassName
UIApplication
Object, and creates a Delegate object based on delegateClassName, which is assigned toUIApplication
Object’s delegate property. - The App is then placed in the Main Run Loop environment to respond to and process user interaction events.
About some methods in the AppDelegate:
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// The notification process has started, but has not finished displaying.
return YES;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// The program is ready to run. Last chance to operate before the page is displayed.
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// App loses focus and enters inactive state. The main examples are: call, some system pop-ups, double click the home button, pull down to show the system notification bar, pull up to show the system control center, etc.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// App goes to the background.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// The App enters the foreground. Cold starts do not receive this notification.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// App gets focus and is active. Both cold and hot starts receive this notification.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// When an application is about to exit, you can save data and some cleanup before exit in this method.
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
// Received a memory warning, free some memory.
}
@end
Copy the code
10.One more thing
So dyLD3, which I talked about in more detail, is here. Dyld3 is a new dynamic loader introduced by WWDC 2017. Pair with DYLD2 as shown below:
The difference between the two, popular point is: dyLD2 all the process is carried out at the start, each start will talk about all the process again; Dyld3 is divided into two parts, the upper part of the dashed line is executed when the App is downloaded, installed and updated and the result is written to the cache, and the lower part of the dashed line is executed every time the App is started.
This reduces dyLD loading steps and speeds up APP startup time. Dyld3 is currently only available in Apple apps, not developers. It should catch on later.
According to the above analysis process, we can generally summarize the following aspects if we want to optimize the startup of App:
- Reduce the introduction of dynamic libraries. If it is an internal custom component, you can combine some similar components into one.
- To reduce Rebase & Bind time, reduce
__DATA
The number of Pointers in. - To reduce Runtime registration time, reduce categories, and reduce useless classes and selectors.
- Try not to
+[load]
Write something in the method, subtract__atribute__((constructor))
To reduce non-basic C++ static constant creation. - Initialize some third-party libraries as they are used, lazy load, don’t put them all in an AppDelegate.
- Use the Swift.
Layer Render phase
I’ve done a lot of prep work, and I’ve reached the rendering display.
The layout process of layers (in this case, automatic layout) is mainly divided into three steps: setting constraints, updating layout, and rendering view. I’m going to talk about it in terms of the view Controller lifecycle.
1. View layout process
Update Cycle
When the program starts, the App will be placed in the Main Run Loop to respond to and process user interaction events. RunLoop is simply a loop that lasts as long as the App is not killed. Each cycle can be thought of as an iterative cycle in which user interaction events are processed and handled accordingly. After the various events have been processed, the control flow returns to the Main Run Loop and updates the view, which then moves to the next Loop. The whole process is shown below:
During the Update Cycle phase, the view is redrawn based on the calculated new frame. This process is fast, so the user doesn’t feel the lag. Because views are updated periodically, sometimes changing constraints, adding views, or modifying frames does not immediately redraw the view. This process will be described in more detail.
The constraint
The frame of a view contains the position and size of the view. The frame (and the current coordinate system) can be used to determine the position of the view. The essence of constraints is to set up a series of relations, which will be converted into a series of linear equations when calculating the layout. X, Y,width and height can be obtained by solving the linear equations, so as to determine the view position. This stage, from subview to Super View, prepares messages for the next stage of the layout.
updateConstraints()
This method is used to dynamically change view constraints in automatic layout. In general, this method should only be overridden and should not be called manually. During development, some static constraints can be set in the view initialization method or in the viewDidLoad() method; For some dynamic constraints, for example, UILabel sometimes needs to change the size with the word count of the text, and dynamic modification constraints need to be modified. In this case, you can override this method and write the code of dynamic modification constraints in the secondary method.
There are also actions that mark the view and trigger this method automatically in the next Update cycle:
- Enable/disable constraints.
- Change the size or priority of the constraint.
- Change the view hierarchy.
setNeedsUpdateConstraints()
If you want your view to be bound to call the updateConstraints() method in the next Update Cycle, you can call this method to mark your view, The updateConstraints() method is called if necessary in the next Update Cycle.
I say “if necessary” because if the system detects no change in the view, this method will not be called, even if it is flagged, to avoid performance cost. So it’s flagged, just telling the system to check if it wants to update the constraint. Here are some ways to do the same.
updateConstraintsIfNeeded()
If you don’t want to wait until the end of the Run loop, when you enter the Update cycle, to check the tag and update the constraint. You can call this method if you want to check the marked view immediately and update the constraints. Again, calling this method checks only those views that are tagged and, if necessary, calls the updateConstraints() method.
invalidateIntrinsicContentSize()
Some views (such as UILabel) have the intrinsicContentSize property, which is the inherent size based on the view content. You can also by overloading to customize the size, overloading, you need to call invalidateIntrinsicContentSize () method to mark intrinsicContentSize has expired, need to be the next update to recalculate the cycle.
layout
The next step is the layout after calculating the size and location of the view based on the constraints. This section is from the super view to the subview, setting the view’s center and bounds using the size and position calculated in the previous step.
layoutSubviews()
This method repositions and resizes the view and its subviews. This method is expensive because it handles the layout of the current view and its view, and calls the view’s layoutSubviews(), layer by layer. Again, this method should only be overridden and should not be called manually. Override this method when you need to update the frame view.
Some operations may trigger this method, and indirect triggering is much less costly than a manual call. This method can be triggered in the following situations:
- Modify the view size.
- Add A View.
- UIScrollView rolling.
- Device rotation.
- Update view constraints
These situations can either tell the system view that the frame needs to be recalculated and call layoutSubviews(), or trigger the layoutSubviews() method directly.
setNeedsLayout()
This method flags the view and tells the system that the view layout needs to be recalculated. In the next Update cycle, the layoutSubviews() method of the view is called. Again, the system will only call it if necessary.
layoutIfNeeded()
SetNeedsLayout is a tagged view, and the layoutSubviews() method may be called in the next Update cycle. LayoutIfNeeded () tells the system to call layoutSubviews() immediately. Of course, calling layoutIfNeeded() only checks to see if the view needs to be refreshed, and calls layoutSubviews() if necessary. If you call layoutIfNeeded() twice in the same Run loop and there is no view update between the two calls, layoutSubviews() will not fire the second time.
This method is useful when doing constraint animations. Call this method to ensure that the other views have been updated before animation. This method is then called to animate to the new state after setting the new constraint in the animation block. Such as:
[self.view layoutIfNeeded];
[UIView animateWithDuration:1.0 animations:^{
[self changeConstraints];
[self.view layoutIfNeeded];
}];
Copy the code
Apply colours to a drawing
The display of a view includes colors, text, images, and Core Graphics drawings. Similar to the constraint and layout steps, there are methods to refresh the render. This process is from the super view to the subview.
draw(_:)
UIView’s draw method (drawRect in OC) is used to draw the contents of the view. It only works on the current view, not its subviews. Again, this method should be triggered by another method and not called manually.
setNeedsDisplay()
This method is similar to setNeedsLayout() in the layout. Calling this method marks the view, and then the next Update Cycle system iterates through the marked view, calling its draw() method to redraw it. Most UI components, if updated, are marked and redrawn in the next Update cycle. You generally do not need to call this method explicitly.
This step has no method like layoutIfNeeded() to refresh immediately. It usually doesn’t matter if you wait until the next Update cycle.
The three links
The layout process is not one-way, but rather an iterative process of constraint-layout. It is possible for the layout process to affect constraints that trigger updateConstraints(). Just determine the layout, decide if you need to redraw it, and show it off. After this round is complete, the next runloop is entered. Their general process is as follows:
The methods of the three processes mentioned above are similar and confusing to remember. You can compare your memory by the following table:
Methods effect | The constraint | layout | Apply colours to a drawing | |
---|---|---|---|---|
Refresh method, can be overloaded, not directly called | updateConstraints | layoutSubviews | draw | |
Mark the refresh method so that the view invokes the refresh method in the next Update cycle | setNeedsUpdateConstraints invalidateIntrinsicContentSize |
setNeedsLayout | setNeedsDisplay | |
updateConstraintsIfNeeded | layoutIfNeeded | |||
Trigger the refresh method operation | Enable/disable constraints Change the size or priority of the constraint Changing the view hierarchy |
Modifying view size Add A View UIScrollView rolling Rotating equipment Update view constraints |
Modifying view bounds |
2.View Controller lifecycle
When looking for a job, schools are often asked about the VC life cycle. I’ve been asking this question a lot lately when INTERVIEWING other people. Neither I, when I was recruited, nor anyone else I interviewed, even if they had worked for three or five years, could answer this question well.
This is a basic problem, not too technically difficult, and should be mastered.
Single View Controller lifecycle
Describe the individual View Controller lifecycle in method call order as follows:
-
Called when the load class is loaded, before the main function.
-
The Initialize class is called the first time it is initialized, after the main function.
-
Class initializes the associated method [initWithCoder:] when called using storeBoard. [initWithNibName: bundle:] when using custom NIB files. There are other init methods that are called when you normally initialize a class.
-
LoadView starts loading the view, there is no view until then. Unless manually called, it will only be called once during the View Controller lifecycle. in
-
ViewDidLoad View Controller is only called once in its lifetime. Class member variables, subviews, and other data are initialized in this method.
-
The viewWillAppear view is called just before it is displayed.
-
ViewWillLayoutSubviews are going to lay out the subviews.
-
ViewDidLayoutSubviews has completed the subview layout, and the first time I get the frame of the view. Any code that depends on layout or size should be placed in this method. In the previous method, the view didn’t have a layout, frame was all 0; In later methods, the layout or position variables may change due to some modification.
-
The viewDidAppear view shows the completion of the call.
-
Called when the viewWillDisappear view is about to disappear.
-
ViewDidDisappear is called when the view has disappeared.
-
Called when the Dealloc View Controller is released.
Each View Controller has its own method call timing for transitions
Different transition mode, between the two VC method call order is different. The common methods are as follows:
-
Navigation
Push the operation
- New viewDidLoad
- Current viewWillDisappear
- New viewWillAppear
- New viewWillLayoutSubviews
- New viewDidLayoutSubviews
- Current viewDidDisappear
-
New viewDidAppear
Pop operation (New of previous step becomes Current here, same below)
-
Current viewWillDisappear
- New viewWillAppear
- Current viewDidDisappear
- New viewDidappear
-
Page Curling (UIPageViewControllerTransitionStylePageCurl)
Normal Normal page turning operations
- New viewDidLoad
- Current viewWillDisappear
- New viewWillAppear
- New viewWillLayoutSubviews
- New viewDidLayoutSubviews
- Current viewDidDisappear
-
New viewDidAppear
Canceled in the middle of saying Canceled
-
New viewWillAppear
- New viewWillAppear
- Current viewWillDisappear
- New viewWillLayoutSubviews
- New viewDidLayoutSubviews
- New viewWillDisappear
- Current viewWillAppear
- New viewDidDisappear
- Current viewDidAppear
-
Page Scrolling (UIPageViewControllerTransitionStyleScroll)
Normal Slide to turn pages
- New viewDidLoad
- New viewWillAppear
- Current viewWillDisappear
- New viewWillLayoutSubviews
- New viewDidLayoutSubviews
- New viewDidAppear
-
Current viewDidDisappear
Canceled in the middle of a Canceled flight
-
New viewWillAppear
- Current viewWillDisappear
- Current viewWillAppear
- Current viewDidAppear
- New viewWillDisappear
- New viewDidDisappear
As you can see, the life cycle method call sequence is different between the two View cotrollers for different special methods. Is it very confusing, do not memorize, just need to know this case, in the development is to pay attention to it.
conclusion
The above is basically all the process of a project from compilation to startup. Understanding this process can help us develop better. Because the article is quite long, there are inevitably some mistakes. Please point out if you find it and I will revise it as soon as possible.
reference
- objc-Issues-Build-Tools
- In-depth understanding of the iOS App startup process
- XNU, DYLD Source Analysis of Mach-O and Dynamic Library loading Process (PART 1)
- XNU, DYLD source analysis, Mach-O and dynamic library loading process (part 2)
- Demystifying iOS Layout
- The Inconsistent Order of View Transition Events