problems

We know that in object-oriented programming, each object has a corresponding constructor and destructor, which are called separately when the object is created and destroyed. The whole process from creation to destruction of objects is called the life cycle of objects. In order to ensure that objects are valid and not released prematurely when (1) objects are used; (2) When objects are not needed, they are destroyed normally instead of staying in memory all the time. We need to manage the life cycle of objects. So what is the object lifecycle management strategy in iOS? To get a better understanding of the iOS memory management mechanism step by step, here are some questions: (1) iOS uses reference counting to manage the life cycle of objects. What is reference counting? (2) How is reference count stored? (3) What is MRC, what is the memory management strategy under MRC, and what should be paid attention to? (4) What is the role of ARC, what are the new rules of ARC, and what problems ARC will bring? (5) How to convert objects managed by ARC and MRC, and what should be paid attention to during conversion? (6) What is Autoreleasepool and how does it work? What is his relationship to Runloop? (7) What is TaggedPointer and which objects use the TaggedPointer technique?

Principle of inquiry

(1) About iOS memory management

Application memory management is the process of allocating memory while the program is running, using it, and releasing it when it is finished. Well-written programs will use as little memory as possible. In Objective-C, it can also be seen as a way to allocate ownership of limited memory resources between a lot of data and code. With knowledge of memory management, we can manage the application’s memory by managing the object life cycle well and releasing them when they are no longer needed.

Although memory management is usually considered at the individual object level, in reality our goal is to manage the object graph, ensuring that only the objects needed are kept in memory and that no memory leaks occur.

The following image is a “memory Managed object diagram” from Apple’s official documentation, which shows the “create, hold, release, destroy” process of an object.

(2) iOS uses reference counting to manage object lifecycles. What is reference counting?

Principle of reference counting

Use reference counting to manage the life cycle of objects, making the reference count +1 when an object needs to be held; Make an object’s reference count -1 when it is not needed to hold it. When an object’s reference count reaches zero, the object is destroyed.

The book Objective-C Advanced Programming: iOS and OS X Multithreading and Memory Management gives an example of “lighting problems in the office,” which provides a good example of reference counting.

Suppose there is only one lighting device in the office. People coming into the office at work need light, so turn it on. For people leaving the office after work, lighting is no longer needed, so turn it off.

What will the office look like if there are so many commuters and everyone turns the lights on and off? If the earliest person to leave the office turns off the lights, it will look like the picture below, leaving everyone in the office in darkness.

The solution to this problem is to keep the lights on when there is at least one person present and off when no one is present. (1) The first person to enter the office turns on the light. (2) The person who enters the office after that needs lighting. (3) People who leave the office after work do not need lighting. (4) The last person to leave the office turns off the lights (no one needs lighting at this time). To determine if anyone is still in the office, import the count function here to count the number of people who need lighting. Let’s take a look at how this works. (1) The first person to enter the office, “number of people needing light” + 1. The count has changed from 0 to 1, so the light is on. (2) After that, each time someone enters the office, the “number of people needing light” increases by 1. As the count changes from 1 to 2. (3) Every time someone leaves the office after work, “number of people needing light” is reduced by 1. As the count changes from 2 to 1. (4) The last person to leave the office, “number of people in need of lighting” minus 1. The count has changed from 1 to 0, so we have to turn off the light. So you can keep the lights off when you don’t need them. The only lighting equipment in the office is well managed, as shown in the picture below:

In Objective-C, “objects” are the equivalent of lighting in an office. In the real world, there is only one lighting device in an office, but in objective-C, computers have limited resources, but a single computer can handle several objects at once. In addition,”The context in which the object is used“Is equivalent to the person who enters the office at work. Although the “environment” here sometimes alsoRefers to running program code, variables, variable scope, objectsAnd so on, but conceptually is the environment in which objects are used. The mapping between the actions of people entering the office and the office lighting in Objective-C is shown in the following table:

Use the counting function to calculate the number of people who need lighting, so that the lighting of the office has been well managed. Similarly, objects can be well managed using reference counting, which is Objective-C memory management. As shown below:

Storage of reference counts

We’ve taken a look at the concept of “reference counting,” the reference counting function used by objective-C “objects” to manage their memory life cycle. So how is the reference count of an object stored? Which data structure is it stored in?

First, the ISA has to be mentioned.

Isa Pointers are used to maintain the relationship between objects and classes, and to ensure that objects and classes can find corresponding methods, instance variables, attributes, protocols, and so on.Before arm64, ISA was a plain pointer to objc_class and stored the memory addresses of Class and meta-class objects. Instance isa points to class, and class ISA points to meta-class.

// objc.h
struct objc_object {
    Class isa;  // Before arm64 architecture
};

Copy the code

Starting with the ARM64 architecture, ISA has been optimized to be represented by nonpointer, become a common body (Union) structure, and use bitfields to store more information. There are a lot of things to store in 64 bits of memory, of which 33 bits are used to store class and meta-class objects. To obtain the memory address of class and meta-class objects, the value of ISA and ISA_MASK must be masked by bit operation.

// objc-private.h
struct objc_object {
private:
    isa_t isa;  // Start with the ARM64 architecture
};

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

#if SUPPORT_PACKED_ISA

    // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
    // nonpointer must be the LSB (fixme or get rid of it)
    // shiftcls must occupy the same bits that a real class pointer would
    // bits + RC_ONE is equivalent to extra_rc + 1
    // RC_HALF is the high bit of extra_rc (i.e. half of its range)

    // future expansion:
    // uintptr_t fast_rr : 1; // no r/r overrides
    // uintptr_t lock : 2; // lock for atomic property, @synch
    // uintptr_t extraBytes : 1; // allocated with extra bytes

# if __arm64__  // In the __arm64__ schema
#   define ISA_MASK        0x0000000ffffffff8ULL  // Retrieve the memory address of Class and meta-class objects
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;  // 0: represents a common pointer, storing the memory address of Class and meta-class objects
                                          // 1: indicates that it is optimized to use bitfields to store more information
        uintptr_t has_assoc         : 1;  // Whether the associated object is set, if not, the release will be faster
        uintptr_t has_cxx_dtor      : 1;  // whether there is a C++ destructor (.cxx_destruct), if not, the release will be faster
        uintptr_t shiftcls          : 33; // Store the memory address information of Class and meta-class objects
        uintptr_t magic             : 6;  // Used to tell if an object is not initialized during debugging
        uintptr_t weakly_referenced : 1;  // If there is a weak reference, the release will be faster
        uintptr_t deallocating      : 1;  // Whether the object is being released
        uintptr_t has_sidetable_rc  : 1;  // If it is 1, the reference count is too large to be stored in ISA, and the excess reference count is stored in a RefCountMap hash table called SideTable
        uintptr_t extra_rc          : 19; // The value stored inside is the number of reference counts outside of the object itself, retaincoun-1
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)}; .// in the __x86_64__ schema
};

Copy the code

If ISA is not nonpointer, it is the ISA pointer before the ARM64 architecture. Since it is just a pointer that stores the memory address of a Class or meta-class object, it cannot store reference counts by itself, so the reference counts of an object were stored in a RefCountMap hash table called SideTable.

If ISA is nonpointer, it can store some reference counts of its own. From the above definition of union ISA_t, we can see that isa_t stores two reference-counting related things: extra_rc and has_sideTABLE_rc.

  • Extra_rc: the value stored in extra_rc is the number of reference counts outside the object itself. Has_sidetable_rc changes to 1 if the 19 bits are not enough.
  • Has_sidetable_rc: If 1, the reference count is too large to be stored in ISA. The excess reference count is stored in SideTable’s RefCountMap.

So, if ISA is nonpointer, the object’s reference count is stored in extra_RC of its ISA_T and in RefCountMap of SideTable.

(3) What is MRC, what is the memory management strategy under MRC, and what should be paid attention to?

Memory Management Policy

(a) to create and hold objects use alloc/new/copy/mutableCopy method (or methods) begin with the methods of creating objects we hold directly, the RC (below the reference count, unified use of RC) initial value is 1, can we use directly, Call the release method when it is not needed.

    id obj = [NSObject alloc] init]; // Create and hold object, RC = 1
    /* * use this object, RC = 1 */
    [obj release]; // Call release when it is not needed, RC = 0, and the object is destroyed

Copy the code

If we want to create, through the custom method and holds the object, the method name should begin with the alloc/new/copy/mutableCopy, naming rules and should follow the hump, the object returned by alloc/new/copy/mutableCopy method to create, such as:

- (id)allocObject
{
    id obj = [NSObject alloc] init];
    return obj;
}

Copy the code

If we want to create, through the custom method but it doesn’t hold objects, the method name should not begin with the alloc/new/copy/mutableCopy, and before the returned object should be released automatically by the autorelease method to add the object to the pool. Such as:

- (id)getObject
{
    id obj = [NSObject alloc] init];
    [obj autorelease];
    return obj;
}

Copy the code

This way, when the caller creates an object using the method, he knows by method name that he does not hold the object, so he retains it before using it and releases it when it is no longer needed.

Retain We can retain an object using retain. Using some custom methods, such as the getObject method above, we create objects that we do not hold and whose RC starts with 1. Note, however, that if you want to use this object, you need to retain first, otherwise it may cause the program to Crash. The reason is that inside the getObject method, objects are added to the auto-release pool, and the auto-release pool will send a release message to the objects in the auto-release pool at the appropriate time. Once the object’s RC drops to 0, it will be destroyed. In the main thread, when the RunLoop iteration ends, the release method is automatically called for the objects in the auto-release pool. So if we do not retain before use, when the RunLoop iteration ends, the object will receive a release message and will be destroyed if its RC value drops to 0. If we try to access the object that has been destroyed, the program will Crash.

    id obj = [NSMutableArray array]; // Create an object without holding it, add the object to the automatic release pool inside the array method, RC = 1

    [obj retain]; // Retain the object before use, RC = 2
    /* * use this object, RC = 2 */
    [obj release]; // Call release when not needed, RC = 1
    /* * RunLoop may end the iteration at some point, call release to the object in the auto-release pool, RC = 0, and the object is destroyed * * If the RunLoop has not finished iterating at this point, the object can still be accessed, but this is very dangerous and can cause Crash */

Copy the code

If the @AutoReleasepool scope ends, the autoRelease method is automatically called. If we access this object again, the program will Crash.

    /* 错误的用法 */

    id obj;
    @autoreleasepool {
        obj = [NSMutableArray array]; // Create object but do not hold it, add object to automatic release pool, RC = 1
    } // @autoreleasepool scope ends, object release, RC = 0, object destroyed
    NSLog(@ "% @",obj); // EXC_BAD_ACCESS

