First iOS Memory Management

1 It seems that everyone has considered in the process of learning iOS

  1. What does alloc retain Release delloc do?
  2. How is Autoreleasepool implemented?
  3. What is __unsafe_unretained?
  4. How is a Block implemented
  5. When does a circular reference 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, source code.

2. Start with ARC

ARC (Automatic Reference Counting) is a change introduced by WWDC2011 and iOS5. ARC is a feature of the LLVM 3.0 compiler to automatically manage memory.

Unlike GC in Java, ARC is a compiler feature rather than runtime based, so ARC actually automatically helps developers insert code to manage memory at compile time rather than monitoring and recollecting memory in real time.

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

  1. Each object has a “reference count”
  2. Object is held, reference count +1
  3. Object abandoned, reference count -1
  4. Reference Count =0, release the object

3 You need to know

1. The Foundation framework that contains the NSObject class is not publicly available

  1. The Foundation framework is non-open source, but NSObject is included in obj4, which is open source.
  2. The source code for the Core Foundation framework, as well as some of the source code for memory management through NSObject, is public.
  3. GNUstep is an interchangeable framework for the Foundation framework

GNUstep is also part of the GNU project. Reimplementing the Cocoa Objective-C libraries as free software in a sense, GNUstep and Foundation framework are similar in implementation and analyzing memory management for Foundation through GNUstep source code

Alloc retain release dealloc

4.1 the GNU – alloc

Look at the alloc function in GNUStep.

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

+ (id) alloc
{
  return [self allocWithZone: NSDefaultMallocZone()];
}

+ (id) allocWithZone: (NSZone*)z
{
  return NSAllocateObject (self.0, z);
}
Copy the code

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

struct obj_layout {
    NSUInteger retained;
};

NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)
{
    intSize = Calculate the size of memory needed to hold the object;id	new = NSZoneCalloc(zone, 1, size);
    memset (new, 0, size);
    new = (id)&((obj)new)[1];
}
Copy the code

The NSAllocateObject function allocates the required space for an object by calling NSZoneCalloc, sets the memory space to nil, and returns the pointer used as an object.

Let’s simplify the above code:

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

struct obj_layout {
    NSUInteger retained;
};

+ (id) alloc
{
    int size = sizeof(structObj_layout) + object size;struct obj_layout *p = (struct obj_layout *)calloc(1, size);
    return (id)(p+1)
    return [self allocWithZone: NSDefaultMallocZone()];
}
Copy the code

The alloc method uses struct obj_layout as a retained integer to store the reference count, and writes it to the head of the object’s memory. The whole block of memory is set to 0 and returned.

The representation of an object looks like this:

4.2 the GNU – retain

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

- (NSUInteger) retainCount
{
  return NSExtraRefCount(self) + 1;
}

inline NSUInteger
NSExtraRefCount(id anObject)
{
  return ((obj_layout)anObject)[- 1].retained;
}
Copy the code

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

- (id) retain
{
  NSIncrementExtraRefCount(self);
  return self;
}

inline void
NSIncrementExtraRefCount(id anObject)
{
  if (((obj)anObject)[- 1].retained == UINT_MAX - 1)
    [NSException raise: NSInternalInconsistencyException
      format: NSIncrementExtraRefCount() asked to increment too far]; ((obj_layout)anObject)[-1].retained++; }Copy the code

Across this code, the NSIncrementExtraRefCount method first writes the exception that occurred when the retained variable exceeded the maximum value (because retained is the NSUInteger variable), and then retains ++ the code.

The GNU – release 4.3

Corresponding to retain, the release method does just retain –.

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

- (oneway void) release
{
  if (NSDecrementExtraRefCountWasZero(self)) {[selfdealloc]; }}BOOL
NSDecrementExtraRefCountWasZero(id anObject)
{
  if (((obj)anObject)[- 1].retained == 0)
  {
	  return YES;
	}
  ((obj)anObject)[- 1].retained--;
	return NO;
}
Copy the code

4.4 the GNU – dealloc

Dealloc will release the object.

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

- (void) dealloc
{
  NSDeallocateObject (self);
}

inline void
NSDeallocateObject(id anObject)
{
  obj_layout o = &((obj_layout)anObject)[- 1];
  free(o);
}
Copy the code

