Mobile Development 14 min read

How to Shrink iOS App Size: Resource & Binary Optimization Techniques

This article explains practical methods to reduce iOS app package size by removing unused resources, analyzing Xcode LinkMap files to identify dead code and selectors, slimming protobuf implementations, and applying compiler and build‑setting optimizations.

WeChat Client Technology Team
WeChat Client Technology Team
WeChat Client Technology Team
How to Shrink iOS App Size: Resource & Binary Optimization Techniques

Premise

WeChat has accumulated redundant code and unused resources after many version iterations; ARC support added in October increased the executable size by about 20%, and the mandatory 64‑bit requirement doubled the binary, making package‑size optimization urgent.

Resource slimming

Resource slimming removes useless assets and compresses needed ones. Unused resources are those present in the project but never referenced in code; they can be found by searching for the resource name (excluding @2x/@3x) in the source. Compression focuses on lossless PNG optimization using ImageOptim or the compress command; lossy compression is discouraged.

Xcode's Link Map File

The LinkMap file, generated when Write Link Map File is enabled, describes the composition of the executable, including code (__TEXT) and data (__DATA) sections. It can be found at the path set in the project settings after a build.

Each LinkMap consists of three parts (example shown for WeChat):

1. Object files:

[ 0] linker synthesized [ 1] /xxxx/WCPayInfoItem.o [ 2] /xxxx/GameCenterFriendRankCell.o [ 3] /xxxx/WloginTlv_0x168.o ...

2. Sections:

This part lists the segment table, showing each segment’s offset, size, type, and name (e.g., __text for machine code, __cstring for string constants). See Apple’s “OS X ABI Mach-O File Format Reference” for details.

3. Symbols:

# Address Size File Name 0x100005A50 0x00000074 [ 1] +[WCPayInfoItem initialize] 0x10231C120 0x00000018 [ 1] literal string: I16@?0@"WCPayInfoItem"8 0x10252A41A 0x0000000E [ 1] literal string: WCPayInfoItem ...

Executable file slimming

LinkMap analysis helps locate optimization points in the binary.

1. Find unused selectors

Objective‑C’s dynamic nature includes all classes and methods in the binary. By extracting all selectors from the __TEXT.__text section with the regex ([+|-][.+\s(.+)]) and comparing them with selectors referenced in the __DATA.__objc_selrefs segment (via otool -v -s __DATA __objc_selrefs), unused selectors can be identified. System‑API protocols should be whitelisted.

2. Find unused Objective‑C classes