Copy the code
    /* Correct usage */

    id obj;
    @autoreleasepool {
        obj = [NSMutableArray array]; // Create object but do not hold it, add object to automatic release pool, RC = 1
        [obj retain]; // RC = 2
    } // @autoreleasepool scope ends, object release, RC = 1
    NSLog(@ "% @",obj); // Normal access
    /* * use this object, RC = 1 */
    [obj release]; // Call release when it is not needed, RC = 0, and the object is destroyed

Copy the code

Release when you no longer need to use (hold) the object, you need to call the release or autoRelease method torelease (or “give up the right to use the object”), so that it rC-1, to prevent memory leakage. When the object’s RC is 0, the dealloc method is called to destroy the object.

(4) cannot release a hold objects themselves We can learn from the above, holding objects have two ways, one is through the alloc/new/copy/mutableCopy method to create objects, and the second is through retain method. If you are the owner, you need to call the release method when the object is no longer needed. However, if you are not the owner, you cannot release the object or Crash the program. In addition, it is not safe to send messages to already reclaimed objects in two cases:

    id obj = [[NSObject alloc] init]; // Create and hold object, RC = 1
    [obj release]; // If you're the owner, call release when you don't need it, RC = 0
    /* * The object has been destroyed and should not be accessed */
    [obj release]; // EXC_BAD_ACCESS is no longer the owner, and then release will Crash
    /* * Releasing the destroyed object again (overreleasing) or accessing the destroyed object will cause a crash */

Copy the code
    id obj = [NSMutableArray array]; // Create object, but do not hold object, RC = 1
    [obj release]; // EXC_BAD_ACCESS does not hold object RC = 1, causing Crash

Copy the code

Practical memory management tips

Use autoRelease to send delayed release

The String object is created using the alloc method, so a release message is sent when the object is no longer needed.

- (NSString *)fullName {
    NSString *string = [[NSString alloc] initWithFormat:@ % @ % @ "".self.firstName, self.lastName]; 
    [string release];
    return string;
}
Copy the code

According to memory management rules, you create and hold objects using the alloc method and send a release message when the object is no longer needed. But if you use release in a method, the NSString object will be destroyed before return, and the method will return an invalid object. You can use autoRelease when you need to send deferred release messages, usually when returning objects from methods. For example,

- (NSString *)fullName {
    NSString *string = [[NSString alloc] initWithFormat:@ % @ % @ "".self.firstName, self.lastName];
   [string autorelease];
   return string;
}

Copy the code

You can also implement the fullName object method this way

- (NSString *)fullName {
    NSString *string = [NSString stringWithFormat:@ % @ % @ "".self.firstName, self.lastName];
    return string;
}
Copy the code

According to memory management rules, you don’t hold NSString objects, so you don’t have to worry about releasing them, you just return. The stringWithFormat method internally calls the autoRelease method on the NSString object.

In contrast, the following implementation is incorrect:

- (NSString *)fullName {
    NSString *string = [[NSString alloc] initWithFormat:@ % @ % @ "".self.firstName, self.lastName];
    return string;
}
Copy the code

The reason is that inside the fullName method we create the object and hold it through the alloc method, but we don’t release the object. And the method name does not begin with alloc/new/copy/mutableCopy. From the caller’s point of view, the object acquired through this method is not held, so he retains and releases it when he doesn’t need it, and there is no memory problem in using it this way. However, the object has a reference count of 1 and is not destroyed, causing a memory leak.

You don’t hold objects returned by reference

Some methods in Cocoa specify that objects are returned by reference (they take arguments of type ClassName ** or ID *). A common one is to use an NSError object, which contains information about the error (if an error occurs), Such as initWithContentsOfURL: options: error: (NSData) and initWithContentsOfFile: encoding: error: (nsstrings) methods. In these cases, memory management rules are also followed. When you call these methods, you do not create the NSError object, so you do not hold it and do not need to release it, as shown in the following example:

    NSString *fileName = <#Get a file name#>;
    NSError *error;
    NSString *string = [[NSString alloc] initWithContentsOfFile:fileName
                            encoding:NSUTF8StringEncoding error:&error];
    if (string == nil) {
        // Deal with error...
    }
    // ...
    [string release];
Copy the code

Implement dealloc to relinquish ownership of the object

The NSObject class defines a dealloc method that is automatically called by the system when an object has no owner (RC=0) and its memory is reclaimed — called Freed or Deallocated in Cocoa terminology. The dealloc method destroys the object’s own memory and frees any resources it holds, including ownership of any instance variables. Here is an example of implementing the Dealloc method in the Person class:

@interface Person : NSObject
@property (retain) NSString *firstName;
@property (retain) NSString *lastName;
@property (assign.readonly) NSString *fullName;
@end
 
@implementation Person
// ...
- (void)dealloc
    [_firstName release];
    [_lastName release];
    [super dealloc];
}

Copy the code

Note: Do not call the method of another object dealloc directly; You must call [super Dealloc] at the end of the implementation;

You should not tie the management of system resources to the object lifecycle. See the section Don’t Use Dealloc to Manage Scarce Resources.

When the application terminates, no dealloc message may be sent to the object. Because the memory of a process is cleared automatically upon exit, it is more efficient to have the operating system clean up resources than to call the dealloc method on all objects.

Using accessor methods makes memory management easier

If a class has an object type attribute, you must ensure that the object assigned by that attribute is not released during use. Therefore, when assigning an object, you must take ownership of the object and increment its reference count by one. You must also decrease the reference count of the currently held old objects by one. It may seem tedious or tedious at times, but if you always use accessor methods, you are much less likely to have problems with memory management. If you use retain and release on instance variables throughout your code, this is definitely the wrong thing to do. The following defines an NSNumber object property in the Counter class.

@interface Counter : NSObject
@property (nonatomic.retain) NSNumber *count;
@end;
Copy the code

@property automatically generates declarations of setter and getter methods, and in general, you should use @synthesize to get the compiler to synthesize methods. But it is useful to know the implementation of accessor methods.

At sign, the synthesize automatically generates the implementation of the setter and getter methods and the underline instance variables, but if the setter and getter sides are overridden, no underline strength variables are generated. Note this;

Getter methods only need to return synthesized instance variables, so retain and release are not required.

- (NSNumber *)count {
    return _count;
}
Copy the code

In setter methods, if everyone else is following the same rules, it is very likely that someone else will subtract one from the reference count of the new object newCount at any time, resulting in the newCount being destroyed, so you must retain it and increase its reference count by one. You must also release the old object to relinquish possession of it. So the new object is retained, the old object is released, and then the assignment is performed. In Objective-C it is possible to send messages to nil, and this returns nothing. So even if the first call, the _count variable is nil, it’s okay to release it. You can also see “Deep Insight Runtime (3) : Message Mechanism”.

Note: You must retain the new object before releasing the old one. If the order is reversed, if the old and new objects are the same object, an accident may occur that causes the object dealloc.

- (void)setCount:(NSNumber *)newCount {
    [newCount retain];
    [_count release];
    // Make the new assignment.
    _count = newCount;
}
Copy the code

This is the official Apple practice, which is slightly underperforming and involves unnecessary method calls if the old and new objects are the same object. A better approach is to determine whether the old and new objects are the same object, and if so, do nothing. If the old and new objects are not the same object, the old object is released, the new object is retained and assigned to the synthesized instance variable.

- (void)setCount:(NSNumber *)newCount {
    if(_count ! = newCount) { [_count release]; _count = [newCountretain]; }}Copy the code
  • Use accessor methods to set property values

Suppose we want to reset the count property above. There are two methods:

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [self setCount:zero];
    [zero release];
}
Copy the code
- (void)reset {
    NSNumber *zero = [NSNumber numberWithInteger:0];
    [self setCount:zero];
}
Copy the code

For simple cases, we could also manipulate the _count variable directly as follows, but sooner or later something goes wrong (for example, when you forget retain or release, or when the memory-management semantics of the instance variable (that is, the property key) change).

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [_count release];
    _count = zero;
}

Copy the code

Also note that if YOU use KVO, changing a variable in this way does not trigger the KVO listening method. For a more comprehensive summary of KVO, see iOS – Some summary of KVO.

  • Do not use accessor methods in initialization methods and dealloc

Instead of using accessor methods to set instance variables in initializer methods and dealloc, you should manipulate instance variables directly.

For example, when we initialize the Counter object, we initialize its count property. The correct approach is as follows:

- (instancetype)init {
    self = [super init];
    if (self) {
        _count = [[NSNumber alloc] initWithInteger:0];
    }
    return self;
}

Copy the code

Because the Counter class has instance variables, you must also implement the dealloc method. This method gives up ownership of any instance variables by sending them a release message, and finally calls the implementation of super:

- (void)dealloc {
    [_count release];
    [super dealloc];
}

Copy the code

That’s what Apple does officially. The recommended practice is to assign nil to _count after release.

Note: First of all, nil and release are used: nil sets the pointer to an object to null. Nil just breaks the connection between the pointer and the object in memory, and does not free the object’s memory. Release is the operation that actually frees the object’s memory. The reason we assign nil to _count after release is to prevent _count from being accessed again after it has been destroyed and causing Crash.

- (void)dealloc {
    [_count release];
    _count = nil;
    [super dealloc];
}
Copy the code

We can also use self.count = nil in dealloc; In one step, because it is usually equivalent to [_count release]; And _count = nil; Two-step operation. But try not to.

- (void)dealloc {
    self.count = nil;
    [super dealloc];
}
Copy the code

According to? Why do I need self = [super init] in my initialization method?

  • So let’s explain self and super in a nutshell. Self is the object pointer to the current message receiver. Super is a compiler directive that calls a method using super to find the implementation of the method from the parent class of the current message receiver class, but the message receiver is still a subclass. A more detailed explanation of self and super can be found in “The Essence of Super”.

  • To call [super init], a subclass calls the init method of its superclass, completing the initialization of the superclass first. Note whether self or subclass is in the init method of the parent class during the call.

  • Self = [super init], if the parent class is successfully initialized, then the subclass is initialized. If the parent class fails to initialize, [super init] returns nil and assigns to self, then the contents of the if (self) statement are not executed, and the subclass’s init method returns nil. Doing so prevents an unusable object from being returned because the parent class initialization failed. If you don’t, you may end up with an unavailable object that behaves unpredictably and may eventually cause your program to Crash.