4.5 Apple implementation

In Xcode, set Debug -> Debug Workflow -> Always Show Disassenbly to open. This allows you to see more detailed method calls after the break point.

By setting a breakpoint trace on methods such as NSObject’s alloc, you can see that several methods are called internally:

retainCount

__CFdoExternRefOperation CFBasicHashGetCountOfKey

retain

__CFdoExternRefOperation CFBasicHashAddValue

release

__CFdoExternRefOperation CFBasicHashRemoveValue

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

This method is included in Core Foundation, and can be found in cfruntime. c.

CFRuntime.c __CFDoExternRefOperation:

int __CFDoExternRefOperation(uintptr_t op, id obj) {
    CFBasicHashRefTable = hash table of the object (obj);int count;
    
    switch (op) {
        case OPERATION_retainCount:
        count = CFBasicHashGetCountOfKey(table, obj);
        return count;
        break;
        case OPERATION_retain:
        count = CFBasicHashAddValue(table, obj);
        return obj;
        case OPERATION_release:
        count = CFBasicHashRemoveValue(table, obj);
        return 0== count; }}Copy the code

So __CFDoExternRefOperation is a specific method call for a different operation. If op is OPERATION_retain, the retain method is removed.

As you can see from method names like BasicHash, reference count tables are actually hash tables.

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

Below is a comparison of Apple and GNU implementations:

5 autorelease and autorelaesepool

In apple’s documentation for NSAutoreleasePool:

Each thread, including the main thread, maintains a stack that manages NSAutoreleasePool. When new pools are created, they are added to the top of the stack. When pools are destroyed, they are removed from the stack. The autoRelease object is added to the Pool at the top of the stack for the current thread. When a Pool is destroyed, the objects in it are also released. When the thread terminates, all pools are destroyed and freed.

If you look at the NSAutoreleasePool class method and autoRelease method break points, you can see that the following functions are called:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Equivalent to objc_autoreleasePoolPush
    
id obj = [[NSObject alloc] init];
[obj autorelease];
// equivalent to objc_autoRelease (obj)
    
[NSAutoreleasePool showPools];
// Check the NSAutoreleasePool status
    
[pool drain];
// Equivalent to objc_autoreleasePoolPop(pool)
Copy the code

[NSAutoreleasePool showPools]

objc[21536]: ##############
objc[21536]: AUTORELEASE POOLS for thread 0x10011e3c0
objc[21536]: 2 releases pending.
objc[21536]: [0x101802000]  ................  PAGE  (hot) (cold)
objc[21536]: [0x101802038]  ################  POOL 0x101802038
objc[21536]: [0x101802040]       0x1003062e0  NSObject
objc[21536]: ##############
Program ended with exit code: 0
Copy the code

AutoreleasePoolPage is available in objC4:

objc4/NSObject.mm AutoreleasePoolPage

class AutoreleasePoolPage 
{
    static inline void*push() {generate or holdNSAutoreleasePoolClass object}static inline void pop(void{* token)NSAutoreleasePoolClass object releaseAll (); }static inline id autorelease(idObj) {equivalent toNSAutoreleasePoolClass addObject method AutoreleasePoolPage * Page = get the instance of AutoreleasePoolPage in use; }id *add(idObj) {append object to internal array}voidReleaseAll () {call the release method of the object in the inner array}};void *
objc_autoreleasePoolPush(void)
{
    if (UseGC) return nil;
    return AutoreleasePoolPage::push();
}

void
objc_autoreleasePoolPop(void *ctxt)
{
    if (UseGC) return;
    AutoreleasePoolPage::pop(ctxt);
}
Copy the code

AutoreleasePoolPage is composed as a bidirectional linked list (corresponding to the parent and child Pointers in the structure, respectively). The thread pointer points to the current thread. Each AutoreleasePoolPage object allocates 4096 bytes of memory (the size of a page of virtual memory), which is used to store the address of the AutoRelease object except for the instance variables above. The next pointer points to where the next added autoRelease object will be stored. When the space of a Page is used up, an AutoreleasePoolPage object is created to connect the linked list.

6 __unsafe_unretained

