Bo2SS

Bo2SS

1 Basic Part (Middle)

Today I want to share: Basics (Debugging, Testing, and Release Phase).

image

06 | How to Achieve Fast Compilation and Debugging Through Dynamic Library Injection?#

Although we can speed up compilation by compiling some code into binary and integrating it into the project to avoid full compilation every time, we still need to restart the App and go through the debugging process again for each compilation.

So how can native code achieve dynamic fast debugging? Let's first look at the tools that implement dynamic debugging:

  1. Swift Playground, any code modification can be reflected in real-time;

  2. Flutter Hot Reload, Flutter will check the code that has been modified since the last compilation after clicking the reload button in the VSCode debug bar, recompile the involved code libraries, including the main library and its related libraries. These recompiled libraries will be converted into kernel files sent to the Dart VM, which will reload the new kernel files, triggering the Flutter framework to rebuild, re-layout, and redraw all Widgets and Render Objects.

So, what should the Cocoa framework do to achieve fast debugging?

Injection for Xcode#

The Injection tool can dynamically execute Swift or Objective-C code in a running program to speed up debugging without requiring the program to restart.

Working Principle: Injection listens for changes in source code files. If a file is modified, the Injection Server will execute rebuildClass to recompile and package it into a dynamic library, which is a .dylib file, and then use the writeString method to notify the running App via Socket. The principle diagram is as follows:

Principle diagram of Injection - "Geek Time"

07 | Comparison of Static Analysis Tools: OCLint, Clang, and Infer#

First, let's introduce three commonly used complexity metrics that static analysis tools can use to analyze whether code needs optimization and refactoring.

  1. Cyclomatic Complexity, which refers to the complexity of traversing a module, determined by branching statements like if, for, and operators like &&, ||, etc. Generally, a cyclomatic complexity above 11 is considered very high, and refactoring should be considered, otherwise, it will be difficult to maintain due to an excessive number of test cases.
  2. NPath Complexity, which refers to the number of all possible execution paths of a method. Generally, if it exceeds 200, complexity reduction should be considered.
  3. NCSS (Non-commenting Source Statements) Complexity, which refers to the number of source code lines excluding comments. A high NCSS indicates that a method or class is doing too much, affecting code maintainability and readability, and should be split or refactored. Generally, a method should have fewer than 100 lines, and a class should have fewer than 1000 lines.

Let me state two major drawbacks of using static analysis tools in advance:

  1. It takes longer time. Compared to the compilation process, static analysis itself includes the most time-consuming IO and syntax analysis stages. When deep program errors are found, it will also analyze the current method, parameters, and variables in conjunction with the entire project code.
  2. It can only check for specifically designed, searchable errors. For specific types of error analysis, developers need to rely on their own abilities to write and add some plugins.

Now let's formally compare three commonly used static analysis tools:

  • OCLint is a static analysis tool developed based on Clang Tooling.
  • The Clang Static Analyzer is an open-source tool developed in C++, part of the Clang project, built on top of Clang and LLVM.
  • Infer is an open-source static analysis tool developed by Facebook based on the OCaml language.
Advantages and Disadvantages of Static Analysis Tools
Static Analysis ToolAdvantagesDisadvantages
OCLintMany checking rules, strong customizationHigh customization, less usability
Clang Static AnalyzerHigh integration with Xcode, supports command lineFew checking rules, coarse granularity
InferHigh efficiency, supports incremental analysis, can also analyze in small scopeMedium customization

Overall, Infer performs best in terms of accuracy, performance efficiency, rules, extensibility, and usability, making it worth a try.

References:

08 | How to Use Clang to Improve App Quality?#

In addition to the previously mentioned Clang static analysis tool, a system platform can be developed based on Clang to ensure App quality, such as CodeChecker, which has capabilities for code incremental analysis, code visualization, and code quality report generation, or online web code navigation tools, such as Mozilla's DXR, which facilitate operations and problem analysis on portable devices.

What is Clang?#

Let's first look at the complete compilation flowchart for iOS development:

image

Among them, the black block on the left, Clang, is the compilation frontend for C, C++, and Objective-C, while Swift has its own compilation frontend, the SIL optimizer.

Clang is developed based on C++, and its source code quality is very high, with many aspects worth learning, such as clear directory structure, good functional decoupling, clear classification for easy combination and reuse, unified and standardized code style, and abundant comments for readability. Moreover, it not only has a huge amount of engineering code but also many tools, although the relationships among them are complex. Fortunately, Clang provides a highly usable black-box Driver that encapsulates frontend commands and toolchain commands, greatly enhancing its usability.

What Does Clang Do?#

  1. It performs lexical analysis on the code, splitting it into Tokens.

Token types are divided into 4 categories:

  • Keywords: Syntax keywords, such as if, else, while, for, etc.;
  • Identifiers: Variable names;
  • Literals: Values, numbers, strings;
  • Special symbols: Symbols for addition, subtraction, multiplication, division, etc.
  1. Then it performs syntax analysis, combining Tokens into semantic generation nodes, thus forming an abstract syntax tree (AST).

