Memory layout

  • Stack: Local variables, methods, parameters, functions, method Pointers, generally starting with 0x7, access — accessed through registers
  • Heap: a place where space needs to be created. Objects allocated by alloc, block,copy. Generally starting with 0x6, access – by object -> heap address -> pointer to stack
  • The program loads into memoryUsually starts with 0x1
    • Uninitialized data (.bss)
    • Initialized data (.data)
    • Code snippet (.text) Program code that is loaded into memory
    • Kernel area: system call

staticModified member variables do not occupy memory. Static applies to the global partition and occupies the memory of the global partition, but does not occupy the heap in which the structure resides

Memory management scheme -TaggedPointer

First of all, what is Tagged

  • 1: TaggedPointer is used to store small objects such as NSNumber and NSDate, NSString/NSIndexPath
  • 2: The value of the TaggedPointer pointer is no longer an address, but a real value. So, it’s not really an object anymore, it’s just a normal variable in an object’s skin. Therefore, its memory is not stored in the heap and does not require malloc and free
  • 3. Memory read (objc_msgSend) is 3 times more efficient and 106 times faster than before.

The structure of the common object pointer on the ARM64 is 0x00000001003041e0.

We have 64 bits, but we don’t really use all of them, we only use the middle bits in a real object pointer. Because of alignment requirements, the low position is always 0 and the object must always be at an address that is a multiple of the size of the pointer. Because the address space is limited, the high level is always 0, we don’t actually use 2^64, these high and low levels are always 0.

Selecting one of these bits, where the high and low levels are always 0, and setting it to 1, lets us know that this is not a true object pointer, and gives the other positions some additional meaning. We call this pointer Tagged; for example, we can insert values in other bits

101010 is 42 when converted to base 10. An identifier 1 is added to the end to indicate that this is Tagged, and the system can then treat these objects as Pointers to objects. Instead of assigning a small number object to each of these situations, which are actually confused with random values initialized at process startup, this security makes it difficult to fake a Tagged margin

Set the low value to 1 to indicate that it is a Tagged pointer and distinguish it from the real pointer. The next 3 bits are the tag number. So this is the type of tagged pointer, so for example, 3 means it’s an NSNumber,6 means it’s an NSDate, and since there’s a three-bit tag there are eight possible tag types, and the rest is payload and this is the type of data that you can use anywhere you want.

Now tag 7 has a special case where it represents an extended tag. The extended note can use the next 8 bits to encode the type, which means 256 more tag types at the cost of reducing the Payload

For the above features, it can be used to store some user interface UIColor and NSIndexSets

If you’re a Swift developer, you can create your own Tagged Pointers. If you’ve ever used an enumeration with associated values, that’s a class like Tagged Pointer. The Swift runtime stores the enumeration discriminator in an alternate bit of the associated value payload. And Swift’s use of value types actually makes Tagged Pointer seem less important. Because the value no longer needs to be exactly the size of a pointer, for example the Swift UUID type can be two words and remain inline, rather than assigning a separate object because it does not fit inside a pointer. This is Tagged Pointer on Inter

Tagged pointers on ARM64

On ARM64 these are reversed, with the highest bit set to 1 instead of the lowest bit being usedTagged pointerAnd then the next 3 bits are the tag bits. WhyARM64How about this design?

This is actually a little optimization for objc_msgSend, we want the path of objc_msgSend to be as fast as possible, and the most common path is a plain pointer, and we have two less common cases called Tagged Pointer and nil

It turns out that when we use the highest bit, we can check both cases at once, as opposed to checking Tagged Pointer and nil separately, saving a conditional branch for the common case in msgSend.

As with Inter, the next 8 bits are used as extension tags, and the rest is the payload, which is actually the old format used in iOS13. Apple made some changes in iOS14 in 2020: Keep the tag bit at the top, because msgSend’s optimization is still very useful, moving the tag number to the bottom three bits, which will occupy the top eight bits after the tag bit if you are using an extended tag

Why do you do that? If we look at normal Pointers, our existing tools like dynamic linking will Ignore the first 8 bits of the pointer, which is a Top Byte Ignore feature of ARM. Instead, we will place the extension tag in the Top Byte Ignore bit. For an aligned pointer, the last 3 bits are always 0. You just add a little number to the pointer, 7 which is an extension label that changes the low value to 1