According to? Why not use accessor methods in initialization methods and dealloc?

  • In the initializer method and dealloc, the existence of the object is uncertain, and it may not be fully initialized, so sending a message to the object may not succeed or cause some problems.
  1. To further explain, suppose we use setter methods in init to initialize instance variables. In init, we call self = [super init] to initialize the stuff of the superclass first, that is, the subclass calls the init method of the superclass first (note:

Is self or subclass object in the init method of the parent class called). If a setter method is used in init of the parent class to initialize instance variables, and the subclass overrides that setter method, then the subclass’s setter method is called when the parent class is initialized. At this point, the parent class is only being initialized, and the subclass initialization is not complete, so errors may occur. 2. When a subclass object is destroyed, the subclass’s dealloc is called first and then [super Dealloc] is called (as opposed to init). If a setter method is called in the parent class’s dealloc and that method is overridden by the subclass, the setter method of the subclass is called, but the subclass has already been destroyed, so this can also be an error. 3. In Effective Objective-C 2.0, 52 Effective Ways to Write High Quality iOS and OS X code, dealloc releases only references and disallows listening: Do not call attribute access methods in dealloc, because someone might overwrite these methods and do things in them that cannot be safely done in the recycle phase. In addition, the property may be under the key-value Observation (KVO) mechanism, and observers of the property may “retain” or use the soon-to-be-reclaimed object if the property Value changes. This can throw the state of the run-time system completely out of whack, leading to inexplicable errors. 4. In summary, the cause of the error is usually caused by inheritance and subclass overriding accessor methods. When you use accessor methods in initializer methods and dealloc, you are likely to get an error if there is inheritance and subclasses override the accessor method and do something else in the method. While it is possible to avoid errors by not meeting all of the above conditions, it is better to write code in a standard way to avoid errors.

  • Performance deteriorates. In particular, if the attribute is atomic.
  • Side effects are possible. Using KVO triggers KVO and so on.

However, we must make exceptions in some cases. Such as:

  • If the instance variable to be initialized is declared in the parent class and we do not have access to the instance variable in the child class, then we can only assign to the instance variable through setters in the initializer method.

Use weak references to avoid Retain Cycles

The retain object creates a strong reference (reference count +1) to the object. An object does not dealloc until it has released all its strong references (that is, the reference count =0). If two objects retain strong references to each other, or multiple objects, each object forcibly references the next object until it goes back to the first, then “retain Cycles” will occur. Circular references cause none of the objects in them to dealloc, creating a memory leak. For example, the Document object has a property Page object, and each Page object has a property that stores the Document in which it resides. If a Document object has a strong reference to a Page object, and a Page object has a strong reference to a Document object, neither of them can be destroyed. The solution to the “Retain Cycles” problem is to use weak references. Weak references are non-holding, and the object does not retain the object it references.

However, in order to keep the object graph intact, there must be strong references somewhere. (If there are only weak references, the Page and Paragraph objects may not have any owners and will therefore be destroyed.) Thus, Cocoa established a convention that a parent object should retain a strong reference to its children, and a child object should retain a weak reference to its parent (do not retain). Therefore, the Document object has a strong reference to its Page object, but the Page object is a weak reference to the Document object, as shown below:Examples of weak references in Cocoa include but are not limited totable data sources,outline view items,notification observersAs well as othertargetsdelegates.

Be careful when you send messages to objects that hold only weak references. Crash if you send a message to an object after it has been destroyed. You have to define when objects are valid. In most cases, a weak reference object is aware of weak references to it by other objects, as in the case of circular references, and you are responsible for notifying the other objects when the weak reference object is destroyed. For example, when you register an object with the notification center, the notification center stores a weak reference to the object and sends a message to it when the corresponding notification is published. When an object is about to be destroyed, you need to unregister it in the notification center to prevent the notification center from sending messages to the destroyed object. Also, when the Delegate object is destroyed, you need to send the setDelegate: nil message to the delegate object to remove the delegate reference. These messages are typically sent in the object’s dealloc method.

Avoid causing the object you are using to be destroyed

Cocoa’s ownership policy specifies that an object is passed in as a method parameter that remains valid for the entire scope of the called method and can also be returned as a method return value without fear of it being released. It is not important to the application that the getter method of an object returns cached instance variables or computed values. The important thing is that the object remains valid for as long as you need it.

There are occasional exceptions to this rule, which fall into two main categories.

  1. When an object is deleted from a base collection class.
    heisenObject = [array objectAtIndex:n];
    [array removeObjectAtIndex:n];
    // heisenObject could now be invalid.
Copy the code

When an object is removed from a base collection class, it is sent a Release (instead of an AutoRelease) message. If the collection is the sole owner of the removed object, the removed object (heisenObject in the example) is destroyed immediately.

  1. When the parent object is destroyed.
    id parent = <#create a parent object#>;
    // ...
    heisenObject = [parent child] ;
    [parent release]; // Or, for example: self.parent = nil;
    // heisenObject could now be invalid.
Copy the code

In some cases, you get the child from the parent and release the parent directly or indirectly. If releasing the parent causes it to be destroyed, and the parent is the sole owner of the child, the child object (heisenObject in the example) will be destroyed at the same time (assuming that in the parent object’s dealloc method, the child object is sent a release instead of an AutoRelease message). To prevent these situations, retain a heisenObject when it is acquired and release it after completion. Such as:

    heisenObject = [[array objectAtIndex:n] retain];
    [array removeObjectAtIndex:n];
    // Use heisenObject...
    [heisenObject release];
Copy the code

Do not use Dealloc to manage scarce resources

You should not normally manage scarce resources such as file descriptors, network connections, and buffers or caches in the dealloc method. In particular, you should not design classes to call Dealloc whenever you want the system to call it. Dealloc calls may be delayed or not called due to bugs or application crashes.

Conversely, if you have an instance of a class that manages scarce resources, you should ask that instance to release those resources when you no longer need them. And then, you usually release that instance, and then it dealloc. If the dealloc of this instance is not called or not called in time, you will not have the problem of scarce resources not being released or not being released because you have already released resources. Problems can occur if you try to manage resources on Dealloc. Such as:

  1. Dependent object graph release mechanism.

The release mechanism for object graphs is inherently unordered. Although you usually want to be able to release in a particular order, it makes your program vulnerable. If the object is autoreleased instead of released, the release order may change, which can lead to unexpected results.

  1. Not recycling scarce resources.

Memory leaks are bugs that should be fixed, but they are usually not immediately fatal. However, if you don’t release scarce resources when you want to, you may have a more serious problem. For example, if your application runs out of file descriptors, the user may not be able to save data.

  1. The resource release operation was performed by the wrong thread.

If an object calls autoRelease at an unexpected time, it will be released in the autorelease pool block of any thread it happens to enter. This can easily be fatal for resources that can only be reached from one thread.

Collections hold the objects they contain

When you add an object to a collection (such as array, Dictionary, or set), the collection takes ownership of the object. When an object is removed from the collection or the collection itself is destroyed, the collection relinquishes ownership of the object. So, for example, if you want to create an array that stores numbers, you can do either of the following:

    NSMutableArray *array = <#Get a mutable array#>;
    NSUInteger i;
    // ...
    for (i = 0; i < 10; i++) {
        NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];
        [array addObject:convenienceNumber];
    }
Copy the code

In this case, the NSNumber object is not created by alloc, etc., so there is no need to call release. There is also no need to retain the NSNumber object, as arrays do.

    NSMutableArray *array = <#Get a mutable array#>;
    NSUInteger i;
    // ...
    for (i = 0; i < 10; i++) {
        NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];
        [array addObject:allocedNumber];
        [allocedNumber release];
    }
Copy the code

In that case, you need to release the NSNumber object. The array retains the NSNumber object on addObject:, so it is not destroyed in the array.

To understand this, put yourself in the shoes of the person implementing the collection class. You want to make sure that they are not destroyed in the collection, so you send them a retain message when they are added to the collection. If they are deleted, they must be sent a release message. In the collection’s dealloc method, a Release message should be sent to all remaining objects in the collection.

The ownership policy is implemented using Retain Counts

Ownership policies are implemented through reference counting, also known as “retain count.” Each object has a retain count.

  • When an object is created, its retain count is 1.
  • When a retain message is sent to an object, its retain count is +1.
  • When a release message is sent to an object, its retain count will be -1.
  • When an autorelease message is sent to an object, its Retain Count is -1 at the end of the current automatic pool block release.
  • If the object’s retain count is reduced to 0, it dealloc.

Important: You should not explicitly ask what the retain count of an object is. The results are often misleading because you may not know which system framework objects retain the objects you care about. When debugging memory management problems, you just need to follow the memory management rules.

Note: For detailed implementations of these methods, see “iOS – Cliche Memory Management (iv) : Source Code Analysis memory Management Methods.”

Core Foundation uses similar but different rules

Core Foundation objects have similar memory management rules (see The Core Foundation Memory Management Programming Guide). However, Cocoa and Core Foundation have different naming conventions. In particular, Core Foundation’s rules for creating objects (see The Create Rule).

(4) What is the role of ARC, what are the new rules of ARC, and what problems ARC will bring?

ARC works by adding code at compile time to ensure that objects will survive as long as necessary, but not all the time. Conceptually, it follows the same memory management rules as MRC by adding the appropriate memory management method calls for you.

In order for the compiler to generate the correct code, ARC restricts the use of some methods and how you can use toll-free Bridging, see Managing Toll-free Bridging. ARC also introduces new life cycle modifiers for object references and property declarations.

ARC is supported in Xcode 4.2 for OS X V10.6 and V10.7 (64-bit applications) as well as iOS 4 and iOS 5 apps. However, OS X V10.6 and iOS 4 do not support weak references. Xcode provides a migration tool that automatically converts MRC code To ARC code (such as removing retain and release calls) without creating a project from scratch (choose Edit > Convert > To Objective-C ARC). The migration tool will convert all files in the project to ARC mode. If it is more convenient to use MRC for some files, you can choose to use ARC only for some files.

ARC analyzes the lifetime requirements of the object and automatically inserts the code for the appropriate memory management method calls at compile time without requiring you to remember when to use the retain, release, autorelease methods. The compiler will also generate the appropriate dealloc method for you. In general, if you use ARC, the traditional Cocoa naming convention is only important if you need to interact with code that uses MRC.

New rules of the ARC

ARC introduces some new rules that do not exist when using other compiler patterns. These rules are designed to provide a completely reliable memory management model. Sometimes they lead directly to best practices, and sometimes they simplify code and solve memory management problems when you’re not even paying attention to them. The following rules must be followed under ARC, and if they are violated, a compilation error will occur.

  • Cannot use retain/release/retainCount/autorelease
  • NSAllocateObject/NSDeallocateObject cannot be used
  • The method naming conventions for memory management must be followed
  • Dealloc cannot be explicitly called
  • Use the @Autoreleasepool block instead of NSAutoreleasePool
  • Unusable areas (NSZone)
  • Object variables cannot be members of C struct/union
  • Explicitly convert “id” and “void *” — bridge

Cannot use retain/release/retainCount/autorelease

Under ARC, it is forbidden for developers to manually call these methods, and also to use @selector(retain), @selector(release), etc., otherwise the compilation will not pass. But you can still use the related functions CFRetain, CFRelease, and so on for Core Foundation objects (see Managing Toll-Free Bridging).

NSAllocateObject/NSDeallocateObject cannot be used

Under ARC, it is forbidden for developers to call these functions manually, otherwise the compilation will fail. You can create objects using alloc, and the Runtime takes care of the dealloc object.

The method naming conventions for memory management must be followed