Nodes are mainly divided into three types: Type, Decl (declaration), and Stmt (statement). By extending these three types of nodes, an infinite variety of code forms can be represented.

What Capabilities Does Clang Provide?#

Clang provides infrastructure for tools that need to analyze code syntax and semantic information: LibClang, Clang Plugin, and LibTooling.

LibClang#

LibClang can access Clang's higher-level abstract capabilities, such as obtaining all Tokens, traversing the syntax tree, code completion, etc. Since the API is very stable, updates to Clang versions have little impact on it. However, LibClang cannot fully access Clang AST information.

Clang Plugins#

Clang Plugins are dynamic libraries loaded by the compiler at runtime, allowing you to perform operations on the AST and integrate them into the compilation process, becoming part of the compilation.

LibTooling#

LibTooling is a C++ interface that allows you to write standalone syntax checking and code refactoring tools.

Compared to LibClang, its interface is not as stable, requiring attention to AST API upgrades, and it cannot be used out of the box; compared to Clang Plugins, it cannot affect the compilation process. However, it can fully control Clang AST and can run independently.

Related materials:

Tutorial for building tools using LibTooling and LibASTMatchers (Building a language conversion tool using LibTooling)

09 | How to Implement a Non-intrusive Tracking Solution?#

In iOS development, tracking can solve two major problems: 1) Understanding user behavior in the App, 2) Reducing the difficulty of analyzing online issues.

Common tracking methods mainly include three types:

  1. Code tracking: Writing code to implement tracking. The characteristic is precise tracking and easy debugging, but the workload for development and maintenance is relatively large.

  2. Visual tracking: Visualizing the addition and modification of tracking. The characteristic is an improved tracking experience.

  3. No tracking: More accurately, it is "full tracking," where tracking code does not appear in business code. The characteristic is easy management and maintenance, but it can only address general tracking needs.

Both visual tracking and no tracking belong to non-intrusive tracking solutions. So how can they be implemented?

Runtime Method Swizzling#

The three most common tracking methods in iOS development are tracking page entry counts, page stay time, and click events.

For these three common situations, we can insert tracking code using runtime method swizzling technology to achieve non-intrusive tracking:

  1. First, write a runtime method swizzling class SMHook, adding the method hookClass:fromSelector.

  2. Determine the method and identifier to be replaced based on the tracking type, and use SMHook for method swizzling in the +load() method.

Below is the Non-intrusive Tracking - Runtime Method Swizzling Information Reference Table for reference:

Non-intrusive Tracking - Runtime Method Swizzling Information
Tracking TypeSwizzled MethodIdentifier
Page entry counts, page stay timeUIViewController lifecycleNSStringFromClass([self class])
UITableView (special case)setDelegateNSStringFromClass([self class])
Click eventsClick event methodNSStringFromSelector(action) + NSStringFromClass([target class])
Gesture eventsinitWithTarget:action:NSStringFromSelector(action) + NSStringFromClass([target class])

Unique Event Identifier#

Different instances of the same UIButton under a view cannot be distinguished solely by the combination of "action selector name" + "view class name." At this point, we need a unique identifier to differentiate different events.

Each subview will have its own index in the parent view, which can be combined to generate a unique event identifier. Special cases: UITableViewCell can determine the uniqueness of each Cell through indexPath; UIAlertController can determine its unique identifier through content...

However, the accuracy of the unique event identifier is difficult to guarantee (for example, if the view hierarchy is changed at runtime, due to frequent updates in demand iterations), so the non-intrusive tracking solution through runtime method swizzling is generally only used in places where functions and views are stable.

Consideration: Is it possible to use the Clang AST interface to traverse the AST at build time to insert the required tracking code?

10 | Package Size: Reducing from Resource and Code Levels#

Apple has strict limits on the size of iOS Apps: Download size (200 MB) exceeding this limit will hinder users from downloading the App over cellular networks, affecting new user conversion; Executable file text segment size (iOS 7-: 80MB, iOS 7 - 8: 60MB, iOS 9+: 500MB) exceeding this limit will lead to App review rejection, affecting App listing; additionally, a large App package size will also affect user upgrade rates.

Therefore, controlling package size is crucial. Next, let's introduce some common methods for reducing package size.

Official App Thinning#

App Thinning is a new technology introduced by Apple to improve the App download process, mainly addressing the high data consumption for users downloading Apps and the excessive storage space occupied on iOS devices.

Two thinning points:

  1. Image resource size. Match different sizes of resources based on iOS device screen sizes. For example, iPhone 6 will only download 2x resolution image resources, while iPhone 6 Plus will only download 3x resolution image resources.
  2. Chip instruction set architecture files. Users will only download a chip instruction set architecture file suitable for their device when downloading the App, as the App will also have optimized versions for different chip architectures, such as 32-bit and 64-bit.

Three thinning methods:

  1. App Slicing: After you upload the App to iTunes Connect, the App is sliced to create different variants suitable for different devices.
  2. On-Demand Resources: Mainly serves multi-level scenes for games, downloading resources for subsequent levels based on user progress, and deleting resources for levels that have already been cleared, thus reducing the initial installation size of the App.
  3. Bitcode: Optimizes package size for specific devices, with minimal optimization.

