iOS Advanced - iOS Memory Management

iOS Advanced - iOS Memory Management

1 It seems that everyone has considered the problem when learning iOS

  • What does alloc retain release delloc do?
  • How is autoreleasepool implemented?
  • What is __unsafe_unretained?
  • How Block is implemented
  • When does a retain cycle occur and when does it not?

So in this blog post I will explain in detail from ARC to iOS memory management, as well as Block-related principles and source codes.

2. Let’s start with ARC

When talking about iOS memory management, we have to start with ARC (Automatic Reference Counting), which was introduced by WWDC2011 and iOS5. ARC is a feature of the LLVM 3.0 compiler that is used to automatically manage memory.

Unlike GC in Java, ARC is a compiler feature rather than a runtime feature, so ARC actually automatically helps developers insert memory management code during the compilation phase rather than real-time monitoring and memory recovery.

ARC's memory management rules can be summarized as follows:

  • Each object has a reference count.
  • The object is held, and the reference count is +1.
  • The object is abandoned, and the reference count is -1.
  • "Reference count" = 0, release the object

3 What you need to know

  • The Foundation framework, which contains the NSObject class, is not publicly available.
  • The Core Foundation framework source code and some of the source code for memory management through NSObject are public.
  • GNUstep is an interchange framework for the Foundation framework

GNUstep is also one of the GNU projects. It reimplements the Cocoa Objective-C software library as free software.

In a sense, the implementation of GNUstep and Foundation frameworks is similar.

Analyzing Foundation's memory management through GNUstep source code

4 Implementation of alloc retain release dealloc

4.1 GNU – alloc

See the alloc function in GNUStep.

GNUstep/modules/core/base/Source/NSObject.m alloc:

  1. + (id) alloc
  2.  
  3. {
  4.  
  5. return [self allocWithZone: NSDefaultMallocZone()];
  6.  
  7. }  
  8.   
  9.  
  10. + (id) allocWithZone: (NSZone*)z
  11.  
  12. {
  13.  
  14. return NSAllocateObject (self, 0, z);
  15.  
  16. }

GNUstep/modules/core/base/Source/NSObject.m NSAllocateObject:

  1. struct obj_layout {
  2.  
  3. NSUInteger retained;
  4.  
  5. };
  6.  
  7.   
  8.  
  9. NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)
  10.  
  11. {
  12.  
  13. int   size = calculate the memory size required to accommodate the object;
  14.  
  15. id new = NSZoneCalloc(zone, 1, size );
  16.  
  17. memset (new, 0, size );
  18.  
  19. new = (id)&((obj)new)[1];
  20.  
  21. }

The NSAllocateObject function allocates the space required to store the object by calling the NSZoneCalloc function, then sets the memory space to nil, and finally returns a pointer to be used as the object.

We simplify the above code:

GNUstep/modules/core/base/Source/NSObject.m alloc simplified version:

  1. struct obj_layout {
  2.  
  3. NSUInteger retained;
  4.  
  5. };
  6.  
  7.   
  8.  
  9. + (id) alloc
  10.  
  11. {
  12.  
  13. int   size = sizeof(struct obj_layout) + object size;
  14.  
  15. struct obj_layout *p = (struct obj_layout *)calloc(1, size );
  16.  
  17. return (id)(p+1)
  18.  
  19. return [self allocWithZone: NSDefaultMallocZone()];
  20.  
  21. }

The alloc class method uses the retained integer in struct obj_layout to save the reference count and writes it to the memory header of the object. It then returns after all memory blocks of the object are set to 0.

The representation of an object is as follows:

4.2 GNU – retain

GNUstep/modules/core/base/Source/NSObject.m retainCount:

  1. - (NSUInteger) retainCount
  2.  
  3. {
  4.  
  5. return NSExtraRefCount(self) + 1;
  6.  
  7. }
  8.   
  9.  
  10. inline NSUInteger
  11.  
  12. NSExtraRefCount(id anObject)
  13.  
  14. {
  15.  
  16. return ((obj_layout)anObject)[-1].retained;
  17.  
  18. }

