今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

Serious Analysis of iOS Package Size Optimization

This article is published on the "Sohu Technology Products" public account Serious Analysis of iOS Package Size Optimization

iOS Package Size Analysis and Optimization#

Background#

Package size optimization is an inevitable issue encountered during project development. There are many articles online about package size optimization, and each article says something different. The author has previously optimized package size based on articles found online, but the results were unsatisfactory. Therefore, the author wants to summarize and organize their own package size optimization logic based on existing articles and knowledge, combined with their own understanding and practice. It is important not only to know how to change the package size but also to understand why these changes are effective, which led to this article.

Analysis#

To optimize the installation package size, it is essential to first clarify the factors that affect the installation package size. Previously, the author optimized the package size without much thought, and despite efforts, no significant results were achieved. Moreover, the author could not clearly explain what was done and why.

Later, it was concluded that before taking action, one should think and analyze first. It is necessary to consider the factors affecting the issue, list them out, and identify any gaps; then analyze these factors to determine which are controllable and which are not. For the controllable factors, one should consider how to optimize them, and for the uncontrollable factors, whether they can be avoided. It is best to use a mind mapping tool to record everything; then execute the steps according to the data organized in the mind map.

Looking back at the installation package size, the factors affecting it can be analyzed from three aspects: Xcode settings, resources, and code. How to optimize these aspects? And how to check the results of each optimization step?

First, let's address the optimization issue:

Optimizing Xcode's build settings affects the size of the generated package. By optimizing the Xcode compilation options, the generated IPA package can be made smaller, for example, by excluding debug symbols and removing exception support.

Resource file optimization involves not only image resources but also code resources and other imported resources. By analyzing the composition of the installation package, one can identify which parts are large or unreasonable and optimize them accordingly.

Code optimization can be achieved by generating a Link Map File to analyze the size of each file, combined with Mach-O file analysis for further optimization.

Next, how to check the results of each optimization step:

The results of each optimization step can be checked by analyzing the size of the generated IPA and its composition, comparing it with the initial IPA package size to visually assess the optimization results. However, one can go further by analyzing the composition of the IPA, comparing the optimized composition to see which specific part of the package was affected by each operation, leading to changes in package size. So, let's first look at what contents an IPA package includes, and then see which part of the IPA changes after each optimization step.

Composition of the Installation Package#

The IPA generated by iOS is essentially a compressed file, so you can change the .ipa suffix to .zip and decompress it. This will yield a Payload folder, which contains a file named xxx.app. This xxx.app is the package that includes all the files. By selecting xxx.app and right-clicking to show package contents, you can see what it contains, roughly as follows:

Installation package:

  • _CodeSignature: IPA package signature file
  • .lproj: Language files
  • Frameworks: Third-party libraries, SwiftSupport libraries
  • Plugins: App-created extensions, such as: Widget, Push, Share
  • Assets.car: Resource file generated by Assets.xcassets, containing images of various resolutions
  • embedded.mobileprovision: Certificate configuration file
  • Info.plist: Project configuration
  • exec format xxx: Executable package
  • Other resource files
    • .mp3 files
    • .html files
    • .json files
    • .png or .jpg files

As an example, I must say that the contents of this IPA are quite comprehensive, with various resources included, haha.

image

The initial IPA package size was 22.9M, and after decompression, the .app size was 57.1M, with the size details of each part as follows:

ContentSize
_CodeSignature93 KB
.lproj4 KB
Frameworks37.5 MB
Plugins181 KB
Assets.car4.9M
embedded.mobileprovision8KB
Info.plist6 KB
exec format xxx13 MB
Other resource files1.4 MB

After understanding the composition of the IPA package, we can go back and analyze step by step according to Xcode's compilation optimization, resource file optimization, and code optimization.

Xcode Build Settings#

This step is often overlooked because when it comes to optimization, the first thing that comes to mind is resource optimization, such as image compression and removing unused code. However, there is relatively little mention of optimizing Xcode's own compilation settings. Moreover, the references provided online may yield different results for each project, and some compilation options need to be tailored to the actual project. Therefore, the author has organized and summarized the following:

Xcode compilation optimization related:

  1. In Build Settings, disable exception support by setting Enable C++ Exceptions and Enable Objective-C Exceptions to NO, and adding -fno-exceptions to Other C Flags;

    WeCom20210430-150019.png

    WeCom20210430-150116.png

    Note: Enable C++ Exceptions and Enable Objective-C Exceptions refer to the project's support for error handling, such as try-catch and throw. If the project uses such exception handling, disabling this will cause errors (Cannot use '@try' with Objective-C exceptions disabled). This includes macros that use try{}, @finally{}, etc., such as @strongify. If disabled, errors will occur during the final packaging.

    The meaning of -fno-exceptions is to disable the exception mechanism. Refer to gcc. Similarly, if the project has try throw, this option should not be set to NO.

    Before detailing the library support for -fno-exceptions, first a passing note on the things lost when this flag is used: it will break exceptions trying to pass through code compiled with -fno-exceptions whether or not that code has any try or catch constructs. If you might have some code that throws, you shouldn't use -fno-exceptions. If you have some code that uses try or catch, you shouldn't use -fno-exceptions.

  2. Build Settings -> Architectures, set Release to arm64

    WeCom20210430-170718@2x.png

    Architectures specify which instruction set types the project is compiled to support. The more supported instruction sets, the larger the compiled data package will be. The default standard architectures (armv7, arm64) parameter includes both 32-bit and 64-bit instruction sets. If 32-bit is not needed, you can change the supported instruction sets in other settings to reduce the IPA package size.

    armv6: iPhone, iPhone 3G, iPod 1G/2G
    armv7: iPhone 3GS, iPhone 4, iPhone 4S, iPod 3G/4G/5G, iPad, iPad 2, iPad 3, iPad Mini
    armv7s: iPhone 5, iPhone 5c, iPad 4
    arm64: iPhone X, iPhone 8(Plus), iPhone 7(Plus), iPhone 6(Plus), iPhone 6s(Plus), iPhone 5s, iPad Air(2), Retina iPad Mini(2,3)
    arm64e: XS/XS Max/XR/ iPhone 11, iPhone 11 pro
    x86_64: Simulator 64-bit processor
    i386: Simulator 32-bit processor

    Note: After Xcode 12, the Valid Architectures option is no longer available.
    Additionally: If you change Architectures from standard architectures (armv7, arm64) to arm64, you will find that the target cannot be selected to run on the simulator, so it is recommended to modify it to arm64, x86_64, or leave it unchanged in Debug mode, and set it to arm64 in Release mode.

    Update: After being advised by a friend, there is another way to set Architectures without modification. In Excluded Architectures, set Release mode Any iOS SDK -> armv7, which can achieve the same effect. After setting, it will exclude the armv7 instruction set in Release mode. Selecting the target will find that it is set to Any iOS Simulator SDK -> arm64 by default, meaning that arm64 instruction set is excluded when running on the simulator.

    As follows:

    WX20210508-162103.png

  3. Build Settings -> Generate Debug Symbols set to NO

    WeCom20210430-152121.png

    Generate Debug Symbols means generating debug symbols. When this option is set to YES, each source file will have additional parameters -g and -gmodule when compiled into .o files, meaning generate complete debug info, so the resulting .o files will be larger, and consequently, the final executable file will also be larger.

    Generate Debug Symbols (GCC_GENERATE_DEBUGGING_SYMBOLS), Enables or disables generation of debug symbols. When debug symbols are enabled, the level of detail can be controlled by the Debug Information Format (DEBUG_INFORMATION_FORMAT) setting.

    Note: When Generate Debug Symbols is set to NO, breakpoints set in Xcode will not interrupt, meaning breakpoint debugging cannot be performed. Additionally, no DSYM file will be generated, even if the Debug Information Format is set, because there must first be debug information to generate a DSYM file, and setting it to NO means no debug information is produced, so no DSYM file can be generated. Therefore, it is recommended not to set this.

  4. Build Settings -> Deployment Postprocessing, set to NO in Debug mode and YES in Release mode

    Deployment Postprocessing is the master switch for Strip configuration.

    WeCom20210430-171159@2x.png

    Official explanation:

    Deployment Postprocessing (DEPLOYMENT_POSTPROCESSING), If enabled, indicates that binaries should be stripped and file mode, owner, and group information should be set to standard values.

    Note: Deployment Postprocessing is the master switch for Strip configuration. Only when this is set to YES will the settings for Strip Linked Product and Strip Debug Symbols During Copy take effect.

    1. Build Settings -> Strip Linked Product, set to NO in Debug and YES in Release

      This strips unnecessary symbol information from the final generated binary file, which can be set to YES in Release.

      Official explanation:

      Strip Linked Product (STRIP_INSTALLED_PRODUCT), If enabled, the linked product of the build will be stripped of symbols when performing deployment postprocessing.

      Note: If Deployment Postprocessing is not enabled, this option has no effect. After removing symbol information, dSYM must be used for symbolization, so the Debug Information Format needs to be changed to DWARF with dSYM file (in Release). If set to DWARF with dSYM file in Debug, stack information will not be visible during crashes.

    2. Build Settings -> Strip Debug Symbols During Copy, set to NO in Debug and YES in Release

      This determines whether to strip during the file copy compilation phase. Setting it to YES will remove the Debug Symbol from third-party libraries, resources, or extensions copied into the project package.

      Official explanation:

      Strip Debug Symbols During Copy (COPY_PHASE_STRIP), Specifies whether binary files that are copied during the build, such as in a Copy Bundle Resources or Copy Files build phase, should be stripped of debugging symbols. It does not cause the linked product of a target to be stripped—use Strip Linked Product (STRIP_INSTALLED_PRODUCT) for that.

      Note: If Deployment Postprocessing is not enabled, this option has no effect.

    3. Build Settings -> Symbols Hidden by Default, set to NO in Debug and YES in Release

      WeCom20210430-171411@2x.png

      Symbols Hidden by Default will declare all symbols as "private extern," removing symbol information.

      Official explanation:

      Symbols Hidden by Default (GCC_SYMBOLS_PRIVATE_EXTERN), When enabled, all symbols are declared private extern unless explicitly marked to be exported using attribute((visibility("default"))) in code. If not enabled, all symbols are exported unless explicitly marked as private extern. See Controlling Symbol Visibility in C++ Runtime Environment Programming Guide.

  5. Build Settings -> Make Strings Read-Only set to YES

    WeCom20210430-171444@2x.png

    Reuse string literals.

    Official explanation:

    Make Strings Read-Only (GCC_REUSE_STRINGS), Reuse string literals.

  6. Build Settings -> Dead Code Stripping set to YES

    WX20210510-131729.png

    Eliminate dead code. Static language compilers like C/C++/Swift will remove unused code during linking, which is ineffective for dynamic languages like Objective-C.

    Official explanation:

    Dead Code Stripping (DEAD_CODE_STRIPPING), Activating this setting causes the -dead_strip flag to be passed to ld(1) via cc(1) to turn on dead code stripping.

  7. Pod Optimization, if the project is Objective-C but uses Swift libraries in CocoaPods with use_frameworks! enabled, it can be optimized to use use_frameworks! only for specific Swift libraries instead of all third-party libraries. The code is as follows:

    # Podfile
    # The following line should remain uncommented
      use_frameworks!
    
    # Other previous code
    
    
    # Then add the following code, where xxx is the library to be packaged as a framework
    dynamic_frameworks = ['xxx']
    pre_install do |installer|
        installer.pod_targets.each do |pod|
            if !dynamic_frameworks.include?(pod.name)
                def pod.static_framework?;
                 true
                end
                def pod.build_type;
                    Pod::BuildType.static_library
                end
            end
        end
    end
    
    

    In Objective-C projects, if the Podfile references third-party Swift libraries, it is common to enable use_frameworks!, which results in all libraries being packaged as dynamic libraries, and the dependency issues between Swift and Objective-C libraries can increase the size of the package.

    There are two modification methods:

    • Remove the dependent Swift third-party libraries and find corresponding Objective-C libraries as replacements.
    • Use the hooking method in the Podfile to change dynamic libraries to static libraries.

    Both modification methods can significantly reduce the package size. Using replacement libraries can avoid importing Swift-related dependency libraries, and the corresponding use_frameworks! can be commented out, resulting in a smaller package, but this involves more significant changes, requiring the replacement of previous libraries. The hooking method allows specific libraries to be packaged as dynamic libraries while others are packaged as static libraries, requiring less modification to the project while still reducing package size. The specific method depends on the project's situation.

    Note: After modifying the Podfile as above, you need to run the Pod install command to update, and there may be compilation errors.

    1. If the error reported is, 'RuntimeError - [Xcodeproj] Consistency issue: build setting ARCHS has multiple values: {"Debug"=>"$(ARCHS_STANDARD)", "Release"=>["arm64"]}', you can first change the values in Architecture to be the same, then change them back to different instruction sets after running Pod install.
    2. Due to the previous issue of third-party libraries being packaged as frameworks, if you encounter an error like xxx framework not found, you can modify the Other Linker Flags in Build Settings to remove the corresponding library that is no longer a framework. An example is shown below:
    image
  8. Asset Catalog Compiler Compilation Settings Optimization, change Optimization in Build Settings -> Asset Catalog Compiler - Options to space.

    WX20210506-100606.png

    This option can change the encoding compression algorithm selected by actool when building Assets.car, reducing package size. You can use the following command to check the encoding compression algorithm of images in Assets.car.

    // You can generate a .json file with the corresponding information for comparison
     xcrun --sdk iphoneos assetutil --info Assets.car > Assets.json
    
    

    For example, after setting this, the comparison shows the following information, revealing different compression algorithms and varying space usage.

    image
  9. Build Settings -> Optimization Level set to -Oz.

    WX20210506-140136.png

    The default Optimization Level is -Os, and -Oz is a compilation optimization option that appeared after Xcode 11. The core principle is to reuse functions for consecutive machine instructions, thus enabling -Oz to reduce binary size, but it may incur additional execution efficiency costs. Refer to What's New in Clang and LLVM.

    In the Presentation Slides of What's New in Clang and LLVM, Apple provides a comparison of the optimization choices for the Optimization Level parameters. For high-performance requirements, it is recommended to choose -O2 and -O3, while for package size sensitivity, -Os and -Oz can be selected. The default -Os strikes a good balance between performance and size. Ultimately, the choice depends on the actual project.

    image

    Comparison of Optimization Level parameters:

    Official explanation:

    Smallest, Aggressive Size Optimizations: This setting enables additional size savings by isolating repetitive code patterns into a compiler-generated function. -Oz

    image