How to use it?

Most of this work is done by Xcode and the App Store for you~

You only need to add the xcassets directory through Xcode (File > New File > Asset Catalog), and then add the 2x and 3x resolution images.

Chip instruction set architecture files are created in different variants based on the device by default, with each variant containing only the instruction set files needed for the current device.

Unused Image Resources#

Recommended open-source tool: LSUnusedResources, which can handle this by directly adding rules, effectively performing the following four steps:

  1. Use the find command to obtain all image resource files in the project;
  2. Use the find command and regex matching to find the resource names used in the source code;
  3. The difference between the two above gives the unused resources;
  4. Finally, you can delete the unused resources using the system class NSFileManager.

Image Resource Compression#

Recommended compression scheme: Convert to WebP format (a Google open-source project; high compression rate, supports both lossy and lossless compression modes; supports Alpha transparency and 24-bit color depth, avoiding the rough edges that may occur with PNG8 due to insufficient colors).

Related compression tools: cwebp (Google, command line), iSparta (Tencent, GUI).

Considerations: Displaying WebP images requires using libwebp for parsing (libwebp Demo), which may consume twice the CPU and decoding time compared to PNG, representing a trade-off between space and time.

Author's Suggestions:

  1. If the image size exceeds 100KB, consider using WebP;

  2. Otherwise, you can use TinyPng (web tool) or ImageOptim (GUI tool) for image compression.

Code Reduction#

The installation package of the App mainly consists of resources and executable files.

Having discussed resource reduction, let's move on to reducing the executable file, which involves finding and deleting unused code, similar to the four steps for finding unused image resources.

  1. First, use LinkMap to find the complete set of methods and classes;

To obtain LinkMap: Set Write Link Map File to Yes in the project file > Targets > Build Setting, then specify the Path to Link Map File, and you will get a LinkMap file after each compilation.

The composition of the LinkMap file is shown in the following diagram:

Main composition of LinkMap file - "Geek Time"

  • Object File: All files of the code project;
  • Section: The offset position and size of the code segment in the generated Mach-O file;
  • Symbols: Each method, class, block, and their sizes.
  1. Then, find the methods and classes that have been used; 3) Take the difference between the two to get the unused code; 4) Finally, after manual confirmation that the unused code can be deleted, proceed to delete it. There are three ways to choose from for this process:

A. Use MachOView software to view the Mach-O file.

In the Section information, __objc_selrefs contains the called methods, __objc_classrefs contains the called classes, and __objc_superrefs contains the classes that called super.

However, these do not include methods that are dynamically called at runtime (due to the dynamic nature of Objective-C), so secondary confirmation is needed.

B. Directly use AppCode software to find unused code.

Prerequisite: The amount of engineering code does not reach millions of lines.

After analyzing through AppCode > Code > Inspect Code, all unused code will be displayed in the Unused code section.

However, some situations may be misjudged as unused, requiring manual secondary confirmation, such as:

  • Methods called using performSelector, like [self performSelector:@selector(xxx)];
  • Parent class methods used in subclasses;
  • Classes declared at runtime, such as classes called by NSClassFromString, classes used without specifying class names like [[self class] xxx], and UITableView custom Cells registered using registerClass;
  • Properties accessed using dot notation;
  • Unused protocols defined in JSONModel...

C. Runtime check whether classes have actually been used.

After finding and deleting unused code through methods 1 and 2, there may still be unused code in the package, such as code used during static checks that may not even have an entry point in the online version, let alone be used by users.

In the ObjC runtime source code, there is a function that determines whether a class has been initialized, called isInitialized, and the result it returns is saved in the meta-class (the first << 29 bit of the class_rw_t structure flags).

When writing a runtime unused class checking tool, we can use this initialization information:

  • First, check all classes during the offline testing phase to find uninitialized classes;
  • Then, after going live, observe uninitialized classes across multiple versions;
  • After confirming that certain classes are truly unused, delete them.

11 | Hot Issues Q&A (1): Basic Module Q&A#

Dynamic Library Loading Methods#

There are two:

  1. Dynamically loaded by dyld when the program starts running. Dynamic libraries loaded by dyld need to be linked at compile time, and the bound addresses are determined after loading.
  2. Explicit runtime linking, which loads using the dynamic linker-provided APIs dlopen and dlsym at runtime. This method does not require participation in linking at compile time. However, Apps that load remote dynamic libraries in this way are not allowed to go live on the App Store by Apple, so they can only be used for offline debugging.

To what extent is it appropriate to learn assembly?

  • If your work does not involve reverse engineering and security fields, being able to understand assembly code is already very good;
  • If you want to learn assembly language, practice writing and debugging code while also using Xcode tools.

Reference material:

Mike Ash's “Dissecting objc_msgSend on ARM64”, which analyzes the objc_msgSend source code and details the ARM64 assembly code inside.

Additional recommendation:

Wang Yin's Blog

Only by mastering knowledge in a certain area can you think of using that knowledge to solve problems when encountering issues, so at least understand this knowledge first~

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