directory

  1. How I Boosted the startup speed of Twitter Oasis by 30%
  2. How I Increased the startup speed of Weibo Oasis by 30% (II)
  3. Lazy Binary Rearrangement

Preface to 0.

My previous post “How I Increased the startup speed of Weibo Oasis by 30%” received a lot of feedback from my friends.

Among them, the benefit of dynamic library to static library is greater than the benefit of binary rearrangement, but we also encounter some problems in practice.

Ben runs after dressing X, installs X by himself, also wants to complete the principle of kneeling, here I talk about these problems in detail.

1. What exactly did changing the Mach-O Type change?

Let’s look at dynamic libraries first. Here I made two libraries Pod1 and Pod2:

Use_frameworks is configured in Podfile! , and then pod install to generate a dynamic library.

How do you know if this is a dynamic library?

  • First, the mach-o Type of this library is dynamic.

  • After executing the ⌘+B build, we still go to the app in the Products file:

    Right-click on the generated demo. app package and select Show package contents:

    Open the Framewoks folder and we can see that there are two dynamic pod1. framework and Pod2.framework that we created. The folder contains code signatures, resources, info.plist, Pod1(Mach-o), and bundles.

    That is, if we were using a dynamic library, we would see it in the Framewoks folder, and there is no code for it in the main project’s Mach-O file.

Let’s change the Mach-O Type in Build Settings to Static Library.

Also remove install_framework-related parts of pods-demo-frameworks. Sh as described in the previous article:

Contributes.clean Build Folder(or ⇧+⌘+K) and then ⌘+B to Build. With that done, let’s open the demo. app package again:

This time we found that the Framewoks folder was empty! Let’s look at the mach-o file for the main project:

We see that the classes Pod1Object and Pod2Object that we created in the two libraries end up in the Main project’s Mach-o file!

It should be clear by now:

  • The dynamic library is stored separately from the main project’s Mach-O.
  • Static libraries are merged with the main project’s Mach-O.

2. Static libraries may cause problems

We saw earlier that static libraries would be merged with the main project’s Mach-O. What problems would that cause?

  • Symbolic conflicts
  • Bundles of access

2.1 Symbol Conflict

Note the differences between -objc, -all_load, and -force_load:

  • – the ObjC linker loads all objective-C classes and categories in the static library; (Causes executable files to become larger)
  • -all_load linker loads all objective-C classes and categories in the static library (same here as above); -objc is invalid if the static library only has categories, so use this flag.
  • -force_load loads all classes of a specific static library, similar to -all_load but restricted to a specific static library, so -force_load specifies the static library. If two static inventories are in the same symbol, using -all_load will cause the duplicate symbol error. In this case, you can select one of the libraries -force_load according to the situation.

We copied Pod2Object.{h,m} in the Pod1 library and added -all_load to Other Linker Flags in Build Settings.

Contributes.clean Build Folder(or ⇧+⌘+K) and ⌘+B to Build will cause a duplicate symbols error:

Solutions:

Use none or neither of the static libraries. Having said that, it’s not safe either. Change your name if you can.

2.2 Bundle Acquisition

We add the following methods to Pod1Object and Pod2Object:

- (nullable NSBundle *)getBundle {
    return [NSBundle bundleForClass:[self class]];
}
Copy the code

Add to the ViewController of the main project:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSBundle *main = [NSBundle mainBundle];
    NSBundle *pod1 = [[Pod1Object new] getBundle];
    NSBundle *pod2 = [[Pod2Object new] getBundle];
    NSLog(@"%@", main);
    NSLog(@"%@", pod1);
    NSLog(@"%@", pod2);
}
Copy the code

Let’s take a look at the dynamic library:

We see that the Main Bundle is our App, and our Pod1 Bundle and Pod2 Bundle are their corresponding frameworks, as if they have their own sandbox.

Let’s look at static libraries:

You can see that all three bundles have become our Main Bundle!

This is because static libraries are incorporated into the main project Mach-o file:

[NSBundle bundleForClass:[self class]].Copy the code

[self class] The Bundle of the Main project is the Bundle of the Main project.

This problem is a little easier to solve than symbol collisions, but before I do, I want to talk about CocoaPods.