“__unsafe_unretained” is sometimes used across __weak and __strong. What do we know about __unsafe_unretained?

__unsafe_unretained is an insecure ownership modifier. Although ARC’s memory management is the compiler’s job, variables attached with the __unsafe_unretained modifier are not part of the compiler’s memory management object. Assignment gets neither strong nor weak references.

Run a piece of code:

id __unsafe_unretained obj1 = nil;
{
    id __strong obj0 = [[NSObject alloc] init];
            
    obj1 = obj0;
            
    NSLog(@"A: %@", obj1);
}
        
NSLog(@"B: %@", obj1);
Copy the code

Running results:

2017-01-12 19:24:47.245220 __unsafe_unretained[55726:4408416] : <NSObject: 0x100304800> 2017-01-12 19:24:47.246670 __unsafe_unretained[55726:4408416] B: <NSObject: 0x100304800> Program ended with exit code: 0Copy the code

A detailed analysis of the code:

id __unsafe_unretained obj1 = nil;
{
    // Generate and hold your own objects
    id __strong obj0 = [[NSObject alloc] init];
            
    // Because obj0 is a strong reference,
    // So I hold the object myself
    obj1 = obj0;
            
    // Although the obj0 variable is assigned to obj1
    But the obj1 variable holds neither a strong nor a weak reference to an object
    NSLog(@"A: %@", obj1);
    // Outputs the object represented by the obj1 variable
}
        
    NSLog(@"B: %@", obj1);
    // Outputs the object represented by the obj1 variable
    // The object represented by the obj1 variable is deprecated
    // Get a dangling pointer
    // Error access
Copy the code

Therefore, the last NSLog just happens to work. If accessed incorrectly, crash can be caused. However, using the __unsafe_ununretained modifier, assign a value to a variable with the __strong modifier, and ensure that the object exists

The second Block,

Take a few minutes to look at the three questions below and write down your answers.

I asked three friends to answer these three small questions before sorting out this blog post. In the end, except for one friend, all three questions were correct, the other two friends only answered one question.

There are still many iOS friends who do not have a thorough understanding of blocks. This post will explain the Block in detail.

1 Simple rules used by blocks

Understand the simple rules first, then analyze the principle and implementation:

In a Block, the Block expression intercepts the value of the automatic variable used, that is, holds the transient value of the automatic variable. A variable decorated with a __block that, when captured, no longer gets a transient value.

As for Why, more on that later.

2 Block implementation

Blocks are anonymous functions with automatic variables (local variables). Block expressions are simple and can be described as: “^ Return value type argument list expression.” But blocks aren’t just objective-C syntax. What’s going on here?

The CLang compiler provides programmers with a way to understand the mechanics behind Objective-C, and to see how blocks work through clang transformations.

Clang-rewrite-objc yourfile.m Clang will convert Objective-C code to C code.

2.1 Block basic implementation analysis

Create the Command Line project with Xcode and write the following code:

int main(int argc, const char * argv[]) {
    void (^blk)(void) = ^ {NSLog(@"Block")};
    blk();
    return 0;
}
Copy the code

Convert with clang:

The above is converted code, do not square, a paragraph to see.

As you can see, the implementation of the Block is converted to a normal static function __main_func_0.

Look at the rest:

main.cpp __block_impl:

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
Copy the code

The __block_impl structure contains flags, variables reserved for future version upgrades, and function Pointers.


main.cpp __main_block_desc_0:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0.sizeof(struct __main_block_impl_0)};
Copy the code

The __main_block_DESC_0 structure contains variables and block sizes reserved for future version upgrades.


main.cpp __main_block_impl_0:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

The __main_block_IMPL_0 structure contains two member variables, __block_impl and __main_block_DESc_0 instance variables.

There is also a constructor. This constructor is called in main as follows:

Call to the main. CPP __main_block_IMPL_0 constructor:

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
                                              &__main_block_desc_0_DATA));
Copy the code

Remove various casts and simplify:

The main. CPP __main_block_impl_0 constructor is called simply:

struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
Copy the code

Assign a pointer to an instance of the __main_block_IMPL_0 structure to a variable BLK of type __main_block_IMPL_0. Which is our original structure definition:

 void (^blk)(void) = ^ {NSLog(@"Block"); };Copy the code