Summary of Xcode Build Settings Optimization:#

Optimization Results

The author's initial project size was 22.9M. After setting Pod optimization, the size was reduced to 21M. After setting the Asset Catalog Compiler - Options Optimization to space, the project size became 20.7M. After applying the other Xcode compilation optimizations, the project size was reduced to 13.2M (the author set the Architecture to arm64).

Comparison of each part after Pod optimization:

ContentSize
_CodeSignature67 KB
.lproj4 KB
Frameworks21.4 MB
Plugins181 KB
Assets.car4.9M
embedded.mobileprovision8KB
Info.plist6 KB
exec format xxx19.2 MB
Other resource files1.4 MB

A simple comparison shows that the Frameworks size decreased from 35M to 21.4M, while the exec file size increased from 13M to 19.2M, but the total IPA package size became 21M, down from 22.9M.

Why is this the case? Remember what was in the Frameworks folder? The Framework folder contained third-party dynamic libraries set in the Pod, as well as the Swift Support library. Upon closer inspection of the contents of the Frameworks folder, it can be seen that, apart from the specified third-party dynamic library xxx and the Swift Support library, all others have disappeared.

Do you remember what the author changed? The author changed the third-party libraries in the Pod from all using dynamic libraries to some using dynamic libraries and others using static libraries. Because of the different linking methods between dynamic and static libraries, dynamic libraries do not copy during linking; they are dynamically loaded after the program starts, so they are placed separately in the Framework folder; while static libraries are fully copied into the executable file during linking. Thus, this resulted in the Frameworks folder size decreasing while the executable file size increased.

