The original link: www.vadimbulavin.com/xcode-build… by Vadim Bulavin

Translation: CoderWangx

Each Swift program undergoes a series of transformations before running on a real device. This process is typically handled by the Xcode build system. In this article we’ll look at the various parts of the Xcode build system.

Problem description

Any computer system has two sides: software and hardware.

Hardware is the physical part of a computer, such as a monitor or keyboard. Hardware is usually controlled by software, which is a set of instructions that instruct the hardware how to work. Software is responsible for the choreography process, hardware is responsible for the actual execution of the work, both are indispensable.

As software engineers, we focus on the software part. However, the hardware does not directly understand code written with Swift, and it can only receive instructions in the form of electrical charges, with two levels called * ‘logic 0’ and ‘logic 1’ *.

Here’s the question: “How do I convert the Swift code into a form that the hardware accepts?” The answer is a language processing system.

Language processing system

A language-processing system is a collection of programs that can generate executable programs from a set of instructions written in any source language. This allows programmers to use high-level languages without having to write machine code, greatly reducing programming complexity.

The language processing system we use everyday in iOS or macOS development is called the Xcode build system.

Xcode builds the system

The main goal of the Xcode build system is to coordinate the execution of various tasks and ultimately produce an executable program.

Xcode runs many tools and passes dozens of parameters between them, dealing with execution order, parallelism, and so on. This is certainly not something you want to handle manually when writing your next Swift project.

Most language-processing systems, including the Xcode build system, consist of five parts:

  • Preprocessor
  • Compiler
  • Assembler
  • Linker (Linker)
  • Loader

They work together in the following way:

Let’s take a closer look at the steps.

Preprocessing pretreatment

The purpose of the preprocessing step is to transform the program into a form that can be supplied to the compiler. It replaces macros with concrete definitions, finds dependencies, and parses preprocessor instructions.

Macros are not allowed to be defined in Swift projects because there is no preprocessor in the Swift compiler. However, the Xcode build system partially complements this by configuring Active Compilation Conditions in the project build Settings.

Xcode resolves dependencies through the low-level build system LLBuild, which is open source and can be found on the Swift-llBuild page on Github for more information.

The Compiler Compiler

A compiler is a program that maps a source program in one language to a semantically equivalent target program in another language. In other words, the compiler converts Swift, Objective-C, and C/C++ code to machine code without losing the meaning of the former.

Xcode uses two different compilers: one to compile Swift and one to compile Objective-C, Objective-C++, and C/C++ files.

Clang is apple’s official C compiler, already open source: Swift-Clang.

Swiftc is a Swift compiler used by Xcode to compile and run Swift source code. I venture to assume that you have visited this link at least once: it is located in the Swift language repository.

The compiler stage is shown below:

A compiler consists of two main parts: a front end and a back end.

The front end splits the source program into separate parts, without any semantic or type information, using specific syntactic structures. The compiler then uses this structure to generate intermediate representations of the source program. The front end also creates and manages symbol tables to gather information about source programs.

A Symbol is the name of a piece of data or code.

Symbol tables store the names of variables, methods, and classes you name, and each symbol maps to a certain block of data.

Intermediate representation is called the Swift Intermediate Language (SIL) in the Swift compiler. The SIL will be used for subsequent analysis and code optimization. It is not possible to generate machine code directly from the Swift Intermediate language, so the SIL is transformed again into LLVM Intermediate Representation.

In the back-end phase, the above LLVM intermediate description is translated into a pool code.

Assembler Assembler

The assembler converts readable assembly code into relocatable machine code, generating a Mach-O file, which is basically a collection of code and data.

The terms in the above definition: machine code * and Mach-O files need further explanation.

Machine code is a digital language that represents a set of instructions that can be executed directly by the CPU. It is named redirectionable because no matter where the object file is in the address space, the instruction is executed relative to that space.

Mach-o files are a special file format in the iOS/macOS operating system for object files, executables, and libraries. It is a byte stream grouped in meaningful chunks, running on an ARM processor on an iOS device or an Intel processor on a Mac.

Would the Linker

The linker is a computer program that merges different object files and libraries to produce a Mach-O executable that can run on iOS or macOS systems. The linker accepts as input two types of files, namely object files from the assembly stage and different types of libraries (.dylib,.tbd,.a).

Careful readers may have noticed that both the assembler and the linker generate a Mach-O file as output. It’s a little bit different, right?

Object files from the assembly stage are not processed, and some contain missing parts that reference other object files or libraries. For example, if the printf method is used in your code, it is the linker that glue the symbol to the liBC library that implements the printf method. It uses the symbol table generated at compile time to resolve references across different object files and libraries.

You can already see the error “undefined symbol undefined” when building Swift projects with the above features in Xcode.

Loader Loader

Finally, as part of the operating system, the loader loads the program into memory and executes it. The loader allocates the memory needed to run the program and initializes the registers to their initial state.

conclusion

It is hard to underestimate the importance of language processing systems in software engineering. We can choose almost any high-level programming language, such as Swift or Objective-C, without having to write 0 and 1 binaries that hardware can understand. The language-processing system takes care of the rest, producing an executable program that can run on an iPhone, a Mac, or any other terminal.

As iOS/macOS developers, we use Xcode to build systems as part of our daily foundation work. Its main components are: preprocessor, Compiler, Assembler assembler, Linker linker, and loader. Xcode uses different compilers for Swift and Objective-C, swifTC and Clang, respectively.

Understanding the Xcode compilation process is essential for beginners and experienced developers alike.


Thank you for reading

If you like it, follow the original author on Twitter; If you find any problems or have better suggestions, please leave a comment or discuss them with me on Github.