The first article is “iOS from source parsing Runtime (2)” : Focus on objc_object, objc_class, isa, but when you look at the first line of the implementation of the struct objc_object’s first function, Class isa (), you see ASSERT(! IsTaggedPointer ()), and since we’ve seen TaggedPointer countless times in the previous analysis of weak, it’s worth studying TaggedPointer a little bit more.
Tagged Pointer origin
Tagged Pointer is a concept proposed by Apple to save memory and improve performance on 64-bit processors. Its essence is to put the data of some smaller objects directly in the memory space of the pointer, and then use the pointer directly as an object, directly eliminating the process of opening up space for the object in the heap.
This begs the question, “Is all the memory of an object in the heap?” Yes. Here’s my own theory: By default, all of these objects are subclasses of NSObject, and when you look at the + (id)alloc function, you can see that all of the space that’s created at the end is malloc, While malloc is a C runtime function, the memory applied to it is managed by the C runtime, using heap memory management mode. This function actually requests memory from the operating system and then allocates it to the requester, while its internal maintenance has the allocation of the memory it requests in order to manage the memory it owns.
In September 2013, Apple introduced the Tagged Pointer concept in order to save memory and improve operation efficiency of the iPhone 5S (iPhone 5S) on the iOS platform for the first time. Let’s step by step analyze the advantages of Tagged Pointer and its implementation in combination with the source code. The objc_have_tagged_int macro is defined in objc-internal.h to indicate that Tagged Pointer is supported only in __LP64__ environments.
// Tagged pointer objects.
#if __LP64__
#define OBJC_HAVE_TAGGED_POINTERS 1
#endif
Copy the code
The length of the pointer variable is dependent on the address bus. After switching from a 32-bit to a 64-bit system architecture, the length of the pointer variable will also be increased from 32 to 64 bits. If nothing else, a 64-bit Pointer can represent an address of up to 2^64 bytes, which is 2^34 TERabytes. With 8 bytes of current device memory, Tagged Pointer is designed to use a lot of empty space. (For example, on an iPhone, you create an NSObject in the heap, print its address, and see that it takes up only 36 bits, leaving 28 bits zero.)
Tagged Pointer Memory usage
Just to be clear, NSInteger/NSUInteger is derived from the basic type long/int, and NSNumber, NSString, NSDate, and so on are all subclasses of NSObject.
#if __LP64__ || 0 || NS_BUILD_32_LIKE_64
// In a 64-bit environment, NSInteger and NSUInteger account for 8 bytes
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
// In a 32-bit environment, NSInteger and NSUInteger account for 4 bytes
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
Copy the code
// NSNumber is inherited from NSObject. // NSValue is inherited from NSObject. NSNumber is inherited from NSValue. @interface NSNumber : NSValue ... @end @interface NSValue : NSObject <NSCopying, NSSecureCoding> ... @end // NSString from NSObject @interface NSString: NSObject <NSCopying, NSMutableCopying, NSSecureCoding>... @end // NSDate from NSObject @interface NSDate: NSObject <NSCopying, NSSecureCoding>... @endCopy the code
In objc-Runtime-new. h, CF requires that all objects be at least 16 bytes. (The internal member variables of an object are mostly 8 bytes aligned. If the object memory is less than 16 bytes after the final alignment, the object memory is expanded to 16 bytes.)
size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
Copy the code
If you don’t have Tagged Pointer, when you store an NSNumber instance object of type NSInteger in a 32-bit environment, Requires the system to allocate 8 bytes (4 bytes for the (ideal state) object’s ISA pointer + 4 bytes for the stored value) in the heap, and 16 bytes (8 bytes for the (ideal state) object’s ISA pointer + 8 bytes for the stored value) in the 64-bit environment. Then add the size of the pointer variable on the stack (4 bytes for 32-bit and 8 bytes for 64-bit), and if the NSNumber object only stores a small number, switch from 32-bit to 64-bit without any logical change, The memory usage of the NSNumber instance object is also directly doubled. In a 64-bit iOS environment, when NSNumber is stored in the NSIntegerMax instance object, use the malloc_size function to return 32 bytes, that is, the system will open 32 bytes for it. An NSObject instance object system allocates 16 bytes of space for it.
- in
64
Bit environment, notTagged Pointer
When,NSNumber
The instance object is occupied in the heap16
Byte (NSObject
The object is16
Bytes,NSNumber
Object actual occupancy32
Byte) + pointer variable occupied in the stack8
Byte space, total24
Byte space. - in
64
Bit environment, usingTagged Pointer
When,NSNumber
The object occupies the heap0
The byte + pointer variable is occupied in the stack8
Byte space, total8
Byte space.
Tagged Pointer reduces the memory usage by at least half.
Example code:
NSObject *objc = [[NSObject alloc] init];
NSNumber *number = [[[NSNumber alloc] initWithInt:1] copy];
// NSNumber *number = [[NSNumber alloc] initWithLong:NSIntegerMax];
NSLog(@"objc pointer: %zu malloc: %zu CLASS: %@ ADDRESS: %p".sizeof(objc), malloc_size(CFBridgingRetain(objc)), object_getClass(objc), objc);
NSLog(@"number pointer: %zu malloc: %zu CLASS: %@ ADDRESS: %p".sizeof(number), malloc_size(CFBridgingRetain(number)), object_getClass(number), number);
// Console print:
objc pointer: 8 malloc: 16 CLASS: NSObject ADDRESS: 0x282f2c6e0
number pointer: 8 malloc: 0 CLASS: __NSCFNumber ADDRESS: 0xddb739a2fdf961f7 // Look at the address value in the stack
number pointer: 8 malloc: 32 CLASS: __NSCFNumber ADDRESS: 0x282d23da0 // Look at the address value in the heap
Copy the code
How do I know that a Pointer variable is Tagged
isTaggedPointer
The isTaggedPointer function, defined in objc-object.h, determines whether a Pointer variable isTaggedPointer.
inline bool
objc_object::isTaggedPointer(a)
{
return _objc_isTaggedPointer(this);
}
Copy the code
_objc_isTaggedPointer
_objc_isTaggedPointer is a static inline function defined in objc-internal. H that returns a bool.
Return true if PTR is a tagged pointer object. Does not check the validity of PTR’s class.
Return true if PTR is a Tagged Pointer. The class of the PTR is not checked for validity, only if the highest or lowest bit of the pointer memory space is 1 or 0.
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
// Force the pointer value to an unsigned long, and then do the operation with _OBJC_TAG_MASK to see if the result is still equal to _OBJC_TAG_MASK
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
Copy the code
SUPPORT_TAGGED_POINTERS
The SUPPORT_TAGGED_POINTERS defined in objc-config.h indicate that Tagged Pointers are available in Objective-C 2.0 and 64-bit systems.
// Define SUPPORT_TAGGED_POINTERS=1 to enable tagged pointer objects Be sure to edit tagged pointer SPI in objc-internal.h as well.
#if! (__OBJC2__ && __LP64__)
# define SUPPORT_TAGGED_POINTERS 0
#else
# define SUPPORT_TAGGED_POINTERS 1
#endif
Copy the code
OBJC_MSB_TAGGED_POINTERS
OBJC_MSB_TAGGED_POINTERS indicates whether strings are low priority (LSD) or high priority (MSD) under different platforms. Specific details can be referred to: “string low priority (LSD) and high priority (MSD) principle and C++ implementation”
#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__
// 64-bit Mac - tag bit is LSB
// Use string low priority (LSD) for 64-bit Mac
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
// In all other cases, string high priority (MSB) is used
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
Copy the code
_OBJC_TAG_MASK
_OBJC_TAG_MASK indicates that the 64th bit of a Pointer variable is Tagged Pointer for the highest-priority part of the string, and the first bit of a Pointer variable is Tagged Pointer for the lower-priority part of the string.
On aN iOS machine, determine whether Tagged Pointer is a 1 by looking at the 64th bit of the Pointer, and on an X86_64 Mac, by looking at the first bit of the Pointer. (that is, judge the highest bit in iOS, judge the lowest bit in MAC)
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63).#else
# define _OBJC_TAG_MASK 1UL.#endif
Copy the code
Example code:
// Run on the iPhone
// Tagged Pointer
NSNumber *number1 = @1;
NSLog(@"number1 %p %@ %zu", number1, [number1 class], malloc_size(CFBridgingRetain(number1)));
NSNumber *number2 = @2;
NSLog(@"number2 %p %@ %zu", number2, [number2 class], malloc_size(CFBridgingRetain(number2)));
NSString *a = [[@"a" mutableCopy] copy];
NSLog(@"a %p %@ %zu", a, [a class], malloc_size(CFBridgingRetain(a)));
NSString *ab = [[@"ab" mutableCopy] copy];
NSLog(@"ab %p %@ %zu", ab, [ab class], malloc_size(CFBridgingRetain(ab)));
NSString *b = [NSString stringWithFormat:@"b"];
NSLog(@"b %p %@ %zu", b, [b class], malloc_size(CFBridgingRetain(b)));
NSString *c = [NSString stringWithFormat:@"c"];
NSLog(@"c %p %@ %zu", c, [c class], malloc_size(CFBridgingRetain(c)));
/ / not Tagged Pointer
NSNumber *number3 = [[NSNumber alloc] initWithInteger:NSIntegerMax];
NSLog(@"number3 %p %@ %zu", number3, [number3 class], malloc_size(CFBridgingRetain(number3)));
NSString *abcd__ = [NSString stringWithFormat:@"abcdefghijklmnopqrstuvwxyz"];
NSLog(@"abcd__ %p %@ %zu", abcd__, [abcd__ class], malloc_size(CFBridgingRetain(abcd__)));
// Console print
number1 0xd3bc9b2fde3f08b4 __NSCFNumber 0 // 0xd... => 0b1101...
number2 0xd3bc9b2fde3f0884 __NSCFNumber 0 // 0xd... => 0b1101...
a 0xc3bc9b2fde3f0eb7 NSTaggedPointerString 0 // 0xc... => 0b1100...
ab 0xc3bc9b2fde392eb4 NSTaggedPointerString 0 // 0xc... => 0b1100...
b 0xc3bc9b2fde3f0e87 NSTaggedPointerString 0 // 0xc... => 0b1100...
c 0xc3bc9b2fde3f0e97 NSTaggedPointerString 0 // 0xc... => 0b1100...
number3 0x282bcc540 __NSCFNumber 32 // Not all 64 bits are used. The highest bits are 0
abcd__ 0x2805e3150 __NSCFString 48 // Not all 64 bits are used. The highest bits are 0
Copy the code
Analyzing the print, we can see that all of Tagged Pointer’s 64-bit data usage is almost full, with the highest bit being 1 and the malloc_size returning 0. In contrast, untagged Pointer has no space for the object. The first member variable of a normal Objective-C instance object is an ISA Pointer to the memory address of the class object, and you can see by the break point that all Tagged Pointers are 0x0, And when Tagged Pointer is of type NSNumber, the class function will still print __NSCFNumber. Apple didn’t design a separate class for Tagged Pointer. NSString is printing NSTaggedPointerString, so that raises another question, how does TaggedPointer get the class that it belongs to?
Why can we specify the highest or lowest bit to mark a Tagged Pointer
This is because memory is allocated in integer multiples of 2, so it is impossible to allocate a normal memory address that ends in 1. By identifying the lowest as 1, you can distinguish it from other normal Pointers.
So why is it possible to identify if the highest bit is 1? (Currently, the memory of iOS devices is fixed, such as iPhone, iPad, iWatch are fixed, unlike MAC products, we can add our own memory.) This is because the 64-bit operating system, the device generally does not have that much memory. In 64-bit iOS, only 36 significant bits are used for heap area addresses. In other words, the high 16 bits are 0. Therefore, you can set the highest bit identifier to 1 to indicate Tagged Pointer. So if one bit is Tagged Pointer, what’s the rest of the information? So you can imagine, you have to have some bits to represent the type of this Pointer, otherwise you get a Tagged Pointer and you don’t know what type it is, so you can’t parse it.
How do I get a class from Tagged Pointer
Normal Objective-C objects use isa and ISA_MASK to get the memory address of a class object. So how does Tagged Pointer get the memory address of a class object?
So if you go ahead and look at OBJC_HAVE_TAGGED_POINTERS, you can see that objc_tag_index_t enumerates the classes that might be Tagged Pointers.
objc_tag_index_t
// Tagged pointer layout and usage is subject to change on different OS versions.
// The layout and usage of Tagged pointer may vary from OS to OS.
// Tag indexes 0.. <7 have a 60-bit payload.
/ / 0.. The <7 type has 60 bits of payload content.
// Tag index 7 is reserved.
// 7 is reserved.
// Tag indexes 8.. <264 have a 52-bit payload.
/ / 8.. The type <264 has 52 bits of payload content. (numbers between 8 and 19,19 and 264 are not listed in the enumeration value.)
// Tag index 264 is reserved.
// 264 is reserved.
#if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L
enum objc_tag_index_t : uint16_t
#else
typedef uint16_t objc_tag_index_t;
enum
#endif
{
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2.// NSString
OBJC_TAG_NSNumber = 3.// NSNumber
OBJC_TAG_NSIndexPath = 4.// NSIndexPath
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6.// NSDate
// 60-bit reserved
/ / keep
OBJC_TAG_RESERVED_7 = 7.// 52-bit payloads
OBJC_TAG_Photos_1 = 8,
OBJC_TAG_Photos_2 = 9,
OBJC_TAG_Photos_3 = 10,
OBJC_TAG_Photos_4 = 11,
OBJC_TAG_XPC_1 = 12,
OBJC_TAG_XPC_2 = 13,
OBJC_TAG_XPC_3 = 14,
OBJC_TAG_XPC_4 = 15,
OBJC_TAG_NSColor = 16,
OBJC_TAG_UIColor = 17,
OBJC_TAG_CGColor = 18,
OBJC_TAG_NSIndexSet = 19.// The first 60 bits of load content
OBJC_TAG_First60BitPayload = 0.// The last 60 bits are loaded
OBJC_TAG_Last60BitPayload = 6.// The first 52 bits are loaded
OBJC_TAG_First52BitPayload = 8.// The last 52 bits are loaded
OBJC_TAG_Last52BitPayload = 263./ / keep
OBJC_TAG_RESERVED_264 = 264
};
#if__has_feature(objc_fixed_enum) && ! defined(__cplusplus)
typedef enum objc_tag_index_t objc_tag_index_t;
#endif
Copy the code
_objc_taggedPointersEnabled
// Returns true if tagged pointers are enabled.
// The other functions below must not be called if tagged pointers are disabled.
// Return true if Tagged Pointer is enabled. If Tagged Pointer is disabled, the following functions cannot be called.
static inline bool
_objc_taggedPointersEnabled(void)
{
Objc_debug_taggedpointer_mask in SUPPORT_TAGGED_POINTERS
// uintptr_t objc_debug_taggedpointer_mask = _OBJC_TAG_MASK;
extern uintptr_t objc_debug_taggedpointer_mask;
return(objc_debug_taggedpointer_mask ! =0);
}
Copy the code
Objc-runtimenew.mm has a comment on Tagged Pointer Objects that reads:
/* * Tagged pointer objects. * * Tagged pointer objects store the class and the object value in the object pointer; * the "pointer" does not actually point to anything. * * Tagged pointer Objects store class and object data in an object pointer that does not actually point to anything. * Tagged Pointer Objects currently use this representation: * Tagged pointer Objects currently use this representation * * (LSB) * 1 bit set if tagged, clear if ordinary object pointer * 3 bits tag index // Tag type * 60 bits payload // Payload capacity (Storage object data) * * (MSB)(64-Bit iPhone) * The tag index defines The object's class. * The payload format is defined by The Object's class. * Tag index Indicates the class to which the object belongs. The payload format is defined by the class of the object. * * If the tag index is 0b111, the tagged pointer object uses an "extended" representation, * allowing more classes but with smaller payloads: * * If the tag index is 0b111(7), the tagged pointer object uses an "extended" representation, allowing more classes but a smaller payload: * (LSB) * 1 bit set if tagged, clear if ordinary object pointer * 3 bits 0b111 * 8 bits Extended Tag index * 52 bits payload // Extended Tag index * 52 bits payload There are only 52 * (MSB) * * Some architectures reverse the MSB and LSB in these representations. Some architectures reverse MSB and LSB. * * This representation is subject to change. Representation-agnostic SPI is: * objc-internal.h for class implementers. * objc-gdb.h for debuggers. */
Copy the code
_objc_decodeTaggedPointer
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
Copy the code
Decoding Tagged Pointer is an XOR operation with objc_debug_TaggedPointer_obfuscator.
_objc_getTaggedPointerTag
// Extract the tag value from the given tagged pointer object.
// Assumes ptr is a valid tagged pointer object.
// Does not check the validity of ptr's tag.
// Fetch the tag value from the given tag pointer object.
// Assume that the PTR is a valid marked pointer object.
// Do not check the validity of the PTR tag.
static inline objc_tag_index_t
_objc_getTaggedPointerTag(const void * _Nullable ptr);
static inline objc_tag_index_t
_objc_getTaggedPointerTag(const void * _Nullable ptr)
{
// ASSERT(_objc_isTaggedPointer(ptr));
uintptr_t value = _objc_decodeTaggedPointer(ptr);
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
uintptr_t extTag = (value >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK;
if (basicTag == _OBJC_TAG_INDEX_MASK) {
return (objc_tag_index_t)(extTag + OBJC_TAG_First52BitPayload);
} else {
return (objc_tag_index_t)basicTag; }}Copy the code
It’s all shifts and operations.
classSlotForBasicTagIndex
In the objc-Runtimenew. mm definition, the Class pointer is returned according to objc_tag_index_t.
// Returns a pointer to the class's storage in the tagged class arrays.
// Assumes the tag is a valid basic tag.
// Return a pointer to the tagged class from the tagged class array.
// Assume the tag is a valid tag.
static Class *
classSlotForBasicTagIndex(objc_tag_index_t tag)
{
uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator
>> _OBJC_TAG_INDEX_SHIFT)
& _OBJC_TAG_INDEX_MASK);
uintptr_t obfuscatedTag = tag ^ tagObfuscator;
// Array index in objc_tag_classes includes the tagged bit itself
// The array index in objc_tag_classes includes the tag bit itself
#if SUPPORT_MSB_TAGGED_POINTERS
return &objc_tag_classes[0x8 | obfuscatedTag];
#else
return &objc_tag_classes[(obfuscatedTag << 1) | 1];
#endif
}
Copy the code
classSlotForTagIndex
// Returns a pointer to the class's storage in the tagged class arrays,
// or nil if the tag is out of range.
// Return a pointer to the tagged class from the tagged class array.
Return nil if the tag is outside the interval.
static Class *
classSlotForTagIndex(objc_tag_index_t tag)
{
if (tag >= OBJC_TAG_First60BitPayload && tag <= OBJC_TAG_Last60BitPayload) {
return classSlotForBasicTagIndex(tag);
}
if (tag >= OBJC_TAG_First52BitPayload && tag <= OBJC_TAG_Last52BitPayload) {
int index = tag - OBJC_TAG_First52BitPayload;
uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator
>> _OBJC_TAG_EXT_INDEX_SHIFT)
& _OBJC_TAG_EXT_INDEX_MASK);
return &objc_tag_ext_classes[index ^ tagObfuscator];
}
/ / returns nil
return nil;
}
Copy the code
objc_tag_classes
extern "C" {
externClass objc_debug_taggedpointer_classes[_OBJC_TAG_SLOT_COUNT]; . }#define objc_tag_classes objc_debug_taggedpointer_classes
Copy the code
A global search for objc_tag_classes only shows that it is an array of external classes.
Objc_debug_taggedpointer_obfuscator and initializeTaggedPointerObfuscator function
// Definition in Private Header/objc-gdb.h
// Tagged Pointers were confused by xor with objc_debug_taggedpointer_obfuscator
// tagged pointers are obfuscated by XORing with a random value
// decoded_obj = (obj ^ obfuscator)
OBJC_EXPORT uintptr_t objc_debug_taggedpointer_obfuscator
OBJC_AVAILABLE(10.14.12.0.12.0.5.0.3.0);
/ * * initializeTaggedPointerObfuscator * Initialize objc_debug_taggedpointer_obfuscator with randomness. * initialized with random values Objc_debug_taggedpointer_obfuscator. * * The tagged pointer obfuscator is intended to make it more difficult for an attacker to construct a particular object As a tagged pointer, * tagged pointer The obfuscator is designed to make it harder for an attacker to construct a particular object as tagged Pointer, In the presence of a buffer overflow or other write control over some memory. In the presence of a buffer overflow or other write control over some memory. * The obfuscator is XORed with The tagged payload when setting or retrieving payload values. The obfuscator will specify or with Tagged Pointers. * They are filled with randomness on first use. * /
static void
initializeTaggedPointerObfuscator(void)
{
// Objc_debug_TaggedPointer_obfuscator is a global variable of type unsigned Long
// This is an environment variable that disables tagged confusion
// OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION, "disable obfuscation of tagged pointers")
if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) | |// Set the obfuscator to zero for apps linked against older SDKs,
// For applications linked to the older SDK, set the obfuscator to zero,
// in case they're relying on the tagged pointer representation.
// In case they rely on tagged Pointer.
DisableTaggedPointerObfuscation) {
objc_debug_taggedpointer_obfuscator = 0;
} else {
// Pull random data into the variable, then shift away all non-payload bits.
// Put the random data into the variable and remove all the insignificant bits.
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
// And ~_OBJC_TAG_MASKobjc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK; }}Copy the code
Basically see classSlotForBasicTagIndex function, objc_debug_taggedpointer_obfuscator is the dynamic runtime system created by salt, each run is different, Then the rest of the action is to shift and bit according to the values defined by the different platform macros.
Validation example:
#import "objc-internal. H"
NSString *str1 = [NSString stringWithFormat:@"a"];
NSNumber *num1 = [NSNumber numberWithInteger:1];
NSLog(@"str1 class: %@", _objc_getClassForTag(_objc_getTaggedPointerTag((__bridge void *)str1)));
NSLog(@"num1 class: %@", _objc_getClassForTag(_objc_getTaggedPointerTag((__bridge void *)num1)));
// Print the result:
str1 class: NSTaggedPointerString
num1 class: __NSCFNumber
Copy the code
Get the value of Tagged Pointer
_objc_getTaggedPointerValue and _objc_getTaggedPointerSignedValue
// Extract the payload from the given tagged pointer object.
// Assumes ptr is a valid tagged pointer object.
// The payload value is zero-extended.
// Extract the payload from the given tagged pointer object.
// Assume that PTR is a valid tagged pointer object.
// The payload value is zero extended.
static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr);
static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr)
{
// ASSERT(_objc_isTaggedPointer(ptr));
uintptr_t value = _objc_decodeTaggedPointer(ptr);
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
if (basicTag == _OBJC_TAG_INDEX_MASK) {
return (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
} else {
return(value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT; }}static inline intptr_t
_objc_getTaggedPointerSignedValue(const void * _Nullable ptr)
{
// ASSERT(_objc_isTaggedPointer(ptr));
uintptr_t value = _objc_decodeTaggedPointer(ptr);
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
if (basicTag == _OBJC_TAG_INDEX_MASK) {
return ((intptr_t)value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
} else {
return ((intptr_t)value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT; }}Copy the code
The implementation of the functions is very simple, first Tagged Pointer decoding, with objc_debug_taggedPointer_obfuscator xor operation, and then according to the macro definition of different platforms shift operation.
Example code:
#import "objc-internal. H"
NSString *str1 = [NSString stringWithFormat:@"a"];
NSString *str2 = [NSString stringWithFormat:@"ab"];
NSString *str3 = [NSString stringWithFormat:@"abc"];
uintptr_t value1 = _objc_getTaggedPointerValue((__bridge void *)str1);
uintptr_t value2 = _objc_getTaggedPointerValue((__bridge void *)str2);
uintptr_t value3 = _objc_getTaggedPointerValue((__bridge void *)str3);
NSLog(@"value1: %lx", value1);
NSLog(@"value2: %lx", value2);
NSLog(@"value3: %lx", value3);
/ / print:
value1: 611
value2: 62612
value3: 6362613
NSNumber *num1 = [NSNumber numberWithInteger:11];
NSNumber *num2 = [NSNumber numberWithInteger:12];
NSNumber *num3 = [NSNumber numberWithInteger:13];
uintptr_t value1 = _objc_getTaggedPointerValue((__bridge void *)num1);
uintptr_t value2 = _objc_getTaggedPointerValue((__bridge void *)num2);
uintptr_t value3 = _objc_getTaggedPointerValue((__bridge void *)num3);
NSLog(@"value1: %lx", value1);
NSLog(@"value2: %lx", value2);
NSLog(@"value3: %lx", value3);
/ / print:
value1: b3
value2: c3
value3: d3
Copy the code
In the first group of NSString printing, 0x61, 0x62, and 0x63 respectively correspond to the ASCII codes of A, B, and C, and the last digit indicates the length of the string. In the printing of the second set of NSNumbers: 0xb, 0xc, and 0xd correspond to ASCII codes 11, 12, and 13 respectively. The following 3 probably corresponds to enum OBJC_tag_INDEX_T OBJC_TAG_NSNumber = 3 indicates that the type is OBJC_TAG_NSNumber.
Tagged Pointer Maximum value that can be stored
Based on the previous analysis and when Tagged Pointer is of type NSNumber, on the X86_64 Mac platform:
NSNumber *number = [[NSNumber alloc] initWithInteger: pow(2.55) - 2];;
NSLog(@"number %p %@ %zu", number, [number class], malloc_size(CFBridgingRetain(number)));
/ / print:
number 0x10063e330 __NSCFNumber 32
NSNumber *number = [[NSNumber alloc] initWithInteger: pow(2.55) - 3];;
NSLog(@"number %p %@ %zu", number, [number class], malloc_size(CFBridgingRetain(number)));
/ / print:
number 0x21a60cf72f053d4b __NSCFNumber 0
Copy the code
On the X86_64 Mac platform, store an NSString Tagged Pointer. A Pointer has 8 bytes and 64 bits. The first bit is used to mark a Tagged Pointer. The second to fourth bits are used to mark the Tagged Pointer type. The last four bits after decoding are used to mark the length of the value. So there are only 56 bits left to store the value. In this case, 8 characters would not be TaggedPointer if each character was encoded in ASCII, but NSTaggedPointerString uses a different encoding:
- If the length is between
0
到7
, directly store the string in 8-bit encoding. - If the length is
8
或9
, the string is stored in a six-bit code, using an encoding tableeilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX
. - If the length is
10
或11
, store the string in a five-digit code, using an encoding tableeilotrm.apdnsIc ufkMShjTRxgC4013
.
@”aaaaaaaa” decoded TaggedPointer is 0x2082082082088. After deducting the length of the last 4 bits, it is 0x20820820820. It is only 6 bytes, but because the length is 8, it needs to be decoded. 6 bits are grouped into a group of 0x08080808080808, which is exactly 8 bytes in length. Adopt coding table eilotrm. ApdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P – B79AFKEWV_zGJ/HYX, is just a subscript 8.
The value of TaggedPointer decoded by @aaaaaaaaaa is 1084210842108a. After deducting the length of the last 4 bits, it is 1084210842108. It is only 6.5 bytes, but because the length is 10, it needs to be decoded. Five bits are grouped into a group of 0x080808080808080808, which is exactly 10 bytes in length. Using the encoding table eilotrm.apdnsIc ufkMShjTRxgC4013, the subscript 8 happens to be a.
The + characters are not seen in the code table, so use the + characters to test. Seven + characters should be NSTaggedPointerString, and eight + characters should be normal __NSCFString objects.
For more information on the storage of strings, see “Strings using Tagged Pointer”.
Refer to the link
Reference link :🔗
- The difference between malloc and calloc
- Objective-c Tagged Pointer implementation
- A string that takes Tagged Pointer
- TaggedPointer
- Get deeper into Tagged Pointer