In addition, there is another paragraph in main:

((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
Copy the code

Remove various conversions:

(*blk->impl.FuncPtr)(blk);
Copy the code

Actually is the original:

blk();
Copy the code

All the code in this section is in block_implementation

2.2 Implementation analysis of Block interception of transient value of external variables

In 2.1, clANG transforms the simplest Block declarations and calls without arguments. Clang – rewrite-objc-fobjc-arc-fobjc-Runtime =macosx-10.7 main.m

int main(int argc, const char * argv[]) {
    
    int val = 10;
    const char *fmt = "val = %d\n";
    void (^blk)(void) = ^{printf(fmt, val); }; val =2;
    fmt = "These values were changed, val = %d\n";
    
    blk();
    
    return 0;
}
Copy the code

After clang conversion:

Compared to the conversion code in Section 2.1, you can see that there is a bit more code.

First, __main_block_impl_0 has an extra variable val, and an assignment to val is added to the constructor argument:

main.cpp __main_block_impl_0:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

In the main function, the declaration of the Block becomes this:

Call to the main. CPP __main_block_IMPL_0 constructor:

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
Copy the code

Remove the conversion:

The main. CPP __main_block_impl_0 constructor is called simply:

struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, val);
    struct __main_block_impl_0 *blk = &tmp;
Copy the code

So, by the time a Block is declared, the Block has already changedvalAs a__main_block_impl_0The internal variables of the. No matter how the value of val is changed after the declaration, it does not affect the internal value of val that Block calls. This is how blocks capture transient values of variables.

All code for this section is in EX05

2.3 Access implementation analysis of __block variables

We know that you can read from a Block, but you can’t change a local variable, and if you do, Xcode will tell you that you can’t change a variable inside the Block.

Inside a Block, only local variables are read only, but blocks can read and write the following variables:

  1. A static variable
  2. Static global variable
  3. The global variable

That is, the following code is fine:

int global_val = 1;
static int static_global_val = 2;

int main(int argc, const char * argv[]) {
    static int static_val = 3;
    
    void (^blk)(void) = ^ {
        global_val = 1 * 2;
        static_global_val = 2 * 2;
        static_val = 3 * 2;
    }
    
    return 0;
}
Copy the code

If you want to write local variables inside a Block, you need to add a __block modifier to the accessed local variables.

The __block modifier is similar to the STATIC, auto, and register modifiers in C. Used to specify which storage domain to set the variable value to.

We can write code to test what changes have been made to __block:

EX07:

int main(int argc, const char * argv[]) { __block int val = 10; void (^blk)(void) = ^{val = 1; }; return 0; }Copy the code

After clang conversion:

Compared to 2.2, there seems to be extra code. We found two extra structures.

main.cpp __Block_byref_val_0:

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};
Copy the code

Surprisingly, the __block val becomes an instance of the __Block_byref_val_0 structure. This instance contains the __isa pointer, a flag bit __flags, and a record size __size. Most importantly, there is an __forwarding pointer and a val variable. What’s going on here?

In the main section, we instantiate the structure:

Main.cpp main.m section:

__Block_byref_val_0 val = {(void*)0,
                            (__Block_byref_val_0 *)&val,
                            0,
                            sizeof(__Block_byref_val_0),
                            10};
Copy the code

We can see that when the struct object is initialized:

  1. __forwarding refers to the memory address of the structure instance itself
  2. val = 10

In main, the assignment statement val = 1 becomes:

main.cpp val = 1;Corresponding functions:

(val->__forwarding->val) = 1;
Copy the code

Here’s the gist: val = 1, which actually changes the variable val to which the __forwarding pointer (itself) points in the __Block_byref_val_0 structure instance val.

The same is true for the val access. You can think of it as changing the value of a variable by taking an address, just like changing a variable by taking an address in C.

Therefore, variables declaring __block can be changed. As for __forwarding’s other great effects, we’ll continue to analyze them.

The code for this section is in EX05

3 Block storage domain

There are three types of blocks:

  1. __NSConcreteStackBlock — — — — the stack
  2. __NSConcreteGlobalBlock ———————— in the data area
  3. __NSConcreteMallocBlock — — — — in the heap

