Long time no see, recently summarized some flat (should) often (pay) use (face) to (try) knowledge point, today I talked with you about App volume optimization this thing.

1. Why do I need to slim down

Don’t ask! Ask to prepare for an interview.

Ha ha, just kidding. We will encounter a scene in our life, when we need to open the App in an emergency, we find that the App cannot be opened for a long time! WTF!!!! Another App with the same function can be opened instantly. Which App can retain more users is self-evident!

To borrow a phrase from a character in a game :” Time is money, my friend!”

2. What can we do?

Let’s first look at a mind map:

Mind maps have summed up the optimizations I already know and can implement so far.

As shown in the figure, APP volume optimization includes two parts: resource slimming and code slimming.

I’m going to use APPReduction as a simple demo. You can download it from gayHub if you need it.

3. Specific implementation

3.1 Resource slimming

  • Delete the resource

    Due to the long process of business code stacking, the project is likely to accumulate many useless images, which can actually increase the size of the App. We can use the tool LSUnusedResources to delete resource files.

In the RedutionViewController, in the configImageTest method you’ll find the code call for the image image

- (void)configImageTest{
    
    [UIImage imageNamed:@"cooker"];
    [UIImage imageNamed:@"driver"];
    [UIImage imageNamed:@"function"];
    [UIImage imageNamed:@"header"];
//    [UIImage imageNamed:[@"think_"stringByAppendingFormat:@"005"]];
//    [UIImage imageNamed:[@"think_"stringByAppendingFormat:@"006"]];
//    [UIImage imageNamed:[@"think_"stringByAppendingFormat:@"007"]];
    
}
Copy the code

When we use LSUnusedResources for image resource inspection, we will find unused images.

However, it is important to note that it is best to do manual checks to avoid accidental deletions, and that the application does not consider the resource obsolete if the code is simply commented out.

  • Compressing resources

    Please talk more with UI in a reasonable and legal way, please cut the picture according to the need to cut the map, not every one is high-definition code, in an acceptable range can compress the resource picture! And I think all the quality changes related to images need to be handled by the UI, so don’t mess with it yourself.

  • Large image resources

    Large image resources that are not often used can be downloaded to the APP instead of packed into the IPA. Don’t talk to the code if you can talk to the product

3.2 Code slimming

When our App is packaged as an IPA, the code is packaged into.o files, and these.O files make up MachO, and the system generates an attached file LinkMap when it compiles MachO files.

3.2.1 LinkMap

  • The composition of LinkMap

    LinkMap is composed of Object File, Section and Symbol, which describes all code information of the project. The space can be optimized according to this information.

  • LinkMap access

    1. Enable the compilation option Write Link Map File \nXCode -> Project -> Build Settings -> set Write Link Map File to YES

    2. In XCode, enable the compilation option Write Link Map File \nXCode -> Project -> Build Settings -> set the address where Path to Link Map File is located

    3. Run the project to generate a. TXT file in the address location

  • The analysis of the LinkMap

    1. With the help of LinkMap parsing tool, we can analyze the size of each class

2. Optimize the volume of the code specifically. For example, the three-party library takes up a huge amount of space. When choosing two identical libraries, you can also make a choice according to the specific gravity of the volume.

We can see from the macro point of view which parts of the code need to be optimized, but from the micro point of view which are useless classes and which are useless methods, we need to further analyze MachO level.

3.2.2 MachO analysis

MachO file can be said to be the most important part of App compilation, through the MachOView software we can see the composition of MachO more intuitively. If your MachOView crashes while running please follow this article to fix it.

  • The composition of MachO

__objc_selrefs: Records almost all methods called

__objc_classrefs and __objc_superrefs: record almost all classes used