GNUstep/modules/core/base/Source/NSObject.m retain:

  1. - (id) retain
  2.  
  3. {
  4.  
  5. NSIncrementExtraRefCount(self);
  6.  
  7. return self;
  8.  
  9. }
  10.  
  11.   
  12.  
  13. inline void
  14.  
  15. NSIncrementExtraRefCount(id anObject)
  16.  
  17. {
  18.  
  19. if (((obj)anObject)[-1].retained == UINT_MAX - 1)
  20.  
  21. [NSException raise: NSInternalInconsistencyException
  22.  
  23. format: @"NSIncrementExtraRefCount() asked to increment too far"];
  24.  
  25. ((obj_layout)anObject)[-1].retained++;
  26.  
  27. }

In the above code, the NSIncrementExtraRefCount method first writes the code that causes an exception when the retained variable exceeds the maximum value (because retained is an NSUInteger variable), and then performs the retain++ code.

4.3 GNU – release

Corresponding to retain, the release method does retain --.

GNUstep/modules/core/base/Source/NSObject.m release

  1. - (oneway void) release
  2.  
  3. {
  4.  
  5. if (NSDecrementExtraRefCountWasZero(self))
  6.  
  7. {
  8.  
  9. [self dealloc];
  10.  
  11. }
  12.  
  13. }
  14.  
  15.   
  16.  
  17. BOOL
  18.  
  19. NSDecrementExtraRefCountWasZero(id anObject)
  20.  
  21. {
  22.  
  23. if (((obj)anObject)[-1].retained == 0)
  24.  
  25. {
  26.  
  27. return YES;
  28.  
  29. }
  30.  
  31. ((obj)anObject)[-1].retained --;  
  32.  
  33. return   NO ;
  34.  
  35. }

4.4 GNU – dealloc

dealloc will release the object.

GNUstep/modules/core/base/Source/NSObject.m dealloc:

  1. - (void) dealloc
  2.  
  3. {
  4.  
  5. NSDeallocateObject (self);
  6.  
  7. }
  8.   
  9.  
  10. inline void
  11.  
  12. NSDeallocateObject(id anObject)
  13.  
  14. {
  15.  
  16. obj_layout o = &((obj_layout)anObject)[-1];
  17.  
  18. free (o);
  19.  
  20. }

4. ***pple implementation

In Xcode, set Debug -> Debug Workflow -> Always Show Disassenbly to on. This way, you can see more detailed method calls after the breakpoint.

By setting breakpoints on alloc and other methods of the NSObject class, you can see that several methods are called internally:

retainCount

  1. __CFdoExternRefOperation
  2. CFBasicHashGetCountOfKey

retain

  1. __CFdoExternRefOperation
  2. CFBasicHashAddValue

release

  1. __CFdoExternRefOperation
  2. CFBasicHashRemoveValue

You can see that they all call a common __CFdoExternRefOperation method.

As you can see from the prefix, this method is included in Core Foundation and can be found in CFRuntime.c. The source code is simplified and listed as follows:

CFRuntime.c __CFDoExternRefOperation:

  1. int __CFDoExternRefOperation(uintptr_t op, id obj) {
  2.  
  3. CFBasicHashRef table = get the hash table of the object (obj);
  4.  
  5. int   count ;
  6.  
  7.   
  8.  
  9. switch (op) {
  10.  
  11. case OPERATION_retainCount:
  12.  
  13. count = CFBasicHashGetCountOfKey( table , obj);
  14.  
  15. return   count ;
  16.  
  17. break;
  18.  
  19. case OPERATION_retain:
  20.  
  21. count = CFBasicHashAddValue( table , obj);
  22.  
  23. return obj;
  24.  
  25. case OPERATION_release:
  26.  
  27. count = CFBasicHashRemoveValue( table , obj);
  28.  
  29. return 0 == count ;
  30.  
  31. }
  32.  
  33. }

