Component Size Analysis and Version Statistics for iOS Apps Using Linkmap and Static Library Inspection
This article describes how 58.com analyzes the code and resource size of each business component in its iOS app by parsing linkmap files and static libraries, builds version‑wise statistics, and compares component volume changes across releases to support efficient parallel development and app slimming.
Background: As business scenarios evolve, the client architecture of large iOS applications must support parallel development, which improves efficiency but introduces management and maintenance challenges. 58.com needed a fast, convenient way to measure the size of each business module within an IPA and to generate version statistics for component size changes.
Common technical approaches: (1) Integrate components into an empty project and compare package sizes before and after compilation – inefficient for 58.com due to fine‑grained code splitting and many components. (2) Analyze the linkmap file generated during linking – a mainstream method that reveals component size by parsing executable sections.
Linkmap analysis: The file lists executable paths, architecture info, target file identifiers, section directories, and detailed section data. By extracting __TEXT and __DATA sections from the Mach‑O files, the size contributed by each component can be determined. However, this method suffers from poor extensibility (requires a mapping table for absolute paths), inability to count resource sizes, and link order bias where early‑linked components absorb shared code.
58.com’s technical solution: Using CocoaPods, each component is a pod with both static‑library and source variants. The workflow extracts the arm64 slice of each static library (via lipo -thin ), parses Mach‑O headers, sections, and symbols to compute code size, then traverses component directories to sum resource sizes (including compiled nibs and compressed xcassets via actool ). The results are stored in a plist and later merged into Excel files using libxl.
Static library composition: A static library is a multi‑architecture Mach‑O archive containing a header, symbol table, string table, and multiple object files. Each object file includes its own Mach‑O header, sections, and optional debug or bitcode data. The analysis must handle multiple architectures, bitcode, debug info, and symbol stripping, as well as alignment padding and nested static libraries.
Linker workflow: The linker performs address space reallocation, symbol resolution, and relocation. Space allocation (either sequential stacking or similar‑section merging) significantly impacts final binary size. 58.com simulates the merging process to estimate the post‑link size of each component.
Implementation challenges addressed: • Byte‑alignment differences between static libraries and object files. • Offsets of target files within the archive versus their own internal offsets. • Extraction of Chinese strings stored in __ustring sections (UTF‑16LE). • Handling single‑object static libraries and nested archives recursively. • Filtering irrelevant files (demos, docs, .git) during resource traversal. • Aggregating components by business module for reporting.
Result: The solution has been applied to five releases of 58.com’s app, achieving volume error rates within 10% for total size and 5% for incremental changes. It provides a convenient command‑line tool that accepts any file path, outputs component code and resource sizes, and generates comparative Excel reports, supporting continuous delivery pipelines.
Conclusion: The approach offers easy usage, low maintenance, accurate resource accounting, unbiased component size estimation, and extensibility for future integration into automated delivery systems.
struct segment_command {
uint32_t cmd; /* LC_SEGMENT */
uint32_t cmdsize; /* includes sizeof section structs */
char segname[16]; /* segment name */
uint32_t vmaddr; /* memory address of this segment */
uint32_t vmsize; /* memory size of this segment */
uint32_t fileoff; /* file offset of this segment */
uint32_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
struct segment_command_64 {
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize; /* includes sizeof section_64 structs */
char segname[16]; /* segment name */
uint64_t vmaddr; /* memory address of this segment */
uint64_t vmsize; /* memory size of this segment */
uint64_t fileoff; /* file offset of this segment */
uint64_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
//获取mach-o header
mach_header_64 mhHeader;
tmpRange = NSMakeRange(range.location, sizeof(mach_header_64));
[fileData getBytes:&mhHeader range:tmpRange];
case S_CSTRING_LITERALS:{
NSArray *array = [self read_strings:secRange fixlen:sectionHeader.size fromFile:fileData];
[objcMachO.sections setObject:array forKey:sectionName];
}58 Tech
Official tech channel of 58, a platform for tech innovation, sharing, and communication.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.