Write in the front: the exploration of the underlying principles of iOS is my usual development and learning in the continuous accumulation of a step forward road. I hope it will be helpful for readers to record my journey of discovery.Copy the code

The directory is as follows:

  1. Exploring the underlying principles of iOS alloc
  2. Exploration of the underlying principles of iOS structure in vivo alignment
  3. The nature of the object explored by iOS underlying principles & the underlying implementation of ISA
  4. The underlying principles of the ISA-class (part 1)

Summary column for the above

  • Summary of the phase of iOS underlying principle exploration

Writing in the front

In the previous article exploring the underlying principles of iOS – Isa – The Underlying Principles of Class Structure (Part 1), we analyzed the path of ISA and the relationship of class inheritance, and combined with the inheritance of ISA and class to do the comprehensive analysis; Then we explore the underlying structure of the class and focus on class_datA_bits_t bits, With class_rw_t* data and its internal properties, methods, protocols, and ro-ivars, and validated our conjecture that class methods are stored in metaclasses. Today, we continue to explore the underlying principles of classes such as caching.

Cache means cache, so either cache methods or cache properties. We explore the cache step by step according to the rhythm of exploring bits, also through memory translation. From the previous article, we know that the offset to bits is 32 bytes and the offset to cache is 16 bytes.

To prepare

IMPSELThe relationship between

  • SELMethod number (equivalent to the name of a table of contents in a book)
  • IMP: Function pointer address (equivalent to the page number of a table of contents in a book)

First, we know what to find in the book (the names in the SEL catalogue); Then, find the corresponding page number by name (IMP); Finally, page numbers are used to locate specific content.

cache_tWhat is the data structure

First, let’s look at the memory structure of cache_t

You can’t see it directly from the memory structurecache_tThat property in is what we want, so, according to what we have forcacheTo cache methods or attributes, there must be some way to add, delete, modify and check, so let’s take a lookcache_tProvided methods until we see that there is oneinsertmethodsDon’t hesitate to go in and see the implementation

You can see that a BUCket_t structure is operated on during the insert. Let’s take a look at bucket_t in detail

Sure enough, sel and IMP are seen here at bucket_t.

At this point, let’s briefly summarize the cache_t data structures:

Storage of LLDB validation methods

Next, let’s do some verification and check the following:

So, we can makeSMPersonAn instance of thepExecute the instance methodAnd then, get it againcache_tIn thebuckets()We found that there was still no contentSo, in the same way, let’s gobuckets_tInside the search to see the relevant method, sure enough, found the familiarsel()imp() So, continue with LLDB

cache_tProcess analysis

First of all cache_t is a structure and _bucketsAndMaybeMask is a uintptr_t which is an unsigned long and it stores buckets and maybeMask as it’s called.

To be a cache, you need to write first and then read. So we’re going to follow our path to cache_t

insert()

void insert(SEL sel, IMP imp, id receiver);
Copy the code

It is also clear from the method that the SEL and IMP are inserted into the receiver.

INIT_CACHE_SIZEFor 4

_maybeMask.store

When an instance calls a method, it creates a new bucket, where the IMP does not store directly, but instead stores the 8-byte address pointer. Otherwise, it will take up a lot of memory space. When you need to read class information, you follow the pointer address to find the implementation. This way, you don’t have to read the class information slowly every time.

Occupied = 1; maybeMask = 3;

Buckets is an array, where the data is stored, and at this point, we don’t know, we do a cache_hash algorithm here, and we get a hash address.

cache_hash

Next, look for sel imp CLS () in place of bucket_t-b at occupied++ when sel() does not exist. If sel() already exists, it is not cached.

incrementOccupied()

When you continue to store, you will determine if it exceeds 75% of the capacity, no, normal storage; If yes, determine that capacity exists and expand the capacity twice (4 x 2 = 8, m = 8-1 = 7). After capacity expansion, all the previous storage will be wiped out, and there will only be a new storage method sel IMP. < to clear the old bucket and capacity address by opening a new buckets >

cache_fill_ratio

collect_free

Why not just add it instead of expanding it and clearing it?

Originally we have opened up memory is unable to change, open up a new memory space is a new address, the original stored value is not taken over, because memory array translation is very expensive, and apple at the bottom of a principle is the newer the better. During expansion, there is a low chance that the method that was called during expansion will be used again. However, the method that was called during expansion is important, so the system will only store the expansion call.

Of course, we can explore, after the expansion, call the previous method again, and find that the cache will continue to add; But in this case, it’s out of order because of the cache_hash algorithm up here.

In the endCache_t process

details

_bucketsAndMaybeMask

Other buckets() can be obtained only by panning the pointer to _bucketsAndMaybeMask.

bucket()

Take the address of _bucketsAndMaybeMask and do the and to force it to bucket_t *

bucketsMaskThe values are different in different schemas

CACHE_MASK_STORAGE