Therefore, __CFDoExternRefOperation performs specific method calls for different operations. If op is OPERATION_retain, the method that specifically implements retain is removed.

From the method name BasicHash, we can see that the reference count table is actually a hash table.

The key is the hash (the address of the object) and the value is the reference count.

The following figure compares Apple and GNU's implementation:

5 autorelease and autorelaesepool

Apple's documentation for NSAutoreleasePool states:

Each thread (including the main thread) maintains a stack of managed NSAutoreleasePools. When a new Pool is created, they are added to the top of the stack. When the Pool is destroyed, they are removed from the stack.

The autorelease object will be added to the Pool at the top of the current thread's stack. When the Pool is destroyed, the objects in it will also be released.

When the thread ends, all Pools are destroyed and released.

Set breakpoints on the NSAutoreleasePool class method and the autorelease method, and view its running process. You can see that the following functions are called:

  1. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  2.  
  3. // Equivalent to objc_autoreleasePoolPush
  4.  
  5.   
  6.  
  7. id obj = [[NSObject alloc] init];
  8.  
  9. [obj autorelease];
  10.  
  11. // Equivalent to objc_autorelease(obj)
  12.  
  13.   
  14.  
  15. [NSAutoreleasePool showPools];
  16.  
  17. // Check NSAutoreleasePool status
  18.  
  19.   
  20.  
  21. [pool drain];
  22.  
  23. // Equivalent to objc_autoreleasePoolPop(pool)

[NSAutoreleasePool showPools] can show the status of all pools of the current thread:

  1. objc[21536]: ##############
  2.  
  3. objc[21536]: AUTORELEASE POOLS for thread 0x10011e3c0
  4.  
  5. objc[21536]: 2 releases pending.
  6.  
  7. objc[21536]: [0x101802000] ............ PAGE (hot) (cold)
  8.  
  9. objc[21536]: [0x101802038] ################ POOL 0x101802038
  10.  
  11. objc[21536]: [0x101802040] 0x1003062e0 NSObject
  12.  
  13. objc[21536]: ##############
  14.  
  15. Program ended with exit code: 0

In objc4, you can see AutoreleasePoolPage:

  1. objc4/NSObject.mm AutoreleasePoolPage
  2.  
  3.   
  4.  
  5. class AutoreleasePoolPage
  6.  
  7. {
  8.  
  9. static inline void *push()
  10.  
  11. {
  12.  
  13. Create or hold NSAutoreleasePool class objects
  14.  
  15. }
  16.  
  17. static inline void pop(void *token)
  18.  
  19. {
  20.  
  21. Deprecated NSAutoreleasePool class objects
  22.  
  23. releaseAll();
  24.  
  25. }
  26.  
  27. static inline id autorelease(id obj)
  28.  
  29. {
  30.  
  31. Equivalent to the addObject class method of the NSAutoreleasePool class
  32.  
  33. AutoreleasePoolPage *page = Get the AutoreleasePoolPage instance in use;
  34.  
  35. }
  36.  
  37. id * add (id obj)
  38.  
  39. {
  40.  
  41. Appends the object to the internal array
  42.  
  43. }
  44.  
  45. void releaseAll()
  46.  
  47. {
  48.  
  49. Call the release method of the object in the internal array
  50.  
  51. }
  52.  
  53. };
  54.  
  55.   
  56.  
  57. void *
  58.  
  59. objc_autoreleasePoolPush(void)
  60.  
  61. {
  62.  
  63. if (UseGC) return nil;
  64.  
  65. return AutoreleasePoolPage::push();
  66.  
  67. }
  68.  
  69.   
  70.  
  71. void
  72.  
  73. objc_autoreleasePoolPop(void *ctxt)
  74.  
  75. {
  76.  
  77. if (UseGC) return ;
  78.  
  79. AutoreleasePoolPage::pop(ctxt);
  80.  
  81. }