__NSConcreteGlobalBlock occurs where:

  1. Set global variables where there is Block syntax
  2. Block syntax does not use any external variables in an expression

Blocks placed on the stack are discarded if the scope of the variable to which they belong ends. If __block is used, the end of scope of the variable to which __block belongs is also deprecated.

To solve this problem, blocks need to be moved from the stack to the heap when necessary. In many cases, the compiler will help you copy blocks when ARC is in effect, but in many cases, you will need to copy blocks manually.

Block copy of different storage domains is affected as follows:

Copy affects __block objects as follows:

At this point, you can see the power of __forwarding — whether the Block is in the heap or on the stack, correct access is ensured because __forwarding refers to the real address of the structure instance to which the local variable is converted.

To be specific:

  1. When a __block variable is used by a Block, the Block is copied from the stack to the heap, and the __block variable is also copied to and held by the Block.
  2. When a __block variable is used by multiple blocks, when any Block is copied from the stack to the heap, the __block variable is also copied to the heap and held by that Block. But as a result of__forwardingThe presence of a pointer allows access to a __block variable, regardless of whether the two variables are in the same storage domain.
  3. If a Block on the heap is deprecated, the __block variable it uses is also released.

As mentioned earlier, the compiler will help with some Block copy, as well as manually copy blocks. If a Block is copied to the heap, the following conditions can occur:

  1. Call Block’s copy method
  2. Block as the return value
  3. Assign a Block to an enclosing__strongIs a member variable of the modifier (type ID or type Block)
  4. Contains in the method nameusingBlockCocoa framework methods or GCD apis

4 Block circular reference

Block circular references are a very common problem in programming, and many times we don’t even know that circular references are happening until we suddenly realize that there is a problem when we say “Why isn’t this object calling Delloc?”

Block storage domain also states that blocks retain __block objects once after copy.

Then a circular reference occurs for:

block_retain_cycle:

@interface MyObject : NSObject

@property (nonatomic.copy) blk_t blk;
@property (nonatomic.strong) NSObject *obj;

@end

@implementation MyObject

- (instancetype)init {
    self = [super init];
    _blk = ^{NSLog(@"self = %@".self); };return self;
}

- (void)dealloc {
    NSLog(@"%@ dealloc".self.class);
}

@end

int main(int argc, const char * argv[]) {
    id myobj = [[MyObject alloc] init];
    NSLog(@ "% @", myobj);
    return 0;
}
Copy the code

Because self -> BLK, BLK -> self, neither side can release.

Note, however, that circular references also occur in the following cases:

block_retain_cycle

@interface MyObject : NSObject

@property (nonatomic.copy) blk_t blk;

//
@property (nonatomic.strong) NSObject *obj;

@end

@implementation MyObject

- (instancetype)init {
    self = [super init];
    
    //
    _blk = ^{NSLog(@"self = %@", _obj); };return self;
}

- (void)dealloc {
    NSLog(@"%@ dealloc".self.class);
}

@end

int main(int argc, const char * argv[]) {
    id myobj = [[MyObject alloc] init];
    NSLog(@ "% @", myobj);
    return 0;
}
Copy the code

This is due to self -> obj, self -> BLK, BLK -> obj. This situation is very easy to ignore.

5. Review the question

Let’s take a look at the first few questions:

  1. The first question:

    Since the Block captures transient values, the output is in Block val = 0

  2. The second question:

    Since val is __block, external changes affect internal access, so the output is in block val = 1

  3. The third question:

    In Block val = 1, change val inside the Block, and print after Block val = 2.

Other

I wrote this post after reading a book called “Objective-C Advanced Programming for iOS and OS X Multithreading and Memory Management.” There was also a lot of content in the post about objective-C advanced Programming for iOS and OS X multithreading and memory management.

I highly recommend this book. In this book, you can find in-depth information about iOS memory management. But it should be noted that many knowledge points in this book are not very detailed, you need to expand the mentality to learn. Where there is no detailed explanation, take the initiative to explore, expand, find more information, and finally, you will find that you have a more in-depth understanding of iOS memory management.

For the test code in this article, it’s all here.