background

Jike is one of the earliest iOS development teams in China to fully embrace Swift. At present, 100% of jike’s business code (except third-party library dependence) is realized through Swift. With the development of the business, we immediately split the architecture several times, divided the project into multiple targets by modules, and relied on more and more third-party libraries.

Prior to Xcode 9, because Swift static libraries were not officially supported, development teams had to package project dependencies into dynamic libraries. However, as there are more and more dynamic libraries in the project, the Startup speed of the App will inevitably suffer.

Finally, in the era of Xcode 9, Swift brings native support for static libraries. Instant had been looking at swift static libraries for a long time, so when Xcode 9 came around, we decided to change the project to static library packaging.

Problems and methods encountered

Static libraries are one of the concerns of the Swift community, so support for them began immediately after Xcode 9 came out.

After a brief manual attempt (changing the target build), we decided to try this ourselves because (a) the static library packaging was optimized for our current project after comparing our tests, and (b) it didn’t change much after resource management, the code didn’t change much.

We learned that Cocoapod was working on support, but there were some issues with Cocoapods support when we started, and the changes we made were necessary to switch to Cocoapod in the future. We decided to go ahead and script the manual transformation and use Xcodeproj instead

main_project = Xcodeproj::Project.open('Ruguo.xcodeproj') main_target = main_project.targets.first # add complie flag # 1. Static library links are optimized for unreferenced code, adding -all_load to binary Prevent some OC symbols to find main_target. Build_configurations. Each do | config | config. Build_settings [' OTHER_LDFLAGS] + = [' - all_load] unless config.build_settings['OTHER_LDFLAGS'].include? ('-all_load') end # main target build phases # main_embed_frameworkS_phase = main_target.build_phases.select {|phase| (phase.respond_to? :name) && phase.name == "Embed Frameworks" }.first main_copy_resources_phase = main_target.build_phases.select {|phase| phase.kind_of? Xcodeproj::Project::Object::PBXResourcesBuildPhase }.first main_linkPhase = main_target.build_phases.select { |phase| phase.kind_of? Xcodeproj::Project::Object::PBXFrameworksBuildPhase }.first ExcludeTargets = [' main project, some targets, such as extensions, Not into static library '] main_project. The targets. Each do | target. | if not excludeTargets include? Target. The name then target_linkPhase = target.build_phases.select { |phase| phase.kind_of? Xcodeproj::Project::Object::PBXFrameworksBuildPhase }.first target_copy_resources_phase = target.build_phases.select {|phase| phase.kind_of? Xcodeproj::Project::Object::PBXResourcesBuildPhase }.first # find dynamic library references # 1. TBD is a stub library for dynamic libraries. It has the same link symbol as the corresponding dynamic library, but no code. Can speed up compilation target_linkPhase. Files_references. Each do | file_reference. | if file_reference path. End_with? (".tbd") or file_reference.path.end_with? (".dylib") or file_reference.path.end_with? (".framework") then main_linkPhase.add_file_reference(file_reference, true) end # stub library is not allow in static library if file_reference.path.end_with?(".tbd") then target_linkPhase.remove_file_reference(file_reference) end end # add OTHER_LDFLAGS target.build_configurations.each do | config | config. Build_settings [' MACH_O_TYPE] = 'staticlib' # keep static library symbol information config. Build_settings [' STRIP_INSTALLED_PRODUCT]  = 'NO' end # copy frameworks resources into main bundle target_copy_resources_phase.files_references.each do |file_reference| main_copy_resources_phase.add_file_reference(file_reference, true) end end end main_project.saveCopy the code
  • all_load

The first problem when packaged as a static library was that some of the OC’s selectors were not found, and the solution was to add the all_load compilation option, ref

  • Resource management

Unlike dynamic framework, static library objects will eventually be typed into the main binary, so the paths of the various framework and target resources in the project will be different. In order to standardize resource usage, we set up separate bundles for each module’s resources and modified the way resources are used in the project

  • Symbol is missing