Comparison after setting Asset Catalog Compiler - Options Optimization to space:

ContentSize
_CodeSignature67 KB
.lproj4 KB
Frameworks21.4 MB
Plugins181 KB
Assets.car4.7M
embedded.mobileprovision8KB
Info.plist6 KB
exec format xxx19.2 MB
Other resource files1.4 MB

The comparison shows that the size of Assets.car decreased from 4.9M to 4.7M, indicating that asset compression was effective.

Comparison after setting Optimization Level to -Oz:

ContentSize
_CodeSignature67 KB
.lproj4 KB
Frameworks21.4 MB
Plugins181 KB
Assets.car4.7M
embedded.mobileprovision8KB
Info.plist6 KB
exec format xxx18.9 MB
Other resource files1.4 MB

The comparison shows that the executable file size decreased from 19.2M to 18.9M, indicating that the optimization was effective, but the total IPA package size did not change (after uploading to fir, the package size decreased by 0.03M), so the author ultimately did not enable this option.

Comparison after other Xcode compilation settings optimization:

ContentSize
_CodeSignature67 KB
.lproj4 KB
Frameworks11.1 MB
Plugins83 KB
Assets.car4.7M
embedded.mobileprovision8KB
Info.plist6 KB
exec format xxx10.3 MB
Other resource files1.4 MB