Search for patterns like [ClassName alloc, ClassName *, or [ClassName class] in the code, or use otool to list all classes in __DATA.__objc_classlist and __objc_classrefs; the difference yields unused classes.

3. Scan duplicate code

Tools such as Simian can detect duplicated code, though the results may be noisy and refactoring can be costly.

4. Protobuf slimming

Google’s protobuf generates verbose code. By abstracting common serialization methods into a base class and letting derived classes provide field metadata, the binary size can be reduced.

field number

field label (optional, required, repeated)

wire type (double, float, int, etc.)

whether packed

repeated data type

<code>typedef struct { Byte _fieldNumber; Byte _fieldLabel; Byte _fieldType; BOOL _isPacked; int _enumInitValue; union { __unsafe_unretained NSString* _messageClassName; __unsafe_unretained Class _messageClass; // ClassName对应的Class IsEnumValidFunc _isEnumValidFunc; // 检测枚举值是否合法函数指针 }; } PBFieldInfo;</code>

Unused selectors reveal protobuf property getters/setters that are never called. Replacing @synthesize with @dynamic and removing unused ivars further shrinks the binary.

Base class adds an ivarValues array (similar to objc_class ivars) to store property values as objects; primitives are wrapped in NSValue.

Override methodSignatureForSelector: to return signatures for getters/setters.

Override forwardInvocation: to handle dynamic getter/setter calls.

Override setValue:forUndefinedKey: and valueForUndefinedKey: to avoid KVO crashes.

Performance tweaks: cache hash of property names, store types, replace std::map with arrays, use MRC instead of ARC where appropriate.

<code>class PBClassInfo { public: PBClassInfo(Class cls, PBFieldInfo* fieldInfo); ~PBClassInfo(); public: unsigned int _numberOfProperty; std::string* _propertyNames; size_t* _propertyNameHashes; std::string* _getterObjCTypes; std::string* _setterObjCTypes; PBFieldInfo* _fieldInfos; }; @interface WXPBGeneratedMessage () { uint32_t _has_bits_[3]; // 最多96个属性,表示属性是否有赋值 int32_t _serializedSize; PBClassInfo* _classInfo; id* _ivarValues; } - (NSMethodSignature*) methodSignatureForSelector:(SEL) aSelector; - (void) forwardInvocation:(NSInvocation*) anInvocation; - (void) setValue:(id) value forUndefinedKey:(NSString*) key; - (id) valueForUndefinedKey:(NSString*) key; @end</code>

After removing redundant code, the GameResourceReq protobuf class shrank from 127 lines to 8 lines, saving 8.8 MB in the binary and 2.5 MB by using @dynamic for properties.

<code>message GameResourceReq { required BaseRequest BaseRequest = 1; required int32 PropsCount = 2; repeated uint32 PropsIdList = 3[packed=true]; }</code>
<code>// Old implementation @implementation GameResourceReq @synthesize hasBaseRequest; @synthesize baseRequest; @synthesize hasPropsCount; @synthesize propsCount; @synthesize mutablePropsIdListList; @dynamic propsIdList; - (id) init {...} - (void) SetBaseRequest:(BaseRequest*) value {...} - (void) SetPropsCount:(int32_t) value {...} - (NSArray*) propsIdListList {...} - (NSMutableArray*)propsIdList {...} - (void)setPropsIdList:(NSMutableArray*) values {...} - (BOOL) isInitialized {...} - (void) writeToCodedOutputStream:(PBCodedOutputStream*) output {...} - (int32_t) serializedSize {...} + (GameResourceReq*) parseFromData:(NSData*) data {...} - (GameResourceReq*) mergeFromCodedInputStream:(PBCodedInputStream*) input {...} - (void) addPropsIdList:(uint32_t) value {...} - (void) addPropsIdListFromArray:(NSArray*) values {...} @end</code>
<code>// New implementation @implementation GameResourceReq PB_PROPERTY_TYPE baseRequest; PB_PROPERTY_TYPE opType; PB_PROPERTY_TYPE brandUserName; + (void) initialize { static PBFieldInfo _fieldInfoArray[] = { {1, FieldLabelRequired, FieldTypeMessage, NO, 0, ._messageClassName = STRING_FROM(BaseRequest)}, {2, FieldLabelRequired, FieldTypeInt32, NO, 0, 0}, {3, FieldLabelRepeated, FieldTypeUint32, NO, 0, 0}, }; initializePBClassInfo(self, _fieldInfoArray); } @end</code>

5. Compiler option optimization

Set Strip Link Product to YES – reduces WeChatWatch binary by 0.3 MB.

Enable Make Strings Read‑Only – cuts ~3 MB.

Disable C++ and Objective‑C exceptions (set to NO, add -fno-exceptions) – saves 27 MB (17.3 MB from __gcc_except_tab, 9.7 MB from __text). Exceptions can be re‑enabled per file if needed.

6. Other explored ways

iOS 8 Embed‑Framework: extract common code from WeChatWatch, ShareExtension, and main app to reduce binary by >5 MB (requires iOS 8+).

iOS 9 App Thinning: lets the App Store deliver only the needed architecture and resources per device, reducing installed size.

7. Establish monitoring

By analyzing LinkMap files across versions, the size contribution of each module can be tracked, enabling detection of growth spikes.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

iOSLinkMapResource Compressionapp size optimization
WeChat Client Technology Team
Written by

WeChat Client Technology Team

Official account of the WeChat mobile client development team, sharing development experience, cutting‑edge tech, and little‑known stories across Android, iOS, macOS, Windows Phone, and Windows.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.