preface

This optimization was proposed by WWDC-2020. You can watch the video below:

aboutruntimeImproved optimization of

In the author’s words, developers don’t have to change any code or use new apis, and the application gets faster. This is because Apple optimizes the underlying data structure in its internal runtime.

Class Data Structures Changes

On disk, in the app binary, the class structure is as follows:

For the object of the class itself, it contains the most frequently accessed information:

  • Pointers to metaclasses, superclasses, and method caches.
  • A pointer to more data that stores additional information, calledclass_ro_t. Among themRoRead only.

Class_ro_t includes the name of the class and information about methods, protocols, and instance variables. And Swift and Objective-C share this infrastructure.

When classes are first loaded from disk into memory, they are fixed at first, but once they are used, they change. To understand the class changes that follow, first look at Clean Memory and Dirty Memory.

Clean Memory and Dirty Memory

  • Clean Memory is Memory that does not change after loading. Class_ro_t belongs to Clean Memory because it is read-only.

  • Dirty Memory is the Memory that has changed while the process is running. Once used, a Class becomes Dirty Memory because new data is written to the Class at runtime.

Features:

Dirty Memory exists as long as the process is running. And unlike macOS, iOS does not use swap, so Dirty Memory is expensive in iOS.

Clean Memory can be removed to save more Memory space. If you need it again, the system can reload it from disk.

This is why the Class data is divided into two parts, the more data you can keep Clean, and the data that does not change is separated and stored as Clean Memory.

class_rw_t

While this data is sufficient to use, the runtime needs to keep track of more information about each class, so the first time a class is used, the runtime allocates additional storage space for it.

The storage capacity allocated by this runtime is class_rw_t for reading/writing data. In class_rw_t, new information is stored that is generated only at run time.

First Subclass and Next Sibling Class: Because of the First Subclass and Next Sibling Class in class_rw_t, all classes are linked into a tree structure. They allow the runtime to traverse all the classes currently in use

Methods, Properties, Protocols: added at Runtime. This can add new Methods to the class when the Category is loaded, or dynamically via the Runtime API.

Demangled Name: A field only Swift will use and not even the Swift class needs it. Unless developers want to access Swfit’s OC name, utilization is low.

Class_rw_t split

Because there are too many things in class_rw_t, it will take up too much memory. Parts that are not commonly used can be split and allocated to another extension record for the class to use.

When you examine usage on the actual device, only about 10% of the classes actually change the Methods. Therefore, Methods, Properties, and Protocols can be split.

Demangled Name: Only Swift will be used and can be split.

Therefore, the splitting effect is as follows:

The difference between class_rw_t and class_ro_t

  • When classes are used, the class has a class_rw_t structure. If classes are not used, the class is a Clean Memory structure of class_ro_t.

  • On the other hand, if there is a class_rw_t structure in the memory structure of a class, then there must be categories

case

Actually verify the memory used by Xcode and Safari’s class_rw_t

$ heap Xcode | egrep 'class_rw|COUNT'

Memory usage is as follows:

Xcode:

  • Class_rw_t Occupies 2877568 bytes

  • Class_rw_ext_t Occupied bytes: 203664, accounting for 7%

Safari:

  • Class_rw_t occupies 186496 bytes

  • Class_rw_ext_t Occupied bytes: 27312, accounting for 15%

Conclusion:class_rw_ext_tSplit, can save memory overhead.