AutoreleasePoolPage is composed in the form of a doubly linked list (corresponding to the parent pointer and child pointer in the structure).

The thread pointer points to the current thread.

Each AutoreleasePoolPage object will allocate 4096 bytes of memory (that is, the size of one page of virtual memory). In addition to the space occupied by the above instance variables, the remaining space is used to store the address of the autorelease object.

The next pointer points to the location where the next autorelease object added will be stored.

When the space of a Page is full, a new AutoreleasePoolPage object will be created to connect to the linked list.

6 __unsafe_unretained

Sometimes we also use the __unsafe_unretained modifier in addition to __weak and __strong, so how much do we know about __unsafe_unretained?

__unsafe_unretained is an unsafe ownership modifier. Although ARC memory management is the work of the compiler, variables with the __unsafe_unretained modifier do not belong to the compiler's memory management objects. Neither strong references nor weak references are obtained when assigning values.

Let's run a piece of code:

  1. id __unsafe_unretained obj1 = nil;
  2.  
  3. {
  4.  
  5. id __strong obj0 = [[NSObject alloc] init];  
  6.   
  7.  
  8. obj1 = obj0;  
  9.   
  10.  
  11. NSLog(@ "A: %@" , obj1);
  12.  
  13. }  
  14.   
  15.  
  16. NSLog(@ "B: %@" , obj1);

Running results:

  1. 2017-01-12 19:24:47.245220 __unsafe_unretained[55726:4408416] A:
  2.  
  3. 2017-01-12 19:24:47.246670 __unsafe_unretained[55726:4408416] B:
  4.  
  5. Program ended with exit code: 0

Detailed analysis of the code:

  1. id __unsafe_unretained obj1 = nil;
  2.  
  3. {
  4.  
  5. // Generate and hold the object yourself
  6.  
  7. id __strong obj0 = [[NSObject alloc] init];
  8.  
  9.   
  10.  
  11. // Because obj0 variable is a strong reference,
  12.  
  13. // So I hold the object myself
  14.  
  15. obj1 = obj0;
  16.  
  17.   
  18.  
  19. // Although obj0 variable is assigned to obj1
  20.  
  21. // But the obj1 variable holds neither a strong reference nor a weak reference to the object
  22.  
  23. NSLog(@ "A: %@" , obj1);
  24.  
  25. // Output the object represented by the obj1 variable
  26.  
  27. }
  28.  
  29.   
  30.  
  31. NSLog(@ "B: %@" , obj1);
  32.  
  33. // Output the object represented by the obj1 variable
  34.  
  35. // The object represented by the obj1 variable has been discarded
  36.  
  37. // So what you get at this time is a dangling pointer
  38.  
  39. // Error access

Therefore, the *** NSLog just happens to run normally. If it is accessed incorrectly, it will cause a crash.

When using the __unsafe_unretained modifier, when assigning to a variable with the __strong modifier, make sure that the object actually exists.

<<:  iOS Advanced——Block

>>:  Android View related core knowledge questions and answers

Recommend

How to get accurate fans through short videos?

Have you ever encountered the same situation? The...

Server Hosting Transfer Notes

Server Hosting Transfer Notes 1. Debug website pr...

A scientific guide to petting cats (full version)

Many of us may have encountered a cat that seemed...

The shepherd's purse that you love to eat can actually be eaten with meat?

The spring breeze first brings the plum blossoms ...

Why can't LG "copy" Samsung's success?

For Chinese consumers, LG is another Korean techn...

What will the next iPad Pro have? MiniLED screen, 5G, and two updates a year?

Although Apple just updated the iPad Pro in March...

7 skills you need to acquire as an app operator

As a type of Internet operation , App operation c...