2.3 CocoaPods

After pod install is implemented, CocoaPods adds a [CP] Embed Pods Frameworks script to the Build Phase of the main project:

This script is executed after Build. We have commented (or deleted) install_framework code from the Organizer library to resolve the issue of error when attempting Validate App in the Organizer after Archive:

In fact, this operation is too simple and crude, resulting in the loss of resource files.

There were few resource files in the tripartite library before, so we didn’t find this problem. Thank you for your reminder.

Let’s take a closer look at what Install_framework does.

# Copies and strips a vendored framework
install_framework()
{
  #Set up thesourceVariable, the path after the tripartite library is built
  if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
    local source="${BUILT_PRODUCTS_DIR}/$1"
  elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
    local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
  elif [ -r "$1" ]; then
    local source="$1"
  fi
  
  #Set the destination variable, the path to which the tripartite library needs to move
  local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
  
  #judgesourceWhether it is a linked file, you need to point to the original file
  if [ -L "${source}" ]; then
    echo "Symlinked..."
    source="$(readlink "${source}")"
  fi
  
  #Rsync --delete Undifferentiated synchronization, which can be simply understood as disk synchronization or replication
  #To learn more about rsync, type man rsync on the command line
  #This is the equivalent of takingsourceSynchronize files (folders) to destination
  #Copy *. Framework to the Frameworks folder
  # Use filter instead of exclude so missing patterns don't throw errors.
  echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
  rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
  
  #Here's finding the binary, the Framework's Mach-O
  local basename
  basename="$(basename -s .framework "$1")"
  binary="${destination}/${basename}.framework/${basename}"

  if ! [ -r "$binary" ]; then
    binary="${destination}/${basename}"
  elif [ -L "${binary}" ]; then
    echo "Destination binary is symlinked..."
    dirname="$(dirname "${binary}")"
    binary="${dirname}/$(readlink "${binary}")"
  fi
  
  #Remove invalid architectures
  # Strip invalid architectures so "fat" simulator / device frameworks work on device
  if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
    strip_invalid_archs "$binary"
  fi
  
  #Code signing
  # Resign the code if required by the build settings to avoid unstable apps
  code_sign_if_enabled "${destination}/$(basename "$1")"
  
  #Swift runtime library, which will not be used after Xcode 7, can be left alone
  # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
  if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
    local swift_runtime_libs
    swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u)
    for lib in $swift_runtime_libs; do
      echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
      rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
      code_sign_if_enabled "${destination}/${lib}"
    done
  fi
}
Copy the code

The *. Framework package will not be copied to the Frameworks folder of the App, and the resource files in the *. Framework will be lost.

Now the question is clear:

  • Static causes the Bundle to become the Main Bundle.
  • Resources are not transferred from *. Framework to App.

Solutions:

Now that the Bundle is the Main Bundle, we can use the script to copy the resources to the App folder.

If [-r "${BUILT_PRODUCTS_DIR}/$1"]; if [-r "${BUILT_PRODUCTS_DIR}/$1"]; then local source="${BUILT_PRODUCTS_DIR}/$1" elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" elif [ -r "$1" ]; Then local source="$1" ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" You have other resources change for filename in ` ls ${source} | grep ". * \. Bundle \ | * \. JPG \ | * \. Jpeg \ | * \. PNG "` do Full_path =${source}/${filename} # Rsync-abrv --suffix. Conflict "${full_path}" "${destination}" done }Copy the code

Now we change the static tripartite library from install_framework method to install_framework_bundle:

if [[ "$CONFIGURATION" == "Debug" ]]; then
  install_framework_bundle "${BUILT_PRODUCTS_DIR}/Pod1/Pod1.framework"
  install_framework_bundle "${BUILT_PRODUCTS_DIR}/Pod2/Pod2.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
  install_framework_bundle "${BUILT_PRODUCTS_DIR}/Pod1/Pod1.framework"
  install_framework_bundle "${BUILT_PRODUCTS_DIR}/Pod2/Pod2.framework"
fi
Copy the code

Let’s compare:

Resources are now properly accessible.