A simple comparison shows that the Frameworks size decreased from 21.4M to 11.1M, the Plugins size decreased from 181KB to 83KB, and the exec file size decreased from 19.2M to 10.3M.

Why did this happen? The author set strip to remove symbol information and configured the package to generate only the arm64 architecture instruction set, so both the Framework and exec sizes decreased. As for the Plugin, it contains app-created extensions, and the author also set it to generate only the arm64 instruction set, so the Plugin size decreased as well.

Optimization Summary

WX20210508-162503.png

Some may wonder if the author's current project package is not too large, whether this optimization is meaningful. In the author's view, package size optimization should be a habit, not something done only when the package is large. It should be done because there is room for optimization. A smaller IPA package indicates a lighter historical burden, making it easier to adjust, speeding up compilation and packaging, and lowering the cost of trial and error, which is precisely when optimization should occur. The experiences and lessons learned from the optimization should be documented, allowing for ongoing attention during future development, which is better for development.

Resource File Optimization#

Resource file optimization is generally straightforward, but it requires ongoing effort. The Xcode compilation settings optimization discussed earlier, once configured, does not require repeated attention unless changes are made. However, resource files are different; as the project iterates, new resource files are continuously introduced, and obsolete resources are generated, so resource file optimization must be ongoing.

Resource file optimization consists of two parts: deleting unused resources and compressing used resources. It is recommended to follow a sequence: first delete, then compress, because if compression is done first and it turns out that there are unused resources, the effort would be wasted.

Deleting unused resources:

  • Code files that are defined but not used
  • Deprecated business logic that still has code
  • Images that are referenced but not used
  • Some duplicate resource imports

Compressing used resources:

  • Compressing images, web pages, json, audio files, etc., introduced into the project

Let's practice step by step:

Deleting Unused Resources#