Under MRC, creating an object with the alloc/new/copy/mutableCopy method holds the object directly. We define a “create and hold object” method that starts with alloc/new/copy/mutableCopy. And it must return the object that the caller should hold. These rules should also be followed if you need to interact with code using MRC in ARC. To allow interaction with MRC code, ARC imposes a constraint on method naming: accessor method names cannot begin with new. This means that you cannot declare a property whose name begins with new unless you specify a different getterName:

// Won't work:
@property NSString *newTitle;
// Works:
@property (getter = theNewTitle) NSString *newTitle;
Copy the code

Dealloc cannot be explicitly called

Under both MRC and ARC, the dealloc method is automatically called when the object reference count is zero. In most cases, we remove notification or observer objects and so on in the dealloc method. Under MRC, we can call dealloc manually. However, under ARC, this is disabled, otherwise the compilation will not pass. Under MRC, we implement dealloc and must call [super Dealloc] at the end of the implementation.

// MRC
- (void)dealloc
{
    // Other processing
    [super dealloc];
}
Copy the code

Under ARC, ARC will handle this automatically, so we don’t have to also disallow writing [super dealloc], otherwise the compilation will fail.

// ARC
- (void)dealloc
{
    // Other processing
    [super dealloc]; ARC Forbids Explicit message Send of 'dealloc'
}
Copy the code

Use the @Autoreleasepool block instead of NSAutoreleasePool

Under ARC, @autoreleasepool should be used for the autoreleasepool pool. NSAutoreleasePool is not allowed, otherwise the compilation will fail.

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
// Error: 'NSAutoreleasePool' is unavailable: Not available in automatic reference counting mode
Copy the code

See iOS – Talk about AutoRelease and @Autoreleasepool for more on how @Autoreleasepool works

Unusable areas (NSZone)

For current runtime systems (where the compiler macro __ OBJC2 __ is set), nszones are simply ignored under both MRC and ARC.

Object variables cannot be members of C struct/union

The presence of objective-C object type variables in C struct/union members causes compilation errors.

Note: Xcode10 starts to support referencing Objective-C objects in C structs in ARC mode. You could have used Objective-C++ before.

struct Data {
    NSMutableArray *mArray;
};
// error: ARC Forbids Objective-c objs in struct or unions NSMutableArray *mArray;
Copy the code

Although it is the LLVM compiler 3.0, there is no way in the C language specification to manage the lifetime of structure members anyway. Because ARC assigns memory management to the compiler, the compiler must be able to know and manage the lifetime of objects. For example, automatic variables (local variables) in C can manage objects using the scope of that variable. However, for C language structure members, this is not possible by the standard. Therefore, members of the object type in the structure must be released before the structure is released, but the compiler cannot reliably do this, so object variables cannot be considered members of the C language structure. There are three solutions to this problem:

① Use Objective-C objects instead of constructs. This is the best solution. If you insist on using a structure and adding object variables to its members, there are two options:

② Cast Objective-C objects to void * via Toll-free Bridging. See Managing Toll-Free Bridging.

③ Add the __unsafe_unretained modifier to objective-C.

struct Data {
    NSMutableArray __unsafe_unretained *mArray;
};
Copy the code

A variable with the __unsafe_unretained modifier is not part of the compiler’s memory management object. If you manage without paying attention to the owner of an assignment object, you may experience a memory leak or a program crash. More attention should be paid to this in use.

struct x { NSString * __unsafe_unretained S; int X; }
Copy the code

__unsafe_unretained pointer is not safe after an object is destroyed, but it is useful for objects such as string constants that are determined to be persistent from the start.

Cannot explicitly convert ‘id’ and ‘void *’

Under MRC, we can cast directly between id and void * variables.

    id obj = [[NSObject alloc] init];
    void *p = obj;
    id o = p;
    [o release];
Copy the code

In ARC, however, this can cause a compilation error: Converting between the Objective-C pointer type ID and the C pointer type void * requires toll-free Bridging. See Managing Toll-free Bridging.

    id obj = [[NSObject alloc] init];
    void *p = obj; // error: Implicit conversion of objective-C pointer type 'id' to C pointer type 'void *' requires a bridged cast
    id o = p;      // error: Implicit conversion of C pointer type 'void *' to Objective-C pointer type 'id' requires a bridged cast
    [o release];   // error: 'release' is unavailable: Not available in automatic reference counting mode
Copy the code

Ownership modifier

ARC introduces several new lifecycle modifiers for objects (we call them “ownership modifiers”) as well as weak reference capabilities. Weak references weak do not extend the life of the object to which it points, and the object is automatically set to nil if there are no strong references (that is, dealloc).

You should use these modifiers to manage object diagrams in your program. In particular, ARC does not prevent strong reference Cycles (formerly known as Retain Cycles, see starting with MRC — Using weak References to avoid Retain Cycles). Using weak references wisely will help ensure that you don’t create circular references.

In ARC, variables of object types are accompanied by ownership modifiers, of which there are four.

__strong
__weak
__unsafe_unretained
__autoreleasing
Copy the code
  • __strong is the default modifier. Objects remain alive as long as there is a strong pointer to them.
  • __weak Specifies a reference that does not keep the reference object alive. When an object has no strong references, weak references are automatically set to nil.
  • __unsafe_unretained specifies a reference that does not keep the referenced object alive. If an object does not have a strong reference, it is not set to nil. If the object it references is destroyed, a dangling pointer is produced.
  • __autoreleasing is used to represent parameters that are passed in by reference (ID *) and released automatically on return (autorelease).

When using ownership modifiers in declarations of object variables, the correct format is:

  ClassName * qualifier variableName;
Copy the code

Such as:

MyClass * __weak myWeakReference;
MyClass * __unsafe_unretained myUnsafeReference;
Copy the code

Other formats are technically incorrect, but the compiler “forgives” them. In other words, this is the standard way.

__strong

The __strong modifier is a strong reference and holds the object to a +1 reference count. This modifier is the default modifier for an object type variable. If we do not explicitly specify an ownership modifier for an object type variable, it defaults to the __strong modifier.

id obj = [NSObject alloc] init];
// -> id __strong obj = [NSObject alloc] init];
Copy the code

__weak

If you rely solely on __strong for memory management, you will inevitably have memory leaks caused by circular references, and __weak will solve the problem.

The __weak modifier is a weak reference, does not hold the object, and its reference count is not increased. __weak can be used to prevent circular references.

Using the __weak modifier alone, the compiler will warn you because instances of NSObject are created without strong references and will be released immediately.

id __weak weakObj = [[NSObject alloc] init]; // ⚠️ core exchange retained object to weak variable; object will be released after assignment
NSLog(@ "% @", obj);
// (null)
Copy the code

The following instances of NSObject are strongly referenced, and variables assigned to the __weak modifier will not be warned.

id __strong strongObj = [[NSObject alloc] init];
id __weak weakObj = strongObj;
Copy the code

When an object is dealloc, the __weak variable pointing to the object is assigned nil. (Specific execution process can be seen: “iOS – Cliche memory management (four) : source analysis of memory management methods”)

Note: __weak can only be used in ARC. In MRC, the __unsafe_unretained modifier is used instead.

__unsafe_unretained

The __unsafe_unretained modifier is as its name implies, insecure and doesn’t hold objects.

Note: Although ARC memory management is the compiler’s job, variables with the __unsafe_unretained modifier are not part of the compiler’s memory management object. Be careful when using this.

The “won’t hold object” feature makes it similar to __weak, preventing circular references. The “insecure” feature is what distinguishes it from __weak, so what is insecure about it? Let’s look at the code:

    id __weak weakObj = nil;
    id __unsafe_unretained uuObj = nil;
    {
        id __strong strongObj = [[NSObject alloc] init];
        weakObj = strongObj;
        unsafeUnretainedObj = strongObj;
        NSLog(@"strongObj:%@", strongObj);
        NSLog(@"weakObj:%@", weakObj);
        NSLog(@"unsafeUnretainedObj:%@", unsafeUnretainedObj);
    }
    NSLog(@"-----obj dealloc-----");
    NSLog(@"weakObj:%@", weakObj);
    NSLog(@"unsafeUnretainedObj:%@", unsafeUnretainedObj); // Crash:EXC_BAD_ACCESS

/* strongObj:
      
        weakObj:
       
         unsafeUnretainedObj:
        
          -----obj dealloc----- weakObj:(null) (lldb) */
        
       
      
Copy the code

The above code crashed. The reason for this is that an __unsafe_unretained modifier pointer points back to the same address even after being destroyed. We call this a “dangling pointer.” If you continue to access the original object through Pointers, the Crash will occur. When an __weak object is freed, all __weak pointer variables to the object are set to nil. This is why __unsafe_unretained is unsafe. So, when you use the __unsafe_unretained modifier to modify an object, you need to ensure that it is not destroyed.

Q: __weak is safer, so why retained __unsafe_unretained?

  • __weak can only be used in ARC, and __unsafe_unretained in MRC.

  • __unsafe_unretained mainly interacts with C code;

  • __weak has a certain consumption on performance. When an object is dealloc, we need to traverse the weak table of the object and set the values of all the weak Pointers in the table to nil. The more weak Pointers to the object, the more performance consumption. So __unsafe_unretained is faster than __weak. Choosing __unsafe_unretained provides some performance benefits when you explicitly know the life cycle of an object.

A holds object B, and B is destroyed when A is destroyed. So when B exists, A must exist. But if B needs to call A’s interface, B can store A’s __unsafe_unretained pointer. For example, MyViewController holds MyView, and MyView needs to call the interface of MyViewController. __unsafe_unretained MyViewController *_viewController can be stored across MyView.

The performance improvement is tiny, though. But __unsafe_unretained is safe as well, so it can be retained quickly. When the situation is uncertain, __weak should be preferred.

__autoreleasing

Automatic release tank

The NSAutoreleasePool class has been deprecated from ARC and is now used as @autoreleasepool instead.

You can use NSAutoreleasePool or @Autoreleasepool in MRC. Use @autoreleasepool, which apple says is about six times faster than NSAutoreleasePool.

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Code benefitting from a local autorelease pool.
[pool release]; // [pool drain]
Copy the code

Q: Objective-c itself supports GC, but it has platform limitations and is limited to MacOS development. IOS development uses RC. On iOS RC, [Pool Release] has the same effect as [Pool drain], but in GC drain triggers GC and release does nothing. It is better to use [pool drain] because it is more compatible with the system and because it is different from normal object releases. (Note: Apple has deprecated GC in OS X Mountain Lion V10.8, using ARC instead.)

Only @autoreleasepool can be used in ARC.

@autoreleasepool {
    // Code benefitting from a local autorelease pool.
}
Copy the code

For the underlying principles of @Autoreleasepool, see iOS – Talk about AutoRelease and @Autoreleasepool.

__autoreleasing use