This means that we can actually put the pointer above into the payload of an extended tag pointer, and the result is a Tagged Pointer that has a normal pointer in its payload. Why is this useful? It turns on Tagged Pointer, the ability to reference constant data in binary files, such as strings or other data structures. Otherwise they will have to eat up dirty Memory

The correct way to do this is to use APIisKindOfClass, etc. These Tagged Pointer items can be retrieved using standard APIS. This also applies to CF types

Under the x86-64Tagged Pointerstructure

Objc_debug_taggedpointer_obfuscator = objc_debug_taggedPOinter_obfuscator = objc_debug_taggedPointer_obfuscator = taggedPointer

static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
    uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
    if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
        return (void *)ptr;
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    uintptr_t permutedTag = _objc_basicTagToObfuscatedTag(basicTag);
    value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
    value |= permutedTag << _OBJC_TAG_INDEX_SHIFT;
#endif
    return (void *)value;
}
Copy the code

So in the above code we can xor an objC_debug_taggedPOinter_obfuscator to get the real value

uintptr_t
kc_objc_decodeTaggedPointer(id ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
Copy the code

Let’s print them out separatelyNSString``NSNumberAs well asNSIndexPath Compared withOBJC_TAGType table, which corresponds exactly to.

ARM64Under theTagged Pointerstructure

As mentioned above, in iOS14 and beyond, the tag bits are the last three bits, not the firstSo, how do we print out the above code on a real machine? We saw when we obfuscated the code that if! DisableTaggedPointerObfuscationTurn off obfuscation, global searchDisableTaggedPointerObfuscation

static void
initializeTaggedPointerObfuscator(void)
{
    if(! DisableTaggedPointerObfuscation) {// Pull random data into the variable, then shift away all non-payload bits.
        arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;

#if OBJC_SPLIT_TAGGED_POINTERS
        // The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
        objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);

        // Shuffle the first seven entries of the tag permutator.
        int max = 7;
        for (int i = max - 1; i >= 0; i--) {
            int target = arc4random_uniform(i + 1);
            swap(objc_debug_tag60_permutations[i],
                 objc_debug_tag60_permutations[target]);
        }
#endif
    } else {
        // Set the obfuscator to zero for apps linked against older SDKs,
        // in case they're relying on the tagged pointer representation.
        objc_debug_taggedpointer_obfuscator = 0; }}Copy the code

LibObjc global search locates here:

OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION,    "disable obfuscation of tagged pointers")
Copy the code

So we’re in XcodeschemeOn the configurationNow that it’s running, there’s no confusion,NSStringandNSNumber(refer to the console printout above)

TaggedPointerRelated interview questions

Which of the following codes will crash?

- (void)taggedPointerDemo {
  
    self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i<100; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"cooci"];
             NSLog(@ "% @".self.nameStr); }); }}Copy the code
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@ "come");
    // Multithreaded reading and writing
    // setter -> retian release
    for (int i = 0; i<100; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"cooci_ learn harmoniously without being impatient"];
            NSLog(@ "% @".self.nameStr); }); }}Copy the code

During multiple operations (multiple clicks on the screen), we found that the second code crashed because the first nameStr type was NSTaggedPointerString

The second nameStr is of type **NSCFString**

The reason for this is that the type changes from TP to OC when the character length is >=10

objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    if (slowpath(isTaggedPointer())) return (id)this;
    // ...
}
Copy the code

Multi-thread reading is a process of retain and release. We can see from the source code that if it is a small object, it will be returned directly, so there is no wild pointer. This means small objects are not managed by ARC memory at all

Retain and release

Retain process, we open the libObjc source to locate this method

objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
Copy the code

Extra_rc: When representing the referential count value of this object, which is actually the referential count value minus 1, the following hash table has_sideTABLE_rc is used if the location is required to be full.

  1. In the first place to judgeisTaggedPointer
  2. judgenonpointerIf it’s anonpointerIs the operation on the ISA bit,
    1. notnonpointerHash table operations reference count (mainly because there is noneextra_rcIn thenonpointe_isaThe object’s reference count is stored in the “.
    2. Is it being released?
    3. isnonpointerDirectly toisa.bitsBit operation findingextra_rcTo add and subtractretain/release) operationnewisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
    4. If it’s full, in the real worlduintptr_t extra_rc : 8Can store 2^8=256 sizes. If it’s full, it finds a new place to store it, and that place is the hash table, and the hash table is 1/2

Release process: As with the above process, the only thing to note is that after the hash table is also decrement, the dealloc operation is performed

((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc))
Copy the code