Memory layout
1. The kernel area
- The memory area occupied by the kernel.
2. Stack area
- Store function parameter value, local variable value, object pointer address;
- The compiler automatically allocates the release;
- Fast and efficient, but the operation mode is not flexible (similar to data structure stack FIFO);
- Stack address allocation mode: from high to low.
Optimization techniques
- Instead of using a lot of local variables (such as loop creation) in a function, you can optimize it by using the AutoReleasePool automatic release pool.
- Note: Local variables are collected and released using GC (garbage collection), which can cost performance. Try to encapsulate the method, use method nesting, speed up the function read efficiency, with space for time.
Stack address allocation from high to low, heap address allocation from low to high, once met, will cause stack overflow.
3. The heap
- Objects created in iOS are stored in the heap (alloc);
- Developer management (if the developer does not recycle, the system will recycle at the end of the program);
- Relatively slow speed, flexible operation mode (similar to linked list), will cause memory fragmentation;
- Heap address allocation mode: From lowest to highest.
Optimization techniques
- Object creation and collection can consume a lot of memory and performance to avoid creating too many objects, beyond which stack overflow can occur.
- There is a subtle difference between methods and functions, so use functions as often as possible.
4. Global (static) zone (.bbs/data)
- Store global or static variables (static modifier);
- Release by the system after the end of the program;
static int a;
Uninitialized global static variables are stored in the. BBS section of the global section.static int a = 10;
Initialized global static variables are stored in the data section of the global extents.
Optimization techniques
- Note the scope of static variables (.h and.m have different scopes, so avoid defining global variables in header file.h).
- Avoid reassigning static variables that have already been defined.
5. Constant area
- Store used string constants (e.g., const, extern string);
- Released by the system after the program ends;
- The same string address is consistent (C language compiler optimization).
6. Code area (.text)
- An area where compiled code is stored;
- Includes an opcode and a reference to the object or address of the object to operate on.
7. Retention period
- A reserved area of memory.
The data structure
1. TaggedPointer
- Tagged Pointer is used to store small objects, such as NSNumber and NSDate.
- The Tagged Pointer value 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.
- When Pointers are insufficient to store data, dynamically allocated memory is used to store data.
2. NONPOINTER_ISA
- Indexed (0) : whether the marker ISA pure ISA pointer or a non-pointer NOPOINTER_ISA pointer (0: a pure ISA pointer; 1: NOPOINTER_ISA pointer);
- Has_assoc (1) : flags whether there is an associated object (0 does not, 1 exists);
- Has_cxx_dtor (2) : whether the object has a destructor of C++ or Objc. If so, the destructor logic needs to be done. If not, the object can be released faster.
- Shiftcls (3~35) : the address of the pointer to the class object that marks the current object.
- Magic (36~41) : used by the debugger to determine whether the current object is a real object or has no initialized space;
- Weakly_referenced (42) : Marks whether an object has a weak reference pointer, and objects without weak references can be released faster;
- Deallocating (43) : Marks whether the object is doing a dealloc operation;
- Has_sidetable_rc (44) : flags whether there is a sitetable structure for storing reference counts;
- Extra_rc (45~63) : The reference count of a token object (stored first in this field and then in the reference count table when it reaches the upper limit).
3. SideTables
SideTables is a 64-element hash array that stores the SideTable. The hash key of SideTables is the address of an object obj. So we can say that an OBj corresponds to a SideTable. But one SideTable is going to have multiple OBJs. Since there are only 64 sidetables, many OBJs share the same SideTable.
SideTable
spinlock_t slock
: spin lock used to lock/unlock SideTable;RefcountMap refcnts
: Hash table with DisguisedPtr< OBJC_object > as key, used to store the REFERENCE count of the OC object (used only when THE ISA optimization is not enabled or when the REFERENCE count of ISA_T overruns in the ISA optimization case);weak_table_t weak_table
: Hash table that stores weak reference Pointers to objects. Is the core data structure of the weak function in OC.
The value stored in the RefcountMap reference count table is size_t, which is actually an unsigned long integer. Weakly_referenced and deallocating are the first two bits, indicating whether there is a weak reference pointer and whether dealloc is being performed. All that remains is the referential count of the object, so to calculate the specific referential count of an object, offset it by two places to the right.
Weak_table_t Key in weak_table_t weak reference table is the object pointer, Value is weak_entry_t, weak_entry_t is a structure array, the element in the array is actually the weak reference information of OC object.
What is spinlock? It is a locking mechanism to protect shared resources. In fact, spin locks are similar to mutex in that they are used to solve the mutual exclusion of a resource. Whether it is a mutex or a spin lock, there can be at most one holder at any one time, which means that at most one execution unit can acquire the lock at any one time. But the scheduling mechanism is slightly different. For mutex, if the resource is already occupied, the resource applicant can only go to sleep. But a spinlock does not cause the caller to sleep. If the spinlock has been held by another execution unit, the caller keeps looping to see if the holder of the spinlock has released the lock, hence the name “spin”.
Hash lookup
- A Hash table is a data structure that accesses data stored in memory based on a Key. That is, it accesses records by computing a function on key values that maps the data for the desired query to a location in the table, which speeds up lookups. This mapping function is called a hash function, and the array of records is called a hash table.
- The rules of the hash function are as follows: through some transformation relationship, the key words are moderately dispersed to the order structure of the specified size. The more dispersed, the smaller the time complexity and the higher the space complexity of the later search.
- Hash is a typical space-for-time algorithm. For example, to search for an array with a length of 100, you only need to traverse and match corresponding records. From the perspective of space complexity, if the array stores byte data, the array occupies 100 bytes of space. Now we’re using the Hash algorithm, and the Hash we said before must have a rule that governs the relationship between keys and storage positions, so we need a fixed length Hash table, which is still an array of 100 bytes. Suppose we need 100 bytes to record the relationship between keys and storage positions, so the total space is 200 bytes. And the size of the table used to record the rule may vary depending on the rule.
Reference counting
1. ARC
Use ARC in Objective-C to let the compiler manage memory. Setting ARC to a valid state in the new Generation of Apple LLVM compilers eliminates the need to type retain or release code again, greatly reducing the risk of crashes, memory leaks, and so on. The compiler is fully aware of target objects and can immediately release objects that are no longer in use. As a result, applications will be predictable, run smoothly, and run much faster.
- ARC is the result of a compiler and Runtime collaboration.
- Manual invocation is prohibited in ARC
retain
/release
/retainCount
/dealloc
. - Added in ARC
weak
,strong
Property keyword.
For example, in a non-pointer ISAnonpointer
,weakly_referenced
,has_sidetable_rc
andextra_rc
Are directly related to ARC.
2. alloc
- After a series of calls, the C function calloc is finally called.
- The reference count is not set to 1 at this point.
After alloc, retainCount gets a reference count of 1.
3. retain
SideTable& table = SideTables()[this];
Retrieve the SideTable of the current object from SideTables using a pointer to the current object.size_t& refcntStorage = table.refcnts[this];
Retrieves the reference count of the current object from the reference count table of the SideTable, using a pointer to that object.refcntStorage += SIDE_TABLE_RC_ONE;
Because the first two bits of size_t store special values, SIDE_TABLE_RC_ONE takes into account a two-bit offset and actually adds 4 to the reference count.
4. release
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
Access the reference count table in the table based on the current object pointer.it->second -= SIDE_TABLE_RC_ONE;
Subtract SIDE_TABLE_RC_ONE from the corresponding value.
5. retainCount
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
Declare a local variable with a value of 1.RefcountMap::iterator it = table.refcnts.find(this);
By the current object, look it up in the reference count table.refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
The result of the search is offset to the right, added to the previously declared local variable, and returned to the caller.
The new alloc object does not actually have an associated key/value mapping in the reference count table, but when retainCount is fetched, it gets a value of 1.
6. dealloc
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
Copy the code
_objc_rootDealloc implementation:
void _objc_rootDealloc(id obj) { ASSERT(obj); // The assertion validation object obj->rootDealloc(); }Copy the code
RootDealloc () :
inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary? If (fastPath (isa.nonpointer &&) // Is isa optimized! Isa.weakly_referenced && // Whether there is weak reference pointing! Isa.has_assoc && // Whether the associated object is set #if ISA_HAS_CXX_DTOR_BIT! Isa.has_cxx_dtor && // is there a C++ destructor #else! isa.getClass(false)->hasCxxDtor() && #endif ! Isa.has_sidetable_rc)) // Whether the reference count is too large to be stored in ISA {assert(! sidetable_present()); // Free (this); } else { object_dispose((id)this); Object_dispose ()}}Copy the code
Object_dispose () :
id object_dispose(id obj) { if (! obj) return nil; objc_destructInstance(obj); // Destroy instance free(obj); Return nil; }Copy the code
Objc_destructInstance () :
void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); Bool assoc = obj->hasAssociatedObjects(); If (CXX) object_cxxDestruct(obj); // This order is important. if (CXX) object_cxxDestruct(obj); If (assoc) _object_remove_assocations(obj, /*deallocating*/true); // Remove the current object's associated object obj->clearDeallocating(); } return obj; }Copy the code
ClearDeallocating () calls the process
inline void objc_object::clearDeallocating() { if (slowpath(! isa.nonpointer)) { // Slow path for raw pointer isa. sidetable_clearDeallocating(); } else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) { // Slow path for non-pointer isa with weak refs and/or side table data. clearDeallocating_slow(); } assert(! sidetable_present()); }Copy the code
ClearDeallocating_slow () :
NEVER_INLINE void objc_object::clearDeallocating_slow() { ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); SideTable& table = SideTables()[this]; table.lock(); if (isa.weakly_referenced) { weak_clear_no_lock(&table.weak_table, (id)this); // Set the weak reference pointer to the object to nil. } if (isa.has_sidetable_rc) { table.refcnts.erase(this); // Erase the object reference count from the reference count table} table.unlock(); }Copy the code
A weak reference
1. Weak implementation principle
An object pointer declared __weak, compiled by the compiler, calls the objc_initWeak() method, and then passes through a series of function call stacks, eventually adding weak-referenced variables in weak_register_no_lock(). The exact location of the addition is calculated using a hash algorithm:
- If the location you are looking for already has an array of weak references for the current object
weak_entry_t
, we add the new weak reference variable to the array; - If none is found, a new array of weak references is created, and the pointer is added to bit 0, and the rest is initialized to nil.
2. How do I set the weak pointer to nil when the weak pointer is released?
When an object is released, the internal implementation of Dealloc will determine whether there is a weak reference to the object. If there is, weak_clear_no_lock(), the related function of weak reference clearance, will be called eventually. In this function, weak_clear_no_lock() will look up the weak reference table according to the current object pointer. It then consolidates all weak reference Pointers to the current object to nil.
Automatic release tank
1. Data structure
- It is combined in the form of bidirectional linked list through stack nodes.
- It’s one to one for threads.
// class AutoreleasePoolPage;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
Copy the code
AutoreleasePoolPage::pop()
- Find the corresponding position based on the incoming sentry object.
- Send release messages to the objects added after the last push operation.
- Roll back the next pointer to the correct position.
2. When will the automatic release pool be released?
Call AutoreleasePoolPage::pop() when the secondary runloop is about to end. A new AutoreleasePool will also be pushed.
3. Why can auto-release pools be nested?
Multiple levels of nesting is the insertion of sentinels multiple times.
4. Application scenario of automatic release pool.
Manually insert the autoreleasePool in a for loop for memory consuming scenarios such as alloc image data.
A circular reference
1. When should I pay attention to circular references?
- The agent
- Block
- NSTimer
- Macrocyclic reference
2. How to break circular references?
- Avoid circular references
- Manually break the loop when appropriate
3. What are the specific solutions?
- __weak
- __block
- __unsafe_unretained
__block
- Under MRC, __block modifier objects do not increase their reference count, avoiding circular references.
- In ARC, __block modifier objects are strongly referenced, so circular references cannot be avoided and need to be removed manually.
__unsafe_unretained
- Modifier objects do not increase their reference count, avoiding circular references.
- If the decorated object is released at some point, dangling Pointers will result in a memory leak.
4. How to solve the circular reference caused by NSTimer?
Because NSTimer is strongly referenced by the current thread’s Runloop after being dispatched, there is no way to solve the circular reference problem by weakly referencing NSTimer objects.
Create an intermediate object that holds two weak reference variables, other than the original object and NSTimer. Implement NSTimer callback in intermediate objects and add a judgment before callback to check whether the original object held by the intermediate object is nil; The callback is executed if the original object exists, and NSTimer is set to invalid if the original object has been released.
// NSTimer+WeakTimer.h #import <Foundation/Foundation.h> @interface NSTimer (WeakTimer) + (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; @endCopy the code
// NSTimer+WeakTimer.m
#import "NSTimer+WeakTimer.h"
@interface TimerWeakObject : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer;
- (void)fire:(NSTimer *)timer;
@end
@implementation TimerWeakObject
- (void)fire:(NSTimer *)timer {
if (self.target) {
if ([self.target respondsToSelector:self.selector]) {
[self.target performSelector:self.selector withObject:timer.userInfo];
}
} else {
[self.timer invalidate];
}
}
@end
@implementation NSTimer (WeakTimer)
+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)ti
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)yesOrNo {
TimerWeakObject *object = [[TimerWeakObject alloc] init];
object.target = aTarget;
object.selector = aSelector;
object.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:object selector:@selector(fire:) userInfo:userInfo repeats:yesOrNo];
return object.timer;
}
@end
Copy the code