As projects iterate, there will inevitably be some redundancy. This may be due to features developed but not launched, with the product asking to keep them, and over time, they are forgotten; or it may be due to offline business logic that developers are not informed about, so the code logic remains; or when deleting certain business logic, the corresponding image resources are not removed; or each developer imports their own familiar third-party libraries.

This optimization can be divided into two steps: prevention and remediation. The author personally feels that prevention is more important than remediation. Because prevention is proactive, if done well, remediation will be much easier.

Prevention

The scenarios mentioned above are likely encountered by readers. So how can we avoid or minimize these situations?

The author's personal idea is to establish a standardized process. By having a set of standardized processes and executing them accordingly, information asymmetry can be avoided.

Information asymmetry between product and development can lead to business-related redundancy. The product knows the specific business data, while the development team does not. Regular synchronization can be conducted to inform developers about the activity status of corresponding businesses, allowing timely project optimization.

Information asymmetry among developers can lead to each one developing their own solutions and reinventing the wheel. Therefore, establishing a common document listing development process standards, third-party library usage standards, design standards, and code standards can help everyone understand the relevant project information. Each developer should adhere to a unified standard, which can help avoid the issue of different coding styles.

Readers can tailor specific standardized processes to their company's actual situation, reflecting on whether similar issues have arisen in past developments, whether changes have been made after they occurred, and how to avoid similar issues in the future.

Remediation

For deleting unused resources:

  • Code that is defined but not used

    AppCode can be used for analysis. Open AppCode, wait for indexing to complete, then select Code -> Inspect Code from the top menu, choose the range as whole Project, and click OK. Wait for AppCode to perform static analysis. After static analysis, you can see all unused code in Unused code.

    image image

    The types of unused code static analysis results in AppCode include:

    ContentSize
    Unused classUnused class
    Unused import statementUnused import declaration
    Unused propertyUnused property
    Unused methodUnused method
    Unused parameterUnused parameter
    Unused instance variableUnused instance variable
    Unused local variableUnused local variable
    Unused valueUnused value declaration
    Unused macroUnused macro definition
    Unused global declarationUnused global declaration

    After the static analysis results from AppCode are out, confirmation is needed before deletion, as the static analysis results may have inaccuracies, such as methods called by performSelector being detected as unused.

  • Deprecated business logic that still has code

    It is necessary to sort out the business process and confirm with the product and business whether the corresponding functionality is offline based on online business data click rates, thus deciding whether to remove the corresponding business module code.

  • Images that are referenced but not used

    It is recommended to use the tool LSUnusedResources. The principle is to traverse the resource directory for files with suffixes ["imageset", "jpg", "png"...], and then match strings in source files ["m", "swift", "xib", "storyboard"...]. If there is no match, it is an unused resource file.

    When using, make sure to check Ignore similar name, then select the project address to scan by clicking Browse in the upper right corner, and click search in the lower right corner to start scanning. The results will be displayed in the bottom Unused Results section. You can also double-click on a single item to open the corresponding folder. It is advisable to search the project for confirmation before deletion to ensure that it is indeed unused (similar strings may be scanned, so confirmation is necessary before deletion).

    image
  • Some duplicate resource imports

    Duplicate resource imports can be divided into two aspects: third-party SDKs and project files.

    • For third-party SDKs

      It is advisable to keep only one SDK for similar functionalities, such as analytics SDKs like Umeng, TalkingData, etc., online log analysis tools like Tsingyun, Bugly, etc., or libraries for network requests and UI layouts. It is recommended to analyze libraries with similar functionalities and retain only one based on the actual situation. Additionally, when importing some third-party libraries, only the parts actually used should be imported instead of the entire library, which is also an area for optimization.

    • For project files

      Use the fdupes tool for duplicate file scanning. The principle is to check the MD5 of all resources to filter out duplicate resources in the project. The comparison order is size comparison > partial MD5 signature comparison > complete MD5 signature comparison > byte-by-byte comparison. Source: Package Size: Slimming Down

      fdupes usage is as follows:

      
      // 1. First install fdupes
      brew install fdupes
      
      // 2. Use it, where xxx is the directory to scan, and yyy.txt is the output file for scan results
      fdupes -Sr /Users/.../xxx/ > /Users/.../yyy.txt 
      
      
      

      The output results are similar to the following, where identical files can usually be retained by modifying references and keeping only one source file.

      WX20210505-103131@2x.png