After successfully transforming the project into a static library package, during the testing phase, we found that the symbol information on the crash log information collected was distorted. (As follows: stack function address offset is too large, obviously a symbol problem)


Using the Hopper tool, we found a large number of symbols missing from the Jike executable. Immediately change the static library compile configuration to

- Strip Debug Symbols During Copy: No
- Strip Style: Debugging Symbols
- Strip Linked Product: No
Copy the code

After compilation, the packaging found that the symbols were restored, but the package size increased by nearly 10M. The symbol information under release should be stored ina separate DSYM file, which can be re-symbolized by dSYM file. Therefore, the symbol should not be typed into the final main binary. The static binary will eventually be typed into the main binary, so the symbols of the static library need to be preserved, so that when the main binary is generated, its DSYM symbol information will be relatively complete.

At the same time, set the main binary, because symbols don’t need to exist in binary after all.

Strip Debug Symbols During Copy: Yes
Strip Style: All Symbols
Strip Linked Product: Yes
Copy the code

This gives us dSYM for complete symbol information and Main Binanry for symbol slimming. The dynamic library, like the Main binary, is a separate MACHo file. It also has its corresponding DSYM file when discarding symbols, so it can be symbolized normally.

progress

  • Immediately package all the pods dependencies and module targets in the project into a static library, and go live and experience the address
  • Premain consumption from800+msDown to500+ms, iPhone 6s
  • Packet size48MTo reduce the35M
    • There is no direct data on the effect of static and dynamic library packaging on app Bundle size
    • However, as a matter of practice and our guess, unlike dynamic libraries, which remain relatively independent and generic, static libraries are packaged and their code is copied as part of the Main Binary, so compiler generated Main Binaries get more optimizations, such as garbage elimination, etc.
    • On iOS platform, the dynamic library is position Independent Code (PIC), which is compared with the static library no-PIC.

CocoaPods

  • When we started working on static library packaging, it was already available in Cocoapods 1.4.0.2-beta
  • Cocoapods 1.4.0.2-beta supports packing static libraries, but you need to add the static_framework = true attribute to your PodSpec. Most PODS do not currently offer podSpecs packaged in static libraries. So if you want to rely on Cocoapod for packaging, one way to do that is to provide your own static library, PodSpec, that corresponds to the pod that your project depends on

The best way to do this is to create your own private repO and upload the corresponding version of your PodSpec, noting that you add the Static_framework attribute to each podSpec. You also need to create a static library version of Dependency for PodSpec. In this way, you can switch between dynamic library packaging and static library packaging by simply switching the version specified by the Podfile

Note: There are some hybrid libraries that are currently not well supported by Cocoapods 1.4.0.2-beta


issue:
Github.com/CocoaPods/C…


LLDB: Rxswift, RxCocoa, etc

  • In the current version of Pod dependency we have used CocoaPods to complete static library packaging, mainly considering lower maintenance costs, and more flexible switching between dynamic/static library packaging, tape-staticIs a static library version of the corresponding pod on the immediate private REPO, for example:
Pod 'AsyncSwift', '2.0.4-static' pod 'Alamofire', '4.2.0-static' pod 'RxSwift', '4.0.0' pod 'RxCocoa', '4.0.0' pod 'AsyncDisplayKit', '2.0.2-static' pod 'SwiftyUserDefaults', '3.0.0-static' pod 'ObjectMapper', '3.1.0 - static'Copy the code

Conclusion & Advertising

Explore and follow up the new technology is the direction of Jike iOS has been moving forward. Welcome more excellent students to join jike and work together to create more cool technologies and applications.

Job link: Instant ferry tickets

reference

[Linker and Libraries Guide ]

[Reliable Software Technologies]

[Framework Programming Guide]

How are static libraries linked and how are dynamic libraries loaded?