In MRC we can register an object with autoReleasepool by sending it an AutoRelease message. While autorelease is disabled in ARC, we can use the __autoreleasing modifier to modify objects to register them in Autoreleasepool.

@autoreleasepool {
    id __autoreleasing obj = [[NSObject alloc] init];
}
Copy the code

The above code in MRC is equivalent to:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
/ / or
@autoreleasepool {
    id obj = [[NSObject alloc] init];
    [obj autorelease];
}
Copy the code
__autoreleasing is the default modifier for the secondary pointer type

As we said earlier, the default ownership modifier for object Pointers is __strong. The default ownership modifier for second-level pointer types (ClassName ** or ID *) is __autoreleasing. If we do not explicitly specify an ownership modifier for the second-level pointer type, the __autoreleasing modifier is appended by default.

For example, we often use NSError to print error messages during development, and we usually pass Pointers to NSError objects in method arguments. The NSString stringWithContentsOfFile method, for example, uses the __autoreleasing modifier for the NSError ** argument.

NSString *str = [NSString stringWithContentsOfFile:<#(nonnull NSString *)#>
                                          encoding:<#(NSStringEncoding)#> 
                                          error:<#(NSError *__autoreleasing  _Nullable * _Nullable)#>];
Copy the code

Example: We declare a method with an argument of NSError **, but do not specify its ownership modifier.

- (BOOL)performOperationWithError:(NSError **)error;
Copy the code

We then try to call the method and find that the NSError ** argument in the smart prompt has an __autoreleasing modifier attached. As you can see, if we do not explicitly specify an ownership modifier for the second-level pointer type, the __autoreleasing modifier is appended by default.

NSError *error = nil;
BOOL result = [self performOperationWithError:<#(NSError *__autoreleasing *)#>];
Copy the code

Note It is important to note that the ownership modifier must be consistent when assigning to a second-level pointer type, otherwise a compilation error will occur.

NSError *error = nil;
NSError **error1 = &error;                 // error: Pointer to non-const type 'NSError *' with no explicit ownersh
NSError *__autoreleasing *error2 = &error; / / error: Initializing 'NSError *__autoreleasing *' with an expression of type 'NSError *__strong *' changes retain/release properties of pointer
NSError* __weak *error3 = &error;          / / error: Initializing 'NSError *__weak *' with an expression of type 'NSError *__strong *' changes retain/release properties of pointer
NSError* __strong *error4 = &error;        // The compiler passes
Copy the code
NSError* __weak error = nil;
NSError* __weak *error1 = &error;          // The compiler passes
Copy the code
NSError *__autoreleasing error = nil;
NSError *__autoreleasing *error1 = &error; // The compiler passes
Copy the code

As we said earlier, the default modifier for the second-level pointer type is __autoreleasing. So why do we call a method with an __strong modifier and get it compiled?

NSError* __strong error = nil;
BOOL result = [self performOperationWithError:<#(NSError *__autoreleasing *)#>];
Copy the code

In fact, the compiler automatically converts our code to the following form:

NSError* __strong error = nil;
NSError *__autoreleasing tmp = error;
BOOL result = [self performOperationWithError:&tmp];
error = tmp;
Copy the code

As you can see, mismatches between local variable declarations (__strong) and arguments (__autoreleasing) cause the compiler to create temporary variables. You can explicitly specify local variable ownership modifier __autoreleasing or parameter ownership modifier __strong to prevent the compiler from creating temporary variables.

(BOOL)performOperationWithError:(NSError* __strong *)error;
Copy the code

But in the MRC reference counting memory management rules: use alloc/new/copy/mutableCopy method to create objects, such as creating and hold objects; Other cases create objects but do not hold them. In order to follow this rule, we should specify __autoRELEASING as the second-level pointer type parameter modifier when obtaining objects with arguments.

As mentioned in the section “Starting with MRC — You Don’t Hold Objects returned by Reference”, some methods in Cocoa specify objects returned by reference (that is, they take arguments of type ClassName ** or ID *). The most common is to use NSError objects. When you call these methods, you don’t create the NSError object, so you don’t hold it and you don’t have to release it. __strong represents holding objects, so __autoreleasing should be used.

In addition, when we display the __autoreleasing modifier, we must be careful that the object variables are automatic (including local variables, function and method parameters), otherwise the compilation will not pass.

static NSError __autoreleasing *error = nil; // Global variables cannot have __autoreleasing ownership
Copy the code

Property Memory management keyword

Speaking of properties, we have to mention at sign synthesize and at sign dynamic. @ synthesize and @ dynamic

@property: helps us automatically generate declarations of setter and getter methods for properties. @synthesize: Automatically generates the implementation of setter and getter methods and underline member variables.

It used to be that we had to manually add the @synthesize to every @property, but after iOS 6 the LLVM compiler introduced “Property Autosynthesis”, which means automatic property synthesis. In other words, the compiler automatically adds the at sign synthesize to every at sign property.

Q: What does @synthesize do now? If we override both setter and getter methods, then the compiler won’t add the @synthesize to the @property, and there’s no underline member variable, so we need to manually add the @synthesize.

@synthesize propertyName = _propertyName;
Copy the code

Sometimes we don’t want the compiler to do the at sign synthesize for us, so we want to decide on the implementation of the property access method while the program is running, so we use at sign Dynamic.

@dynamic: Tells the compiler not to automatically synthesize the @synthesize and to wait until run time to add the method implementation, but it does not affect the declaration of setter and getter methods generated by @property. @dynamic is an expression of OC as a dynamic runtime language. Dynamic runtime languages differ from compile-time languages: dynamic runtime languages defer function decisions to runtime, while compile-time languages make function decisions at the compiler.

@dynamic propertyName;
Copy the code

Property “Memory Management” keyword corresponds to the ownership modifier:For more information about attribute keywords, seeOC – Property Keywords and Ownership Modifiers.

Avoiding the circular reference problem

Delegate avoids circular references

Delegate avoids circular references by using the weak keyword when the delegate property is declared by the delegate.

@property (nonatomic.weak) id<protocolName> delegate;
Copy the code

Blocks avoid circular references

Q: Why do blocks make circular references? Loop references: If the current block captures a member variable of the current object, it may make a strong reference to it. According to the block’s variable capture mechanism, if a block is copied to the heap and captured as an auto variable of object type, it is captured along with its ownership modifier, so the block makes a strong reference to the object if it is __strong (not if the block is on the stack). While the current block may have a strong reference to the current object, resulting in the problem of circular reference; ② Large ring references: If we use __block, we may generate circular references under ARC (MRC does not). Since __block modifiers wrap variables as objects, strong references to __block variables are generated if blocks are copied to the heap, and strong or weak references are generated if __block modifiers modify objects based on the object’s ownership modifier. If the object is __strong (such as __block ID X), then the __block variable makes a strong reference to it (not under MRC). If the object has a strong reference to the block, then the problem of large ring references arises. Loop references can be broken in ARC, and Pointers can be set to nil in blocks (MRC doesn’t loop references, so don’t fix this). The downside, however, is that if the block is never called, the circular reference will always exist.

ARC solution:

  • Method 1: Use __weak or __unsafe_unretained.
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"%p",weakSelf);
    };
Copy the code
    __unsafe_unretained id uuSelf = self;
    self.block = ^{
        NSLog(@"%p",uuSelf);
    };

Copy the code

For non-Trivial Cycles, we need to do this:

    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if(! strongSelf)return;
        NSLog(@"%p",weakSelf);
    };
Copy the code

  • __block (block must be called) :

Cons: Block must be called and pointer set to nil in block. If a block is never called, the object will remain in memory forever, causing a memory leak.

    __block id blockSelf = self;
    self.block = ^{
        NSLog(@"%p",blockSelf);
        blockSelf = nil;
    };
    self.block();
Copy the code

Solutions under MRC:

__unsafe_unretained: Same as ARC. __block (MRC uses __block to modify object types. No object is retained within blocks, so loop references can be resolved with __block.)

	__block id blockSelf = self;
	self.block = ^{
	    NSLog(@"%p",blockSelf);
	};
Copy the code

For more information about blocks, see the OC-Block Explanation.

ARC understack variable initialized to nil

Stack variables using ARC, strong, weak, and autoreleasing will now be initialized to nil by default. Such as:

- (void)myMethod {
    NSString *name;
    NSLog(@"name: %@", name);
}
Copy the code

Print the value of name as null instead of program Crash.

Enable and disable ARC using the compiler flag

Enable ARC with the -fobjc-ARC compiler flag. If it’s more convenient for you to use MRC for certain files, you can use ARC only for certain files. For projects that use ARC as the default, you can disable ARC for specified files using the -fno-objc-ARC compiler flag. As shown below:

(5) How to convert objects managed by ARC and MRC?

Toll-free Bridging

Core Foundation-style objects may be used in your project, either from the Core Foundation framework or from other frameworks that follow the Core Foundation convention standards, such as Core Graphics.

The compiler does not automatically manage the life cycle of Core Foundation objects; you must call CFRetain and CFRelease according to Core Foundation memory management rules. See The Memory Management Programming Guide for Core Foundation.

Under MRC, we can cast directly between objective-C pointer type ID and C pointer type void *, such as Foundation objects and Core Foundation objects. Since the memory is managed manually, there is no need to worry about the transfer of memory management rights.

Converting Foundation objects to Core Foundation objects under ARC requires toll-free Bridging (Bridging) to tell the compiler the ownership semantics of the objects. __bridge, __bridge_retained, and __bridge_transfer should be used to resolve the memory transfer problem. The differences are as follows:

__bridge (common) : does not change the memory management ownership of the object.

Foundation objects managed by ARC continue to be managed by ARC after being converted into Core Foundation objects. Core Foundation objects that were managed manually by developers continue to be managed manually by developers after being converted into Foundation objects. The following uses the NSMutableArray and CFMutableArrayRef objects as examples:

// Originally managed by ARC
NSMutableArray *mArray = [[NSMutableArray alloc] init];
// Continue to be managed by ARC after conversion
CFMutableArrayRef cfMArray = (__bridge CFMutableArrayRef)(mArray); 

// It was managed manually by the developer
CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0.NULL); 
// The conversion continues to be managed manually by the developer
NSMutableArray *mArray = (__bridge NSMutableArray*)(cfMArray); .// The object needs to be released manually when it is no longer needed
CFRelease(cfMArray); 
Copy the code

__bridge is similar or even less secure than the __unsafe_unretained modifier. If used inappropriately and without careful object release, a dangling pointer can cause Crash.

The __bridge conversion does not change the reference count of the object. For example, if we convert the id type to void * and the object is destroyed before we use void *, we will Crash the object if we use void * again. So the void * pointer is recommended for immediate use, and __bridge_retain is recommended if we want to save the void * pointer for later use.

When using __bridge to convert void * to ID, remember that the memory management of the object is still managed manually by the developer. Remember to release the object when it is no longer needed, otherwise memory leaks!

