The amount of memory allocated for running the program is certain, so memory management is very necessary to avoid memory leaks in development, we are due to timely release. So how is memory managed?
1. The ARC and MRC
In iOS development, we have roughly two memory management methods: MRC (manual management) and ARC (automatic management).
- MRC: Follow these rules in a manual memory management environment
- The object is
create
, reference counting+ 1
; - When an object is referenced by another pointer,
Reference count +1
, you need to call it manually【 objc retain 】
; - When the pointer variable no longer refers to the object, to manually release, call
【 objc release.
, reference count -1; - When the reference count is 0, the system will
Automatic destruction
This object.
Basically, in the MRC environment, who creates objects, who releases them; He who quotes, he who governs.
- ARC: Automatic memory management,
The compiler
Help me to operateReference counting
,retain
.release
Such operations.
2. Five memory areas
As we’ve seen before, applications are allocated up to 4 gb of virtual memory for 32-bit operating systems and up to 8 GB for 64-bit operating systems, but applications usually apply for 4 GB of virtual memory in order to remain compatible. In iOS development, taking 4g memory as an example, the memory distribution for program loading is as follows
The kernel area
Occupying 1GB, the main area of the system for kernel processing operations.- Five regions
The stack area
: function, method; The memory address of the stack area usually starts with 0x7The heap area
Block copy: alloc allocates objects, block copy; The memory address of the heap usually starts with 0x6BBS section
: uninitialized global variable, static variable; The memory address starts with 0x1Data segment
: initialized global variable, static variable; The memory address starts with 0x1Code segment
: Program code, loaded into memory.
- Retention area: The main reason is
0x00000000
saidnil
You can’t just use nil to represent a segment, so you give a separate segment of memory for thatDeal with nil
, etc.
3. Memory management scheme
Memory management solutions also include:
taggedPointer
: is used to deal with small objects, such as NSNumber, NSDate, and small NSStringNonpointer_isa
: isa of non-pointer typeSideTables
: hash table, including reference count list and weak reference table
3.1 TaggedPointer
Let’s first look at why the original object is a waste of memory. Suppose we want to store an NSNumber object whose value is an integer. Normally, if this integer is just a plain NSInteger variable, then the memory it consumes is dependent on the CPU’s bit, accounting for 4 bytes on a 32-bit CPU and 8 bytes on a 64-bit CPU. The size of the pointer type is also generally related to the CPU bits, with a pointer occupying 4 bytes of memory on a 32-bit CPU and 8 bytes on a 64-bit CPU. To address the memory footprint and efficiency issues mentioned above, Apple introduced Tagged Pointer. Because the values of variables such as NSNumber and NSDate themselves often require less than 8 bytes of memory
We can also look at WWDC2013’s Session 404 Advanced in objective-c video to see how apple’sTagged Pointer
Introduction of features:
Tagged Pointer
Designed to store small objects such asNSNumber
andNSDate
Tagged Pointer
The value of a 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.- Three times more efficient at memory reads and 106 times faster at creation.
Thus, Apple’s introduction of Tagged Pointer not only reduces the memory footprint of programs on 64-bit machines, but also improves their running efficiency. After a perfect solution to the problem of storage and access efficiency of small memory objects, WWDC2020 changed the format of marker pointer on ARM64. Due to alignment requirements, the low value is always zero. Multiple of pointer size, address high is always zero because address space is limited. We don’t actually go all the way to 2 to 64 and these highs and lows are always zero. So let’s take one of these bits that are always zero and turn it into one that immediately tells us that this is not really a pointer to an object and then we can assign some other meaning to all the other bits. We call this a marker pointer.
3.1.1 TaggedPointer in the simulator environment
So let’s run the following code, address starting at 0x9 which is different from what we said before in the heap stack area, type NSTaggedPointerString
NSCFConstantString
: string constant, which is a compile-time typeconstant
The retainCount value is so large that operating on it does not cause reference count changes and is stored inString constant area
NSCFString
: it is inThe runtime
To create theNSString
Subclass, the reference count is incremented by 1 and stored inThe heap
NSTaggedPointerString
: label pointer, is apple in 64 – bit environment pairNSString
,NSNumber
And other objects to do optimization. forNSString
objects- If the string is a combination of digits, letters, and the length is less than or equal to 9, it automatically becomes
NSTaggedPointerString
Type stored in the constant area - When there are Chinese or other special symbols, it will be directly
__NSCFString
Type stored in the heap area
- If the string is a combination of digits, letters, and the length is less than or equal to 9, it automatically becomes
- for
NSString
Say, when a stringsmaller
, you are advised to directly pass@ ""
Initialize as stored inThe constant area
, can be read directly. thanWithFormat Initialization mode
More quickly
If we look at the source code TaggedPointer and we see that there is a position operation for playLoad, it means that there is encryption and decryption going on.
// payload = (decoded_obj << payload_lshift) >> payload_rshift
// Payload signedness is determined by the signedness of the right-shift.
Copy the code
Then search decoded: encryption.
static inline uintptr_t
_objc_decodeTaggedPointer_noPermute(const void * _Nullable ptr)
{
uintptr_t value = (uintptr_t)ptr;
#if OBJC_SPLIT_TAGGED_POINTERS
if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
return value;
#endif
return value ^ objc_debug_taggedpointer_obfuscator;// xor processing
}
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
value |= _objc_obfuscatedTagToBasicTag(basicTag) << _OBJC_TAG_INDEX_SHIFT;
#endif
return value;
}
Copy the code
The decoder gets the value and the objC_debug_taggedPOinter_obfuscator for xor processing. We move on to the initialization of the obfusker as we did in readImags
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;// Put random data into variables and remove all non-payload factors.
#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
Here, if obfuscation is turned on, you get a random objC_debug_TaggedPOinter_obfuscator value, otherwise 0. So let’s go ahead and look at encode and do an xor
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
Through the implementation, we can know that in the encoding and decoding part, there are two layers of xOR, its purpose is to get the small object itself, for example, take 1010 0001 as an example, assume that the mask is 0101 1000
1010 0001 ^0101 1000 mask (encoding) 1111 1001 ^0101 1000 mask (decoding) 1010 0001Copy the code
What we printed before is the decoded value of the marker pointer
And the previous move left, move right, is in_objc_makeTaggedPointer
The inside. You can see here that we move first, we encrypt.
So we’re going to move 3 bits to the right, get rid of the tag, the first bit is empty so no value is stored, starting from the second bit we’re going to store a byte every 8 bits for a letter, little endian mode from left to right
contrastASCII
Table:test
TaggedPointer is a special pointer, so let’s see64-bit 1 indicates that
TaggedPointer, where63 ~ 61
saidtype
. The NSString type in the figure above is1010
63-61 of them are2
.
{
// 60-bit payloads
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,
// 60-bit reserved
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,
OBJC_TAG_NSMethodSignature = 20,
OBJC_TAG_UTTypeRecord = 21,
// When using the split tagged pointer representation
// (OBJC_SPLIT_TAGGED_POINTERS), this is the first tag where
// the tag and payload are unobfuscated. All tags from here to
// OBJC_TAG_Last52BitPayload are unobfuscated. The shared cache
// builder is able to construct these as long as the low bit is
// not set (i.e. even-numbered tags).
OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit set
OBJC_TAG_Constant_CFString = 136,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
Copy the code
3.1.2 TaggedPinter under real machine
The principle is similar to the simulator, but the storage location is a little different
- The first
1 a
Is the tag taggedPointer - The type is storage
Low three
Char 0, short 1, int 2,3 long, 4 float, 5 double - Go live
four
, which stores dataThe length of the
- And then there’s the data
content
3.1.3 interview questions
//MARK: -taggedpointer
- (void)taggedPointerDemo {
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(queue, ^{
self.nickName = [NSString stringWithFormat:@"test"];
NSLog(@"% @",self.nickName); }); }}//MARK: -taggedpointer
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"Come");
// Multithreaded reading and writing
// setter -> retian release
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<100000; i++) {
dispatch_async(queue, ^{
self.nickName = [NSString stringWithFormat:@"Super ocean view hahaha hahaha."];
NSLog(@"% @",self.nickName); }); }}Copy the code
What happens to practice questions 1 and 2, interview 1 doesn’t crash, interview 2 crashes. As we have learned before, when we read and write a value simultaneously in multi-threaded asynchronous queue, it will crash. When we assign a value, we will retain the new value and release the old value. Because it is asynchronous, two releases will occur at the same time, and when we read again, an error will be reported due to wild pointer.
- Let’s see why interview question 1 didn’t crash.
This shows that the nickName isNSTaggedPointerString
For taggedPointer, its value is stored directly in a pointer. There is no memory to pointer to, so there is no operation on the memory to pointer to, so there is no crash. Look at the source code:
- In Demo2, let’s look at the type of NSString
A type ofNSCFString
, stored in heap area is nottaggedPointer
Type thus occurs as we analyzed above.
3.2 NONPOINTER_ISA
We learned this when we were studying ISA. Here’s a brief explanation:
-
Nonpointer: indicates whether to enable pointer optimization for the ISA pointer. 0: indicates the pure ISA pointer. 1: Indicates that the ISA contains not only the address of the class object but also the class information and reference count of the object
-
Has_assoc: flag bit of the associated object. 0 does not exist and 1 exists
-
Has_cxx_dtor: does the object have a destructor for C++ or Objc? If it has a destructor, the destructor logic needs to be done. If not, the object can be freed faster
-
Shiftcls: Stores the value of the class pointer (payload equivalent to the taggedPointer). With pointer optimization turned on, 33 bits are used to store class Pointers in the ARM64 architecture.
-
Magic: Used by the debugger to determine whether the current object is a real object or has no space to initialize
-
Weakly_referenced: A weak variable that records whether an object is pointed to or used to point to an ARC. Objects without weak references can be released faster.
-
Deallocating: Indicates whether the object is freeing memory
-
Has_sidetable_rc: When the object reference technique is greater than 10, this variable is borrowed to store carry
-
Extra_rc: When representing the reference count of this object, the reference count is actually subtracted by 1. For example, if the object’s reference count is 10, the extra_rc is 9. If the reference count is greater than 10, has_sideTABLE_rc needs to be used.
3.3 Hash Table SideTable
When the object reference technique is greater than 10, we need to borrow the variable to store the carry. Let’s continue exploring the underlying implementation of the reference count RETAIN
3.1 retain
We analyzed the process of retain through source code
- objc_retain
2. retain()
3. rootRetain
Let’s focus on rootRetain
- Check whether or not
taggedPointer
If yes, return directly. - Determine whether or not
nonpointer
If it’s not a class, it’s not an object, no reference counting - Do a dowhile loop
- If it is not nonpointer, it is
Pure isa
Directly toHash table
operation - is
The release of
If it is destructing, there is no need to operate -1. In the case of multithreading, it is already being released, and it may be -1 - Bits that does ++ and gives a reference count
Status identification CARRY
Used to indicateextra_rc
Whether the full - if
carray
State representation ofExtra_rc's reference count is full
Now you need to operateHash table
, that is, take out half of the full state and save toextra_rc
And the other half have hash tablesrc_half
. The reason for doing this is that if all stores are stored in the hash table, every operation on the hash table needs to be unlocked, which is time-consuming and consumes high performanceEqually divided
The purpose of the operation is toTo improve performance
- If it is not nonpointer, it is
3.2 relseas
Release process is similar to retain, setProperty -> reallySetProperty -> objc_release -> release -> rootRelease -> rootRelease order, Enter rootRelease source code
The process is the reverse of retain
- Check whether or not
taggedPointer
If yes, return directly. - Determine whether or not
nonpointer
If it is not a class, it is not an object and does not count references - Do a dowhile loop
The nonpointer
If the hash table is handled directly, the reference count is -1isDeallocating
: Is destructing, do not process, to execute the dealloc processextra_rc--
Perform the reference counting -1 operation, that isextra_rc-1
, if at this timeextra_rc
The value of0
, then walk tounderflow
.
- underflow
has_sidetable_rc
: Determines whether half the reference count is stored in the hash table, if notAutomatic destruction
.sidetable_subExtraRC_nolock
: takes half the reference count from the hash table and proceeds1 the operation
And store it toextra_rc
In the
3.3 dealloc
Check dealloc -> _objc_rootDealloc -> rootDealloc source code implementation
- According to the condition
Check whether there is ISA, CXX, associated object, weak reference table, reference count table
, if not, thenDirect free releases memory
- If yes, enter
object_dispose
methods
- Continue to see
objc_destructInstance
- To view
clearDeallocating
4. To summarize
- We know about memory management by Apple through the
ARC
.MRC
.taggedPointer
, reference-countedretain
和release
Perform in-pipe memory to release objects. In an ARC environment, we do not need to manually manage reference counts for objects. - For small objects like
NSString
.NSNumber
.NSdate
Pointer optimization, store values in Pointers and distinguish them by specific markersTaggedPointer
As well as itstype
. - Object isa based on whether or not
Pure isa
The corresponding processing, pure ISA referenceThe count is stored in a hash table
The non-pure ISAs exist in ISAsextra_rc
In the. inretain
In, the storage is fullTake half and store it in the hash table
,extra_rc++
; - in
release
,extra_rc--
after0
Check whether the hash table is emptydelloc
Process, fetching hash table out if not emptyHalf the reference count
.- 1
After the operation is stored toextra_rc
In the. - Delloc is primarily for objects
Reference count list
.associations
List,A weak reference table
Destroy to free memory.