// Pod1Object @implementation Pod1Object - (nullable NSBundle *)getBundle { return [NSBundle bundleForClass:[self class]]; } - (nullable NSBundle *)getResourceBundle { NSBundle *bundle = [self getBundle]; return [NSBundle bundleWithPath:[bundle pathForResource:@"image1" ofType:@"bundle"]]; } @end // Pod2Object @implementation Pod2Object - (nullable NSBundle *)getBundle { return [NSBundle bundleForClass:[self  class]]; } - (nullable NSBundle *)getResourceBundle { NSBundle *bundle = [self getBundle]; return [NSBundle bundleWithPath:[bundle pathForResource:@"image" ofType:@"bundle"]]; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSBundle *pod1 = [[Pod1Object new] getResourceBundle]; NSBundle *pod2 = [[Pod2Object new] getResourceBundle]; UIImage *image1 = [[UIImage alloc] initWithContentsOfFile:[pod1 pathForResource:@"icon121" ofType:@"png"]]; UIImageView *imageView1 = [[UIImageView alloc] initWithFrame:CGRectMake(0, 100, 100, 100)]; imageView1.contentMode = UIViewContentModeCenter; [self.view addSubview:imageView1]; imageView1.image = image1; UIImage *image2 = [[UIImage alloc] initWithContentsOfFile:[pod2 pathForResource:@"icon120" ofType:@"png"]]; UIImageView *imageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(0, 200, 100, 100)]; imageView2.contentMode = UIViewContentModeCenter; [self.view addSubview:imageView2]; imageView2.image = image2; } @endCopy the code

Note:

In install_framework_bundle, I don’t deal with duplicate names.

-b –suffix. Conflict adds the suffix. Conflict to a file with the same name.

You can use find to scan the App folder to see if any resources with the same name are marked as.conflict.

check_conflict()
{
    local destination="${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
    conflict_list=`find ${destination} -regex '.*\.conflict'`
    conflict_list=(${conflict_list/ /})
    count=${#conflict_list[*]}
    if [ $count -gt 0 ]; then
        echo "Found conflicts:"
        for var in ${conflict_list[@]}
        do
           echo $var
        done
        exit 1
    fi
}
Copy the code

If the resources have the same name, there may be no way to statically.

  • If the tripartite library code is not written well, a crash may occur.
  • If a crash does not occur, code behavior may be affected.

3. Selection of dynamic library and static library

Although this is an old question, since we are talking about static libraries and dynamic libraries, we will briefly mention it.

Library type advantages disadvantages
Static library 1. The target program can run directly without external dependencies.

2. High efficiency teaching dynamic library.
1. Will use the target program’s size increases.
The dynamic library 1. It does not need to be copied to the target program and does not affect the volume of the target program. The same library can be used by multiple programs.

2. It is loaded at runtime, allowing us to replace the library at any time without recompiling the code.
1. Dynamic loading incurs a performance penalty.

2. Dynamic libraries make programs dependent on external environments. If the environment is missing dynamic libraries or the version of the libraries is not correct, the program will not run.

Dynamic libraries are not allowed on iOS, and before iOS8, because apps run in a sandbox, different apps can’t share code:

  • IOS is single-process, and even with dynamic libraries, there is no object to share code with.
  • Dynamic downloading of code is explicitly prohibited by Apple and does not take advantage of dynamic libraries. (You can use it if you don’t need to put it on the App Store)

In summary, so there is no need to exist on the dynamic library.

After iOS8, iOS got App Extesion. Apple later came up with the Embedded Framework because the main iOS App and Extension needed to share code. The dynamic library allows the App and App Extension to share code, but the scope of the dynamic library is limited to one App process and needs to be copied into the target application.

The system dynamic library does not need to be copied to the target program and can be used by multiple processes. Our Embedded Framework doesn’t have that kind of capability.

Advice:

If the application uses App Extesion and the main project and Extension use the same tripartite library:

  • Dynamic libraries can be used to save memory and reduce package sizes.
  • If there are many libraries involved and you want to improve the startup speed, you can consider merging multiple dynamic libraries to reduce the number of dynamic libraries.

Are there any questions you are welcome to ask ~

If you found this article helpful, give me a thumbs up