Here are some examples of “converting void * to ID using __bridge”. Note that memory is managed manually by the developer after the conversion, so the object remains in memory even when out of scope.

/ / use __strong
CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0.NULL);
NSMutableArray *mArray = (__bridge NSMutableArray *)(cfMArray);

NSLog(@"%ld".CFGetRetainCount(cfMArray));     // 2, since mArray is __strong, increase the reference count
NSLog(@"%ld", _objc_rootRetainCount(mArray));  // 1, but the _objc_rootRetainCount prints 1?
 
// Release the object when it is no longer needed
CFRelease(cfMArray); 
NSLog(@"%ld".CFGetRetainCount(cfMArray));     // 1, because __strong is still in scope and strong Pointers are referenced
NSLog(@"%ld", _objc_rootRetainCount(mArray));  / / 1

// The object can be accessed until the __strong scope ends
// When the __strong scope ends, the object will be destroyed and will crash if accessed again
Copy the code
/ / use __strong
CFMutableArrayRef cfMArray;
{
    cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0.NULL);
    NSMutableArray *mArray = (__bridge NSMutableArray *)(cfMArray);
    NSLog(@"%ld".CFGetRetainCount(cfMArray));  // 2, since mArray is __strong, increase the reference count
}
NSLog(@"%ld".CFGetRetainCount(cfMArray));      // 1, the __strong scope ends
CFRelease(cfMArray); // Release objects, otherwise memory leaks
// You can print CF objects using the CFShow function
CFShow(cfMArray);    // The next access will crash
Copy the code
/ / use __weak
CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0.NULL);
NSMutableArray __weak *mArray = (__bridge NSMutableArray *)(cfMArray);

NSLog(@"%ld".CFGetRetainCount(cfMArray));     // 1, because mArray is __weak, the reference count is not increased
NSLog(@"%ld", _objc_rootRetainCount(mArray));  / / 1

/* * Use mArray */

// Release the object when it is no longer needed
CFRelease(cfMArray);
NSLog(@ "% @",mArray);  // nil, this is the advantage of using __weak. The pointer is automatically set to nil when the object is destroyed, and it will not crash if accessed again
Copy the code
/ / use __weak
NSMutableArray __weak *mArray;
{
    CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0.NULL);
    mArray = (__bridge NSMutableArray *)(cfMArray);
    NSLog(@"%ld".CFGetRetainCount(cfMArray));  // 1, because mArray is __weak, the reference count is not increased
}
CFMutableArrayRef cfMArray =  (__bridge CFMutableArrayRef)(mArray);
NSLog(@"%ld".CFGetRetainCount(cfMArray)); // if the object is out of scope, it is not released yet, because memory management is in our hands

CFRelease(cfMArray); // Release objects, otherwise memory leaks
Copy the code

__bridge_retained: Used to deny ARC memory management rights when Foundation objects are converted to Core Foundation objects.

After converting a Foundation object that was managed by ARC to a Core Foundation object, ARC will no longer manage the object. You need to release the object manually by the developer. Otherwise, a memory leak will occur.

// Originally managed by ARC
NSMutableArray *mArray = [[NSMutableArray alloc] init];       
// Manually managed by the developer after conversion
CFMutableArrayRef cfMArray = (__bridge_retained CFMutableArrayRef)(mArray); 
// CFMutableArrayRef cfMArray = (CFMutableArrayRef)CFBridgingRetain(mArray); // Another equivalent.CFRelease(cfMArray);  // Remember to release the object manually when it is not needed
Copy the code

__bridge_retained the object, so that the variable assigned by the translation also retained the object. The reference count of the object is +1. Since the conversion is manually managed by the developer, remember to call CFRelease to release the object when it is no longer needed. Otherwise, the memory leaks.

id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)(obj);
Copy the code

The above code under MRC is equivalent to:

id obj = [[NSObject alloc] init];

void *p = obj;
[(id)p retain];
Copy the code

__bridge_transfer: Used to transfer memory management when Core Foundation objects are converted to Foundation objects.

Core Foundation objects, which are managed manually by developers, are converted into Foundation objects, and the memory management is handed over to ARC. Developers no longer need to worry about releasing objects and memory leaks.

    // It was managed manually by the developer
    CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0.NULL);
    // ARC management after conversion
    NSMutableArray *mArray = (__bridge_transfer NSMutableArray *)(cfMArray);
// NSMutableArray *mArray = CFBridgingRelease(cfMArray); // Another equivalent
Copy the code

__bridge_transfer acts as its name, transferring memory management. Across __bridge_retained, it releases the object held by the converted variable, but retains the object when assigned to the transformed variable, so the reference count remains the same. That is, for Core Foundation reference-counting semantics, the object is freed, but ARC retains references to it.

id obj = (__bridge_transfer void *)(p);
Copy the code

The above code under MRC is equivalent to:

id obj = (id)p;
[obj retain];
[(id)p release];
Copy the code

Here is also an example code:

CFMutableArrayRef cfMArray;
{
    cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0.NULL);
    NSMutableArray *mArray = (__bridge_transfer NSMutableArray *)(cfMArray);
    NSLog(@"%ld".CFGetRetainCount(cfMArray));    // 1, because the object to which the cfMArray pointer points exists, it can still be accessed through that pointer
    NSLog(@"%ld", _objc_rootRetainCount(mArray)); // 1, mArray is __strong
}
// The __strong scope ends and ARC releases the object
NSLog(@"%ld".CFGetRetainCount(cfMArray)); // The next access will crash
Copy the code

What if the code above was changed from __bridge_transfer to __bridge? In fact, the __bridge tutorial provides example code that, if not released, can cause a memory leak. Across __bridge_retained and __bridge_transfer, CFBridgingRetain and CFBridgingRelease are two different functions.

/* Foundation - NSObject.h */
#if __has_feature(objc_arc)  // ARC

// After using a CFBridgingRetain on an NSObject, the caller must take responsibility for calling CFRelease at an appropriate time.
NS_INLINE CF_RETURNS_RETAINED CFTypeRef _Nullable CFBridgingRetain(id _Nullable X) {
    return (__bridge_retained CFTypeRef)X;
}

NS_INLINE id _Nullable CFBridgingRelease(CFTypeRef CF_CONSUMED _Nullable X) {
    return (__bridge_transfer id)X;
}

#else // MRC

// This function is intended for use while converting to ARC mode only.
NS_INLINE CF_RETURNS_RETAINED CFTypeRef _Nullable CFBridgingRetain(id _Nullable X) {
    return X ? CFRetain((CFTypeRef)X) : NULL;
}

// Casts a CoreFoundation object to an Objective-C object, transferring ownership to ARC (ie. no need to CFRelease to balance a prior +1 CFRetain count). NS_RETURNS_RETAINED is used to indicate that the Objective-C object returned has +1 retain count. So the object is 'released' as far as CoreFoundation reference counting semantics are concerned, but retained (and in need of releasing) in the view of ARC. This function is intended for use while converting to ARC mode only.
NS_INLINE id _Nullable CFBridgingRelease(CFTypeRef CF_CONSUMED _Nullable X) NS_RETURNS_RETAINED {
    return [(id)CFMakeCollectable(X) autorelease];
}

#endif
Copy the code

Across ARC, the two functions use __bridge_retained and __bridge_transfer.

Summary: Toll-free Bridging must be properly used for type conversions between Foundation and Core Foundation objects under ARC, otherwise memory leaks may result.

Advice:

  • When we convert a Foundation object into a Core Foundation object, if we immediately use that Core

Foundation object, using __bridge; __bridge_retained for future use, but remember to call CFRelease to release the object.

  • When a Core Foundation object is converted to a Foundation object, __bridge_transfer is used.

(6) What is Autoreleasepool and how does it work? What is his relationship to Runloop?

The Autorelease mechanism in iOS development is designed to delay the release of objects. The concept of auto-release looks a lot like ARC, but it’s actually more similar to the automatic variable feature in C.

Automatic variables: will be deprecated after the variable is out of scope; Automatic release pool: Release messages are sent to the object instances it manages after the release pool life cycle has expired.

Automatic release pool is used under MRC

Using an automatic release pool in an MRC environment requires the NSAutoreleasePool object, whose life cycle is equivalent to the scope of a C language variable. For all objects that have called the AutoRelease method, the Release instance method is called when the NSAutoreleasePool object is discarded. Expressed in source code as follows:

// Test in MRC environment:
// Step 1: Create and hold the NSAutoreleasePool object;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

// Call the autoRelease instance of the object;
id obj = [[NSObject alloc] init];
[obj autorelease];

// Discard the NSAutoreleasePool object;
[pool drain];   [obj release] [obj release]

Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT) Thread 1: EXC_I386_GPFLT
NSLog(Print obj: %@, obj); 
Copy the code

Understand the life cycle of the NSAutoreleasePool object as shown below: Automatic release pool is used under ARC

ARC environments cannot use the NSAutoreleasePool class or call the AutoRelease method. Instead, objects are automatically released using the @Autoreleasepool block and the __autoreleasing modifier. Compare the code differences between the two environments as shown below:

As shown, the @AutoReleasepool block replaces the process of creating, holding, and disposing objects of the NSAutoreleasePoool class. Instead of autorelease, the __autoreleasing modifier registers the object to Autoreleasepool. Due to ARC optimizations, __autorelease can be omitted, so the simplified ARC code looks like this:

//ARC test:
@autoreleasepool {
    id obj = [[NSObject alloc] init];
    NSLog(Print obj: %@, obj); 
}
Copy the code

Explicit use of the __autoreleasing modifier is very rare, because ARC often registers objects to the release pool even when __autoreleasing is not explicitly used. It mainly includes the following situations:

  1. Compiler optimization, check whether the method name begins with a alloc/new/copy/mutableCopy, if not automatically returns the object to register Autoreleasepool;
  2. When you access a variable with an __weak modifier, you actually have to access an object registered with Autoreleasepool, which is automatically added to Autoreleasepool.
  3. Pointers to id or objects (id*, NSError **) are appended with the __autoreleasing modifier by default to Autoreleasepool if no modifier is explicitly specified

Note: If the compiler version is llvM. 3.0 or higher, the @Autoreleasepool block can be used even if ARC is invalid; The source code is as follows:

// Test in MRC environment:
@autoreleasepool{
    id obj = [[NSObject alloc] init];
    [obj autorelease];
}
Copy the code

Underlying principles of AutoreleasePool

1. Use @autoreleasepool{}

We write the following auto-release pool-related test code in the main function:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
    return 0;
}
Copy the code

To explore the underlying implementation of the release pool, we use the clang-rewrite-objc + file name command on the terminal to convert the above OC code into C++ source code.

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */
    {
        __AtAutoreleasePool __autoreleasepool;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_crl5bnj956d806cp7d3ctqhm0000gn_T_main_d37e0d_mi_0);
     }// The braces correspond to the scope of the release pool
     
     return 0;
}
Copy the code