Compressing Used Resources#

  • Compressing images, web pages, json, audio files, etc., introduced into the project

    The compression of web pages refers to js files included in the app resources, which should ideally be compressed after being processed on the H5 end.

    For json file compression, if the data is not needed immediately upon opening the app, it can be placed on the server and downloaded for use.

    For audio file compression, it should be done within acceptable limits, choosing formats with higher compression ratios supported by the system.

    The most critical aspect is image compression, which can be divided into several parts:

    1. For monochrome icons and simple functional icons, it is recommended to use IconFont, a vector icon library format, which can standardize formats and reduce resource file sizes. Rounded corners and shadow images should be implemented in code.

      The use of IconFont can refer to the author's previous demo—IconFont

    2. For regular images, you can use the tinyPNG API for compression. The author has previously modified a script BatchProcessImage that calls the tinyPNG API to compress up to 500 images at once. You only need to specify the project directory, and it will automatically compress and replace the original images without manual import/export. For usage, refer to BatchProcessImage. Note that you should pay attention to the Python version; choose between python3 and python, as well as pip3 and pip, depending on which Python version was used to install the dependencies. The script command should then be run with the corresponding Python version.

      Additionally: If the project has more than 500 images, you can modify the script to store the names of compressed images during the process, so that during the second execution, already compressed images will not be processed again, allowing you to continue from where you left off. If you need to compress previously compressed images again, simply clear the stored names of the compressed images.

    3. Images in xcassets with 2x and 3x will be separated based on the specific device during upload, and will not all be included simultaneously. Therefore, it is best to place images in xcassets. However, according to Douyin Quality Construction - iOS Installation Package Size Optimization Practice, during the compilation of Assets.car, some images may be selected and pieced together into a large image to improve loading efficiency. Small images included in this large image will be referenced through offsets. It is recommended to place frequently used small images in Asset.car, as it ensures optimal loading and rendering speed. However, large images (over 100K) should not be placed in Asset.car. Large images can be converted to WebP. WebP is an open-source project by Google that can compress images to a very small size without noticeable differences. Currently, most commonly used image display libraries in iOS support this format. You can use iSparta for batch conversion.

    4. For resource files in private Pod libraries, it is recommended to create an Asset Catalog file named Images.xcassets in the Resource directory of the Pod library, placing images used by the private library here, and manually modifying the podspec of the SDK to specify resource_bundles to use Images.xcassets.

      
          s.resource_bundles = {
              'xxsdk' => ['xxx/Assets/.../*.xcassets']
          }
      
      

      Ps: The resource file reference method in Pods has resource_bundles and resources; it is recommended to use resource_bundles. Refer to In-depth Exploration of iOS Package Size Optimization.

      • resource_bundles: Allows defining the name and files of the resource bundle for the current Pod library. It is declared in hash form, where the key is the name of the bundle and the value is the file patterns to include. CocoaPods officially recommends using resource_bundles, as using key-value can avoid naming conflicts of resources with the same name. It is also suggested that the bundle name should include the Pod library name to minimize naming conflicts. Using resource_bundles will create a .bundle for the specified resources, so when retrieving images, be sure to specify the bundle's location:

            
            NSString *bundlePath = [[NSBundle bundleForClass:[self class]].resourcePath stringByAppendingPathComponent:@"/PAX.bundle"];
            NSBundle *resource_bundle = [NSBundle bundleWithPath:bundlePath];
            UIImage *image = [UIImage imageNamed:@"xxxx" inBundle:resource_bundle compatibleWithTraitCollection:nil];
        
        
      • resources: Using resources specifies resources that will simply be copied to the target project (main project). The official stance is that using resources cannot avoid conflicts of resources with the same name, and Xcode will not optimize these resources.

    5. Finally, regarding image compression settings in Xcode, sometimes after compressing images, it is found that the package size does not change much, possibly due to Xcode's Compress PNG Files option. It is recommended that if you have compressed images yourself, you can set Xcode's Compress PNG Files to NO.

      • Compress PNG Files
        Automatically compress images losslessly during packaging.

      • Remove Text Metadata From PNG Files
        Remove text characters from PNG resources.

