iOS Memory Management

Introduction Recently I revisited C language memory mechanisms and deepened my understanding of the four-segment memory model. Looking back at iOS memory management with that foundation, I found many parallels. To truly understand iOS memory management, you first need to understand C's memory model. C Language Memory The Four-Segment Memory Model Before a program is executed, the following steps take place: 1. The operating system loads the physical disk code into memory. 2. The operating system divides the C code into four regions. 3. The operating system finds the function entry point and begins execution. The Four Memory Segments - Stack: Automatically allocated and released by the compiler. Stores function parameters and local variable values. Operates like a stack data structure — Last In, First Out (LIFO). - Heap: Generally allocated and released by the programmer. If not released by the programmer, the OS may reclaim the memory when the program ends. Note that this is different from the heap data structure; allocation behaves more like a linked list — First In, First Out (FIFO). - Data Segment: Mainly includes the global/static area and the constant area. At the assembly level, this can be further subdivided into many smaller sections. Global/Static Area (static): Global variables and static variables are stored together. Initialized global and static variables occupy one region, and uninitialized global and static variables occupy an adjacent region. Both are released by the system when the program ends. Constant Area: Constant strings are stored here. Released by the system when the program ends. - Code Segment: Stores the binary code for function bodies. Growth Direction of the Stack and Heap - The stack grows downward: items pushed onto the stack earlier have higher memory addresses. - The heap grows upward: items allocated on the heap earlier have lower memory addresses. Note: For any allocated block of memory pointed to by a pointer (whether on the heap or the stack), the starting address is always the lowest address within that block. Simplified Stack/Heap Model iOS Memory Segments iOS memory segments are similar to those in C: - Stack: Stores local variables; follows last-in-first-out order; destroyed as soon as they go out of scope. Also stores function return addresses and context-saving data. Developers do not need to manage stack memory. Stack addresses are allocated from high to low. - Heap: Memory on the heap is allocated using . Developers are responsible for managing heap memory. Under ARC, the compiler automatically inserts , , and calls during compilation. Heap addresses are allocated from low to high. - Global/Static Area (static): Contains two sub-regions: uninitialized and initialized. In other words, initialized global/static variables and uninitialized global/static variables are stored in adjacent but separate areas. Example: is uninitialized; is initialized. - Constant Area: Constant strings are stored here. - Code Segment: Stores the app's binary code. Additional notes: In iOS, heap memory is shared among the application's components; the system is responsible for heap memory allocation. The system uses a linked list to track all allocated memory spaces (the system only records allocations; it does not manage their contents). After a variable is done being used, its memory must be released. In Objective-C, this is based on the reference count: when the count reaches 0, no variable is using that memory, and the system reclaims it immediately. After an app launches, the code segment, constant area, and global area are fixed in size, so pointers to these regions will not cause crash errors. However, the heap and stack change constantly (heap objects are created and destroyed; stack frames are pushed and popped), so when using a pointer into either of these areas, always make sure the memory has not already been freed — otherwise a crash will occur (i.e., a dangling pointer error). The OS uses pointers stored on the stack to access objects on the heap. If the stack pointer is gone, the heap object becomes inaccessible, which is the root cause of memory leaks. iOS Memory Management In iOS development, objects in memory fall into two main categories: value types, such as , , , and other primitive types; and reference types, which are all Objective-C objects inheriting from . Value types are placed on the stack, laid out contiguously, occupying a continuous block of memory, and follow the last-in-first-out principle. Reference types are placed on the heap. When memory is allocated for an object, a random available region of memory is used, so gaps of indeterminate size may exist between objects, leading to memory fragmentation that we must manage. In terms of performance, stack memory outperforms heap memory because the stack follows last-in-first-out order. When the amount of data is very large, pushing it onto the stack can noticeably degrade performance. Therefore, large amounts of data are stored on the heap, with the stack holding only the heap's address. When data is needed, it can be quickly located via the address stored on the stack. ARC Objective-C provides two memory management approaches: and . Here we focus on ARC. ARC was introduced in iOS 5 and enables automatic memory management. Under ARC, as long as no strong pointer (strong reference) points to an object, the object will be released. Under ARC, you are not allowed to use , , or . Additionally, if you implement , you must not call . ARC property modifiers are and , equivalent to and in MRC respectively. replaces and is the default modifier, representing a strong reference. <font color=redreplaces and declares a weak reference that is automatically set to — but it goes one step further than : when the object the pointer points to is deallocated, the pointer itself is also automatically set to .</font Memory-Related Modifiers - strong: Strong reference, used in ARC; similar to in MRC; increments the retain count by 1. - weak: Weak reference, used in ARC; if the referenced object is deallocated, the pointer is set to , effectively preventing dangling pointers; the retain count is 1. - readwrite: Read-write attribute; requires both a getter and a setter to be generated. - readonly: Read-only attribute; only a getter is generated, no setter; use when you don't want the property changed from outside the class. - assign: Assignment attribute; does not involve reference counting; a weak reference; the setter simply assigns the parameter to the instance variable. - retain: Ownership attribute; the setter retains the incoming parameter before assigning it, incrementing the retain count by 1. - copy: Copy attribute; the setter makes a full copy of the incoming object; use when you need a completely independent copy. - nonatomic: Non-atomic operation; no synchronization; improves performance in a multithreaded context but is not thread-safe. Determines whether the compiler-generated getter/setter uses atomic operations. - atomic: Atomic operation; synchronized; thread-safe; the opposite of . Mixing MRC and ARC In an ARC project, you can add the compiler flag to MRC files. In an MRC project, you can add the compiler flag to ARC files. Memory Leaks Under ARC Even with ARC, memory leaks are still possible. Block Consider the following scenario: In a network utility class , there is a completion block for network requests: A class uses the network utility to send requests and handle callbacks: A retain cycle is clearly forming here: holds ; holds the block; the block holds . These three form a retain cycle, resulting in a memory leak. The following example also causes a memory leak: holds the block, and the block holds , forming an isolated memory island that can never be released. There are two solutions: - Set the object to to break the reference and the cycle. This approach is error-prone since it's easy to forget to nil out a property. - Convert the strong reference to a weak reference to break the cycle. The pattern is mainly for compatibility with older versions of LLVM. You can also use RAC's more elegant and . Also note that Cocoa Framework methods whose names include and GCD APIs internally copy the block passed to them — be aware of retain cycles in those cases as well. Block memory allocation is covered in depth in iOS Block Memory Allocation. executes a selector at runtime. Consider the following code: This code layers dynamic dispatch on top of dynamic binding. Because the selector is not known at compile time, the compiler does not know the method signature, the return type, or even whether there is a return value. Therefore, ARC cannot apply its memory management rules to determine whether the return value should be released. As a result, ARC takes a conservative approach and does not add a release call — meaning that when the method returns an object, ARC may retain it and never release it, causing a memory leak. In this example, the first two cases ( and ) both return objects that need to be released, while the third does not. This kind of leak is so well-hidden that even the static analyzer may fail to detect it. If you change the last line to: and skip creating a return-value variable in testing, it is almost inconceivable that a memory issue lurks here. So if the you use has a return value, you must handle it properly. NSNotificationCenter This is fairly common knowledge: just remember to call , or use the corresponding RAC method. NSTimer When using , to prevent a crash caused by the target being deallocated, the timer retains its target — making this another potential source of memory leaks. Exception Handling Colleagues from Java backgrounds often wonder why iOS development doesn't use . Apple provides two mechanisms: error handling () and exception handling (), where is used to catch exceptions. is the right tool for the vast majority of scenarios and is what Apple recommends. When should you use ? Only in extremely serious situations that directly cause a program crash, and without any need for recovery. under MRC: If throws an exception, will never be released. If holds important or scarce resources, this can have serious consequences. Note that most crashes in Objective-C — such as out-of-memory errors and dangling pointer accesses — cannot be caught. Only errors like array out-of-bounds exceptions can be caught (and shouldn't a developer with good code hygiene be checking bounds beforehand anyway?). References: C Language: The Four-Segment Memory Model and Function Call Model Memory Leak Pitfalls Under ARC