After being converted by the compiler’s clang command, the so-called @Autoreleasepool block we see corresponds to the __AtAutoreleasePool structure.

2. Analyze the implementation of __AtAutoreleasePool

The __AtAutoreleasePool structure can be found in the source code as follows:

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct__AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(); } ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj); }void * atautoreleasepoolobj;
};
Copy the code

The __AtAutoreleasePool structure contains a constructor, a destructor, and a bounding object. Constructor internal call: objc_autoreleasePoolPush() returns boundary object atAutoReleasepoolobj destructor internal call: The objc_autoreleasePoolPop() method passes in the boundary object atAutoReleasepoolobj

Parsing the life cycle of an __autoreleasepool instance in main looks like this: __autoreleasepool is an automatic variable whose constructor is called when the program executes to the point where the object is declared, and whose destructor is called when the program executes to the point where the object is out of scope. So, we can simplify the main function above as follows:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ {
        void *atautoreleasepoolobj = objc_autoreleasePoolPush();
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kb_06b822gn59df4d1zt99361xw0000gn_T_main_d39a79_mi_0);
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}
Copy the code

Objc_autoreleasePoolPush and objc_autoreleasePoolPop

A closer look at the implementation of the AutoreleasePoolPage constructor and destructor shows that they are simply encapsulation of the static methods push and POP corresponding to AutoreleasePoolPage

void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}

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

Understand AutoreleasePoolPage

AutoreleasePoolPage is a C++ class. It is defined in the nsobject. mm file.

// Start at approximately 641 lines
class AutoreleasePoolPage {
\#   define EMPTY_POOL_PLACEHOLDER ((id*)1)  // Empty pool placeholder
\#   define POOL_BOUNDARY nil                // Boundary object (i.e. sentry object)
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);
    magic_t const magic;                  // Verify that the AutoreleasePagePoolPage structure is complete
    id *next;                             // Point to the next position of the newly added autoRelease object, initialized to begin()
    pthread_t const thread;               // AutoreleasePool corresponds to the current thread
    AutoreleasePoolPage * const parent;   // point to the parent node page. The parent value of the first node is nil
    AutoreleasePoolPage *child;           // Point to the child node page, the last node's child value is nil
    uint32_t const depth;                 // List depth, number of nodes
    uint32_t hiwat;                       // An upper limit of data capacity
    / /...
};
Copy the code

In fact, each autorelease pool is a bidirectional linked list composed of several AutoreleasepoolPages, as shown below:

AutoreleasePoolPage has parent and child Pointers to the previous and next page, respectively. When the space of the current page is used up (each AutorelePoolPage is 4096 bytes), an AutorelePoolPage object is created and connected to the list, and subsequent Autorelease objects are added to the new page.

In addition, when next== begin(), AutoreleasePoolPage is empty; When next == end(), AutoreleasePoolPage is full.

5. Understand the role of POOL_BOUNDARY

In the AutoreleasePoolPage source code, it is easy to find the definition of boundary objects (sentry objects) :

#define POOL_BOUNDARY nil
Copy the code

A boundary object is just an alias for nil, and that’s really what it does to be an identifier.

Whenever the autoreleasepoolPush method is called, POOL_BOUNDARY is always placed at the top of the stack using the AutoreleasePoolPage push method and the boundary object is returned.

When the autorelease pool calls the objc_autoreleasePoolPop method, the boundary object is passed in as a parameter, and the autorelease pool sends a release message to the objects in the release pool until the first boundary object is found.

Understand the objc_autoreleasePoolPush method

After the previous analysis, objc_autoreleasePoolPush ultimately calls the AutoreleasePoolPage push method, which is implemented as follows:

static inline void *push() {
   return autoreleaseFast(POOL_BOUNDARY);
}

static inline id *autoreleaseFast(id obj)
{
   AutoreleasePoolPage *page = hotPage();
   if(page && ! page->full()) {return page->add(obj);
   } else if (page) {
       return autoreleaseFullPage(obj, page);
   } else {
      returnautoreleaseNoPage(obj); }}// Push the object to AutoreleaseNoPage and move the pointer to the top of the stack
id *add(id obj) {
    id *ret = next;
    *next = obj;
    next++;
    return ret;
}

// called when the current hotPage is full
static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
    do {
        if (page->child) page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

    setHotPage(page);
    return page->add(obj);
}

// called when the current hotpage does not exist
static id *autoreleaseNoPage(id obj) {
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);

    if(obj ! = POOL_SENTINEL) { page->add(POOL_SENTINEL); }return page->add(obj);
}
Copy the code

Looking at the code above, each call to push creates a new AutoreleasePool, inserts a POOL_BOUNDARY into the corresponding AutoreleasePoolPage, and returns the memory address of the POOL_BOUNDARY. The push method internally calls the autoreleaseFast method and passes in a POOL_BOUNDARY object. HotPage is the AutoreleasePoolPage that is currently in use.

The automatic release pool eventually adds boundary objects to the release pool using the Page ->add(obj) method, which is broken down into three cases in the autoreleaseFast method:

  1. If the page exists and is not satisfied, call page->add(obj) to add the object to the page stack, where next points to
  2. The current page exists but is full. Call autoreleaseFullPage to initialize a new page and call the Page -> Add (obj) method to add the object to the page stack
  3. When the current page does not exist, call autoreleaseNoPage to create a hotPage, and then call page-> Add (obj) to add the object to the page stack

Understand the objc_autoreleasePoolPop method

The AutoreleasePool release calls the objc_autoreleasePoolPop method, which requires a boundary object as a parameter. This boundary object is exactly what is returned each time the objc_autoreleasePoolPush method atAutoReleasepoolobj;

In the same way, objc_autoreleasePoolPop finally calls the pop method of AutoreleasePoolPage, which is implemented as follows:

static inline void pop(void *token)   / / POOL_BOUNDARY address
{
    AutoreleasePoolPage *page;
    id *stop;

    page = pageForPointer(token);   // use POOL_BOUNDARY to find the corresponding page
    stop = (id *)token;
    if(DebugPoolAllocation && *stop ! = POOL_SENTINEL) {// This check is not valid with DebugPoolAllocation off
        // after an autorelease with a pool page but no pool in place.
        _objc_fatal("invalid or prematurely-freed autorelease pool %p; ", 
                    token);
    }

    if (PrintPoolHiwat) printHiwat();   // Record the highest watermark mark

    page->releaseUntil(stop);   // Send release messages to objects on the stack until the first sentinel object is encountered

    // memory: delete empty children
    // Delete the empty node
    if (DebugPoolAllocation  &&  page->empty()) {
        // special case: delete everything during page-per-pool debugging
        AutoreleasePoolPage *parent = page->parent;
        page->kill();
        setHotPage(parent);
    } else if(DebugMissingPools && page->empty() && ! page->parent) {// special case: delete everything for pop(top) 
        // when debugging missing autorelease pools
        page->kill();
        setHotPage(nil);
    } 
    else if (page->child) {
        // hysteresis: keep one empty child if page is more than half full
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if(page->child->child) { page->child->child->kill(); }}}Copy the code

In the above code, the page of the boundary object is found according to the address of the boundary object passed in. Then select the latest object added to the current page and clean forward, you can move forward several pages, until the boundary position; The cleanup is done by sending a release message to these objects, reducing their reference count by one;

In addition, emptying page objects follows a few rules:

  1. If the current page contains less than half of the objects, all sub-pages are deleted.
  2. If more than half of the current page is stored (meaning that the current page will soon be full), keep a sub-page to save the overhead of creating a new page.

8. Autorelease method

Now let’s understand the implementation of the delayed release object autoRelease method. First look at the call stack of this method:

- [NSObjectAutorelease] └ ─ ─idObjc_object: : rootAutorelease () └ ─ ─idObjc_object: : rootAutorelease2 () └ ─ ─static id AutoreleasePoolPage::autorelease(idObj) └ ─ ─static id AutoreleasePoolPage::autoreleaseFast(idObj) ├ ─ ─id *add(idObj) ├ ─ ─static id *autoreleaseFullPage(id│ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─id *add(idObj) └ ─ ─static id *autoreleaseNoPage(id├─ ├─ └ exercises (newParent) ├─id *add(id obj)
Copy the code

As shown above, the AutoRelease method also ends up calling the autoreleaseFast method mentioned above to add the current object to the AutoreleasePoolPage. The analysis of autoreleaseFast is not covered here, but let’s consider the difference between the two calls:

As with push, the key code for autoRelease is to call autoreleaseFast to add an object to the auto-release list stack, but push pushes a boundary object, And autoRelease pushes a concrete autoRelease object.

Relationship between AutoreleasePool and NSThread and NSRunLoop

Due to the optimization of the AppKit and UIKit frameworks, we rarely need to explicitly create an autofree pool block. This involves the relationship between AutoreleasePool and NSThread and NSRunLoop.

Relationship between RunLoop and NSThread

RunLoop is a mechanism for controlling the thread life cycle and receiving events for processing. It is essentially a do-while loop. NSRunLoop can be found in the Apple documentation as follows:

Your application neither creates or explicitly manages NSRunLoop objects. Each NSThread object includes the Application’s main thread — has an NSRunLoop object automatically created for it as needed. If you need to access the Current thread’s run loop, you do so with the class method currentRunLoop.

The relationship between RunLoop and NSThread is summarized as follows:

  1. Runloops correspond to threads one by one. Each thread (including the main thread) has a corresponding RunLoop object. The correspondence is stored in a global Dictionary;
  2. The RunLoop for the main thread is automatically created and started by default. The other thread is created without a RunLoop. If the thread never acquires a RunLoop, it will never have a RunLoop.
  3. Apple doesn’t provide a way to create runloops directly; Runloops are created for other threads when they are first fetched. If the current thread does not have a Runloop, the system automatically creates one.
  4. When the current thread terminates, its corresponding Runloop is destroyed;

Relationship between RunLoop and AutoreleasePool

A description of the relationship found in Apple documentation is as follows:

The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.

As mentioned above, the NSRunLoop of the main thread automatically creates an AutoRelease pool before detecting that each event loop has been opened in response to the event and releases the objects in it by executing drain at the end of the event loop.

Relationship between Thread and AutoreleasePool

A description of the relationship found in Apple documentation is as follows:

Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects (see Threads). As new pools are created, they get added to the top of the stack. When pools are deallocated, they are removed from the stack. Autoreleased objects are placed into the top autorelease pool for the current thread. When a thread terminates, it automatically drains all of the autorelease pools associated with itself.

As mentioned above, all threads, including the main thread, maintain their own auto-release pool stack structure. When new autofree pools are created, they are added to the top of the stack and removed from the stack when the pool is destroyed. For the current thread, the Autoreleased object is placed in the auto-release pool at the top of the stack. When a thread thread stops, it automatically releases all the auto-release pools associated with it.

AutoreleasePool release time on the main thread

Understand the automatic release process on the main thread

Set a breakpoint on the main thread in the following Demo and run the LLDB command Po [NSRunLoop currentRunLoop].

The main thread RunLoop has two observers related to the automatic release pool. Their activities are 0x1 and 0xa0 hex numbers, which are binary 1 and 10100000 respectively. The corresponding CFRunLoopActivity types are as follows:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags.CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),          //0x1, start the Runloop
    kCFRunLoopBeforeTimers = (1UL << 1),            
    kCFRunLoopBeforeSources = (1UL << 2),        
    kCFRunLoopBeforeWaiting = (1UL << 5),  //0xa0, is about to go to sleep
    kCFRunLoopAfterWaiting = (1UL << 6),   
    kCFRunLoopExit = (1UL << 7),           //0xa0, exit the RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
Copy the code

In combination with the event types that RunLoop listens for, the usage of the auto-release pool on the main thread is analyzed as follows:

  1. The App starts, apple registered in the main thread RunLoop two Observer, the callback is _wrapRunLoopWithAutoreleasePoolHandler ();
  2. The first Observer monitors an event called Entry(about to enter Loop), which creates an automatic release pool within its callback by calling _objc_autoreleasePoolPush(). Order = -2147483647(32-bit integer minimum) indicates the highest priority and ensures that the release pool is created before all other callbacks;
  3. The second Observer monitors two events BeforeWaiting(ready to sleep) calling _objc_autoreleasePoolPop() and _objc_autoreleasePoolPush() torelease the old pool and create a new one; _objc_autoreleasePoolPop() is called upon Exit(about to Exit the Loop) torelease the automatic release pool. Order = 2147483647(the maximum value of a 32-bit integer) indicates that it has the lowest priority and is guaranteed to release its pool after all other callbacks;
  4. The code that executes on the main thread is usually written inside such callbacks as event callbacks and Timer callbacks. These callbacks are surrounded by the AutoreleasePool created by RunLoop, so there is no memory leak and the developer does not have to show that the AutoreleasePool was created;

Finally, you can also use the following diagram to understand the process of automatically releasing objects on the main thread:

After the program is started and loaded, the RunLoop corresponding to the main thread stops and waits for user interaction. Each user interaction starts a RunLoop to process all user clicks and touches. When RunLoop detects an event, it creates an automatic release pool; All deferred release objects are added to the pool; At the end of a full run loop, a release message is sent to all objects in the pool, and the pool is automatically freed and destroyed;

Tests the automatic release of objects on the main thread

The following code creates an Autorelease object string and uses weakString for weak reference (does not increase the reference count, so it does not affect the life cycle of the object) as follows:

@interface TestMemoryVC(a)
@property (nonatomic.weak)NSString *weakString;
@end

@implementation TestMemoryVC
- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *string = [NSString stringWithFormat:@ "% @".@"WUYUBEICHEN"];
    self.weakString = string;
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear:%@".self.weakString);
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    NSLog(@"viewDidAppear:%@".self.weakString);
}