__objc_classList: Addresses of all classes in the project

  • Delete useless classes

    The __objc_classrefs section of MachO file records the address of the referenced class, and the __objc_classlist section records the address of all classes. We can think that the address of the unused class can be obtained by taking the difference value of the two, and then symbolized, the information of the unused class can be obtained.

    You can use the classunref tool to find unused classes.

    If you are interested in implementing this, you can read the big guy’s article on iOS code slimming practice: Remove Useless classes

  • Delete unused methods

    When we remove useless classes, it is very likely that there will still be unused methods in the used classes.

    As mentioned earlier, LinkMap stores the information of the project, and all of our methods that have been included in the project can be obtained through LinkMap.

    Inspired by classunref, the author uses Python to implement an automated way of using unused methods

    Because PY is not very familiar and I am lazy, please ignore some syntax, interface design is not standard, etc

    1. Use the command grep ‘\ [+ | -] [. * \ s. * \]’ XXX – linkMap. TXT instruction we get all the code to be included in the engineering project

    Def method_readRealization_pointers(linkMapPath,path):# all method
    lines = os.popen("grep '[+|-]\[.*\s.*\]' %s"% linkMapPath).readlines() // Method lines = method_ignore(lines,path); pointers =set(a)for line in lines:
        line = line.split(The '-')[-1].split('+')[-1].replace("\n"."")
        line = line.split('] ')[0]
        line = str("%s]"%line)
        pointers.add(line)
    if len(pointers) == 0:
        exit('Finish:method_readRealization_pointers null')
    print("Get all method linkMap pointers... %d"% len(pointers))
    return pointers
    Copy the code

    2. Considering that a large number of tripartite libraries are used in your project, and many of the tripartite libraries’ methods are not used, we use method_ignore to ignore them, so that the collection of differences obtained will not include the unused methods of the tripartite libraries

    def method_ignore(lines,path):
      print("Get method_ignore...")
      effective_symbols = setPrefixtul = tuple(class_allIgnore_Prefix(path,)) prefixtul = tuple(path,)' '.' '))
      getPointer = set() // this is to ignore Setter Getter methodsfor line in lines:
          classLine = line.split('[')[-1].upper()
          methodLine = line.split(' ')[-1].upper()
          if methodLine.startswith('SET'):
             endLine = methodLine.replace("SET"."").replace("]"."").replace("\n"."").replace(":"."")
             print("methodLine:%s endLine:%s"%(methodLine.lower(),endLine.lower()))
             iflen(endLine) ! = 0: getPointer.add(endLine) getPointer = list(set(getPointer))
      getterTul = tuple(getPointer)
      
      for line in lines:
          classLine = line.split('[')[-1].upper()
          methodLine = line.split(' ')[-1].upper()
          if (classLine.startswith(prefixtul)or methodLine.startswith(prefixtul)  or methodLine.startswith('SET') or methodLine.startswith(getterTul)):
              continue
          effective_symbols.add(line)
      
      if len(effective_symbols) == 0:
          exit('Finish:method_ignore null')
      return effective_symbols;
    
    Copy the code

    3. Using the otool -v -s __DATA__objc_selrefs directive we can get all the methods that have been implemented

    def method_selrefs_pointers(path):
      # all use methods
      lines = os.popen('/usr/bin/otool -v -s __DATA __objc_selrefs %s' % path).readlines()
      pointers = set(a)for line in lines:
           line = line.split('__TEXT:__objc_methname:')[-1].replace("\n"."").replace("_block_invoke"."")
           pointers.add(line)
      print("Get use method selrefs pointers... %d"% len(pointers))
      return pointers
      
    Copy the code

    3. Use the difference between Step 1 and Step 2 to obtain the unused method.

    def method_remove_Realization(selrefsPointers,readRealizationPointers):
      if len(selrefsPointers) == 0:
         return readRealizationPointers
      if len(readRealizationPointers) == 0:
         return null
      methodPointers = set(a)for readRealizationPointer in readRealizationPointers:
          newReadRealizationPointer = readRealizationPointer.split(' ')[-1].replace("]"."")
          methodPointers.add(newReadRealizationPointer)
      unUsePointers = methodPointers - selrefsPointers;
    
      dict = {}
      for unUsePointer in unUsePointers:
          dict[unUsePointer] = unUsePointer
      
      for readRealizationPointer in readRealizationPointers:
          newReadRealizationPointer = readRealizationPointer.split(' ')[-1].replace("]"."")
          if dict.has_key(newReadRealizationPointer):
              dict[newReadRealizationPointer] = readRealizationPointer
              str = dict[newReadRealizationPointer]
      
      return list(dict.values())
    
    Copy the code

    Interested students can download a modified version of ONLClassMethodUnref here

3.2.3 AppCode

If your project is not large enough, static analysis with AppCode can also find unused code. As simple as this, go to AppCode-> Select Code-> click Inspect Code- and wait for static analysis

However, even if you use all of the above methods, due to the nature of dynamic languages, we still can’t find all the unused code, and we still need to be careful when deleting code! Don’t delete too much! Do regression tests!

The resources

1. IOS code slimming practice: Remove useless classes

2.MachOView crashes when running, please modify according to this article