Damien, iOS developer. Currently working for Ctrip.

Session:developer.apple.com/wwdc20/1016…

An overview of the

Objective-c is an ancient language, born in 1984, that followed Apple through its ups and downs, saw Steve Jobs create NeXT, saw Steve Jobs return to Apple’s glory days, built UIKit with its idiosyncratic syntax, In 2020, faced with Swift, the “old guard” Objective-C is also giving play to its residual heat. Even in the face of more and more battlefield losses, only “old soldiers die, but slowly die” can reflect the tragic and stirring. This year, Apple brought new optimizations to the Objective-C Runtime, so let’s take a closer look at the changes.

Class data structure changes

First, let’s look at the representation of binary classes on disk

class_ro_t

Before we understand the changes that occur when a class is loaded by the Runtime, we need to know two concepts: **Clean Memory: ** A block of Memory that does not change after loading. Class_ro_t belongs to Clean Memory because it is read-only. **Dirty Memory: a block of Memory that changes at Runtime. Once a class is loaded, it becomes Dirty Memory. For example, we can dynamically add methods to a class at Runtime.

To be clear, Dirty Memory is much more expensive than Clean Memory. Because it requires more memory information, it must be retained as long as the process is running. For us, more Clean Memory is obviously better because it saves more Memory. How can we keep most of our class data in Clean Memory by separating out the data parts that never change? Before we get into tuning, let’s take a look at what the structure of a class looks like after it’s loaded.

class_rw_t

Tips: class_ro_t is read-only and stores information about fields determined at compile time; Class_rw_t is created at runtime, and it makes a copy of class_ro_t, and then it adds in the attributes, methods, protocols, etc. of the class, and the reason for that is because Objective-C is a dynamic language, and you can change their methods at runtime, Properties, and classes can add new methods to a class without changing its design.

As it turns out, class_rw_T takes up more memory than class_ro_t, and on the iPhone, we measured about 30MB of these class_rw_T structures on our system. How do you optimize this memory? By measuring the actual usage on the device, we found that about 10% of the classes actually had dynamic change behavior, such as dynamically adding methods, using Category methods, and so on. So, we can extract this dynamic part, which we’ll call class_rw_ext_t, so the structure will look something like this.

Clean Memory

Tips: heap XXXXX | egrep ‘class_rw | COUNT’ you can use this command to check class_rw_t consumption of memory. XXXX can be replaced by the name of App to be measured. Such as: heap Mail | egrep ‘class_rw | \’ COUNT ‘view Mail application usage.

Relative method address

Now, let’s look at the second change to Runtime, the optimization of method addresses. Each class contains a list of methods that the Runtime can find and send messages to. The structure is roughly as follows:

  • Selector: Method name or Selector. Selectors are strings, but they are unique
  • Method type encoding: Method type encoding identifier (see the reference link for details)
  • IMP: the function pointer implemented by the method

On 64-bit systems, they take up 24 bytes of space

Now that you know the structure of the method, let’s look at a simplified view of memory in the process

This is a 64-bit address space in which blocks represent stacks, heaps, and libraries. Let’s focus on the init method in the AppKit library.

Now our address is going to look like this

  1. The offset is always the same no matter where the library is loaded into memory, so there is no need to correct the pointer address after loading.
  2. They can be stored in read-only memory, which is more secure.
  3. Using 32-bit offsets reduces the amount of memory required on 64-bit platforms by half. We can save about 40MB of memory on the iPhone.

After optimization, the memory footprint required for Pointers can be reduced by half.

Another problem with relative Method addresses is what to do in Method Swizzling? As we all know, Method Swizzling replaces two Pointers to the Method function. The Method function implementation can be implemented anywhere, but with the relative offset address, it doesn’t work. For Method Swizzling we use the global mapping table to solve this problem, in the mapping table to maintain the corresponding implementation function pointer address of Swizzles Method. Since Method Swizzling operations are not common, this table will not become very large. The new Method Swizzling mechanism is shown below.

Tagged Pointer Format change

Tagged; / / Tagged; / / Tagged; / / Tagged; / / Tagged; ** A Tagged object by setting its last bit to a Tagged bit and storing the data directly in the Pointer itself. Tagged Pointer is a “fake” object. Using Tagged Pointer increases access speed by 3 times and creation and destruction speed by 100 times.

Advances in Objective-C

When we look at the object pointer, on a 64-bit system we see a hexadecimal address such as 0x00000001003041e0, which we convert to binary representation as shown below

OBJC_TAG_NSAtom            = 0, 
OBJC_TAG_1                 = 1, 
OBJC_TAG_NSString          = 2, 
OBJC_TAG_NSNumber          = 3, 
OBJC_TAG_NSIndexPath       = 4, 
OBJC_TAG_NSManagedObjectID = 5, 
OBJC_TAG_NSDate            = 6, 
OBJC_TAG_7                 = 7
Copy the code

In the remaining fields, we can assign it the data it contains. In Intel, our Tagged Pointer object is represented as follows

Tagged Pointer of type OBJC_TAG_7 is an exception. It can use the next 8 bits as its extended type field. Based on this, we can support more than 256 Tagged Pointer types. Objects like UIColors or NSIndexSets.

In the previous article, we introduced the Tagged Pointer representation on Intel. In ARM64, we have changed things a bit.

It’s actually a minor optimization of objc_msgSend. We want the most common paths in msgSend to be as fast as possible. The most common path represents a plain object pointer. We have two unusual cases: Tagged Pointer and nil. It turns out that when we use the highest bit, we can check both with a single comparison. This saves conditional branches in msgSend compared to checking for nil and Tagged Pointer separately.

conclusion

In 2020, Apple made three optimizations for Objective-C

  • Class data structure changes: Saved more memory for the system.
  • Relative method address: Saves memory and improves performance.
  • Tagged Pointer format change: Improved msgSend performance.

Through optimization, I hope you can enjoy better and faster iPhone use experience.

Data changes to the class structure will be implemented in the latest Runtime release and already exist in MacOS 10.5.5. Optimization of relative method addresses is handled automatically in Xcode developmentTarget > 14. Tagged Pointer changes take effect on iOS 14, MacOS Big Sur, iPadOS 14.

Refer to the link

TypeEncodeing

Lets build Tagged Pointers

Advances in Objective-C

Limited welfare

This article is from WWDC20 Inside Reference. I recommend this column here.

The “WWDC Insider reference” series was initiated by the veteran Driver Weekly, Knowledge Collection and SwiftGG technology organizations. We’ve been doing it for a couple of years, and it’s been good. It is mainly for the annual WWDC content, do a selection, and call on a group of front-line Internet iOS developers, combined with their actual development experience, Apple documents and video content to do a second creation.

There are 213 sessions this year. WWDC20 Insider selected 135 of these sessions and produced 83 articles in just two weeks. It is on sale for a limited time, only 9.9 yuan, very special.

Read the article is not satisfied with the friends, pay close attention to subscribe to WWDC20 internal reference xiaozhuanlan.com/wwdc20 continue to read ~

Pay attention to our

We launched LSJCoiding, a weekly newsletter for veteran drivers, which is called a welcome update to each issue.