@end

// Print the result:
//viewWillAppear:WUYUBEICHEN
//viewDidAppear:(null)
Copy the code

Code analysis: When the string of an automatic variable leaves viewDidLoad’s scope, it is released automatically by a RunLoop iteration on the current main thread. Finally the String is freed before the viewDidAppear method executes (RunLoop completes this iteration).

The release timing on the AutoreleasePool child thread

Child threads do not enable RunLoop by default, so how do I release the delayed object? The relationship between Thread and AutoreleasePool is still relevant:

Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects.

That is, each thread maintains its own Autoreleasepool stack, so while RunLoop is not enabled by default for child threads, Autoreleasepool still exists, and the autoRelease object is released when the child thread exits.

As mentioned earlier, ARC optimizes the __autoreleasing modifier in some cases, which essentially calls the autorelease method on objects that need to be released later. From a source code analysis point of view, if an AutoreleasePool is not created in a child thread, once an Autorelease object is generated, the autoreleaseNoPage method is called to automatically create a HotPage and add the object to its stack. So, in general, there will be no memory leaks in child threads even if we don’t manually add auto-release pools.

AutoreleasePool that needs to be added manually

Although ARC has made many optimizations, in some cases you have to manually create an AutoreleasePool in which delayed objects will be released when the scope of the current release pool ends. Apple documentation describes three situations where we may need to manually add automatic release pools:

  1. Writing programs that are not based on a UI framework, such as a command-line tool;
  2. Create a large number of temporary objects in a loop;
  3. Using child threads created by non-Cocoa programs;

In actual development in an ARC environment, we most often encounter the second case, as shown in the following code:

- (void)viewDidLoad {
    [super viewDidLoad];
    for (int i = 0; i < 1000000; i++) {
        NSString *obj = [NSString stringWithFormat:@ "% @".@"obj"];
        NSLog(Print obj: %@, obj); }}Copy the code

In the above code, because obj is out of scope, it is added to the recently created auto release pool managed by RunLoop on the main thread. Because the for loop does not finish executing in the current thread, the Runloop does not complete the current iteration, resulting in a large number of objects being released late. Objects in the release pool will be destroyed before the viewDidAppear method executes. In this case, it is necessary to release unwanted objects in time through manual intervention to reduce memory consumption; The optimized code is as follows:

- (void)viewDidLoad {
    [super viewDidLoad];
    for (int i = 0; i < 1000000; i++) {
        @autoreleasepool{
             NSString *obj = [NSString stringWithFormat:@ "% @".@"obj"];
             NSLog(Print obj: %@, obj); }}}Copy the code

(7) What is TaggedPointer and which objects use the TaggedPointer technique?

Specific reference: iOS – Cliche memory management (5) : Tagged Pointe

(8) Example analysis

To get more familiar with the above, I wrote a small demo to analyze the output to help understand it better:

//
// demo8ViewController.m
// iOSPlayground
//
// Created by bytedance on 2021/9/9.
//

#import "demo8ViewController.h"

@interface demo8ViewController(a)

@property (nonatomic.strong) NSObject *strongProperty;

@property (nonatomic.weak) NSObject *weakProperty;


@property (nonatomic.weak) NSString *testString;

@end

@implementation demo8ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self testObjAsReturnValue];
}
- (void)viewWillAppear:(BOOL)animated {
}

- (void)viewDidAppear:(BOOL)animated {
}

- (void)setupString {
    NSString *str = [[NSString alloc] init];
    NSLog(@" Retain Count=%ld of the object before assigning it to an __weak pointer", (long)CFGetRetainCount((__bridge CFTypeRef)(str)));
    self.testString = str;
    NSLog(@" Retain Count=%ld of the object after assigning it to a pointer to __weak", (long)CFGetRetainCount((__bridge CFTypeRef)(str)));
}

/** NSString reference count special */
- (void)testNSStringReatinCount {
    NSString *strNoMoreThan10 = @ "123456789";
    NSLog(@"strNoMoreThan10, retain Count=%ld", (long)CFGetRetainCount((__bridge CFTypeRef)(strNoMoreThan10)));
    NSString *strMoreThan10 = @ "1234567890";
    NSLog(@"strMoreThan10, retain Count=%ld", (long)CFGetRetainCount((__bridge CFTypeRef)(strMoreThan10)));
    NSString *str1_No = [[NSString alloc] initWithString:strNoMoreThan10];
    NSLog(@"str1_No, retain Count=%ld", (long)CFGetRetainCount((__bridge CFTypeRef)(str1_No)));
    NSString *str1 = [[NSString alloc] initWithString:strMoreThan10];
    NSLog(@"str1, retain Count=%ld", (long)CFGetRetainCount((__bridge CFTypeRef)(str1)));
    NSString *str2_No = [NSString stringWithFormat:@ "% @", strNoMoreThan10];
    NSLog(@"str2_No, retain Count=%ld", (long)CFGetRetainCount((__bridge CFTypeRef)(str2_No)));
    NSString *str2 = [NSString stringWithFormat:@ "% @", strMoreThan10];
    NSLog(@"str2, retain Count=%ld", (long)CFGetRetainCount((__bridge CFTypeRef)(str2)));
}

- (void)testObjAsReturnValue {
    self.testString = [self createString];
// NSString *__weak strValue = [self createString];
// NSObject *__weak objValue = [self createObj];
    NSLog(@" Retain Count=%ld on object returned as return value", (long)CFGetRetainCount((__bridge CFTypeRef) (self.testString)));
}

- (void)testObjAsParamter{
    NSObject* __strong copyObj = nil;
    @autoreleasepool {
        NSObject *tempObj = [[NSObject alloc] init];
        NSLog(@" Retain Count=%ld after object created using init", (long)CFGetRetainCount((__bridge CFTypeRef)(tempObj)));
        copyObj = tempObj;
        NSLog(@" Retain Count=%ld when strongly referencing an object with a pointer of type __strong", (long)CFGetRetainCount((__bridge CFTypeRef)(tempObj)));
        [self getRetainCountofObj:tempObj];
        NSLog(@" Retain Count=%ld of object after object as argument", (long)CFGetRetainCount((__bridge CFTypeRef)(tempObj)));
    }
    NSLog(@" Retain Count=%ld after object leaves autoreleasepool scope", (long)CFGetRetainCount((__bridge CFTypeRef)(copyObj)));
}

- (long)getRetainCountofObj:(NSObject* __strong)objc {
    long retainCount =  (long)CFGetRetainCount((__bridge CFTypeRef)(objc));
    NSLog(@" Retain Count=%ld of object passed in as argument", retainCount);
    return retainCount;
}

- (NSString *)createString{
    NSInteger i = 1;
    NSString *Str = [NSString stringWithFormat:@" Test string -%ld", i];
    NSLog(@" Reatin Count=%ld for objects created using non-init methods", (long)CFGetRetainCount((__bridge CFTypeRef)(Str)));
// NSString *Str = [[NSString alloc] init];
// NSLog(@" init, reatin Count=%ld", (long)CFGetRetainCount((__bridge CFTypeRef)(Str)));
    return Str;
}

- (NSObject *)createObj {
    NSObject *obj = [[NSObject alloc] init];
    NSLog(@" Reatin Count=%ld for objects created with init method", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
    return obj;
}

@end
Copy the code

Code analysis:TestObjAsParamter = testObjAsParamter;As you can see from the above output, the NSObject object was originally created and held with alloc, so tempObj’s reference count was 1. After assigning the object to the __strong modified object copyObj, the object was retained, so its reference count was 2. After passing it as a parameter to getRetainCountofObj, it is reatined again, so the reference count is 3. After leaving getRetainCountofObj, the object is sent a release message, so its reference count becomes 2. When you finally leave AutoreleasePool scope, the reference count is reduced by 1 to 1 because the object is in the AutoreleasePool pool, and the AutoreleasePool pool is destroyed, and release messages are sent to all objects in the pool.

reference

IOS – Familiar memory management: Introduction to iOS memory management – in-depth analysis of automatic free pool