Summary of Resource File Optimization:#

Optimization Results

The author's project IPA size was reduced from 13.2M after Xcode compilation optimization to 10.3M after resource file optimization.

Comparison of each part after resource file optimization:

ContentSize
_CodeSignature67 KB
.lproj4 KB
Frameworks11.1 MB
Plugins83 KB
Assets.car2.4M
embedded.mobileprovision8KB
Info.plist6 KB
exec format xxx10.3 MB
Other resource files952 KB

A simple comparison shows that Assets.car decreased from 4.7M to 2.4M, and other resource files decreased from 1.4M to 952KB, indicating that resource compression was effective.

Optimization Summary

See the image, 😄

WX20210506-111104.png

Monitoring Mechanism?#

After slimming down, how can we ensure that the package size does not quickly increase again? Just like not rebounding quickly after losing weight? This requires relying on an appropriate monitoring mechanism and reasonable process specifications to control it.

The monitoring mechanism ensures real-time detection of issues. After each packaging is completed, a script can be run to compare package size differences. If there is an increase beyond the set threshold, relevant developers will be notified via email to investigate the reasons for the package size increase. Additionally, it is important to keep records of package size changes after each packaging and document the reasons for these changes.

Process specifications are used to ensure that every project developer is aware of what to pay attention to during development, fostering good development habits to avoid sudden increases in package size.

  • When introducing new third-party libraries, consider whether there are already similar libraries, whether it can be implemented in-house, and whether it will cause an increase in size. Try to avoid mixing Objective-C and Swift, and prioritize using libraries of the same language type.
  • For new image resources, pay attention to their sizes, consider using Iconfont, whether they can be implemented in code, and be mindful of where they are placed in the project. If they are too large, compress them before use.
  • Do not retain deprecated modules; clean them up promptly.
  • Pay attention to changes in package size, and document the reasons and results of these changes for future reference.

Conclusion#

As of now, the author's overall project optimization results are as follows:

Optimization ContentIPA Size
Original22.9M
Xcode Compilation Optimization - After Pod Optimization21M
Xcode Compilation Optimization - After Asset Catalog Compiler Settings Optimization20.7M
Xcode Compilation Optimization - Others13.2M
Resource Optimization10.3M

The expected slimming effect has been achieved. Although there is still room for further optimization, such as changing the only referenced Swift third-party library in the project to Objective-C, thus removing the mixed compilation and significantly reducing project size, the author has temporarily set this aside due to the need to modify business logic.

Overall, the author has reduced the package size from 22.9M to 10.3M through Xcode compilation optimization and resource file compression without changing business logic, exceeding expectations.

However, as a practice in optimizing package size, there are still unfinished aspects, specifically the final step of code optimization. The author plans to write a separate article to supplement the process logic of code optimization.

References#

  1. Package Size: Slimming Down
  2. iOS Package Size Optimization
  3. Douyin Quality Construction - iOS Installation Package Size Optimization Practice
  4. Practical Experience of JD Mall iOS App Slimming
  5. iOS Optimization of IPA Package Size (Today's Headlines)
  6. In-depth Exploration of iOS Package Size Optimization
  7. Practical Experience of Today's Headlines iOS Installation Package Size Optimization—Ideas and Practices
  8. Today's Headlines iOS Installation Package Size Optimization—New Phase, New Practice
  9. iOS Optimization of IPA Package, Reducing Installation Package Size
  10. WeChat iOS Installation Package Slimming
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.