preface

Manual Reference Counting (MRC), also called Manual retained -release (MRR), is used to manage object memory by manually controlling the Reference count of an object.

In the MRC era, we often needed to write retain, release, autorelease and other methods to manually manage object memory. However, these methods were prohibited by ARC and would cause compilation errors.

Let’s start with the MRC and talk about iOS memory management.

Introduction to the

About 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.

Objective-c provides two ways to manage memory in iOS:

  1. MRC, which is the subject of this article, we manage memory explicitly by tracking the objects we hold. This is done using a model called “reference counting,” which is provided by the Foundation framework’s NSObject class along with the runtime environment.
  2. ARC, system use withMRCSame reference counting system, but it inserts the appropriate memory management method calls for us at compile time. useARC, we usually don’t need to know what is described in this articleMRCMemory management implementation, although it may be helpful in some cases. However, as a qualifiediOSDevelopers, it is essential to have this knowledge.

Good practice prevents memory-related problems

  • There are two main types of problems caused by incorrect memory management:
    • ① Release or overwrite data that is still in use

    This results in memory corruption, and often leads to application crashes and even user data corruption.

    • ② Not releasing data that is no longer in use can cause memory leaks

    A memory leak is a failure to release allocated memory that is no longer being used. Memory leaks can cause an application to increase its memory usage, which can lead to system performance degradation or application termination.

  • However, thinking about memory management in terms of reference counting is often counterproductive because you tend to think about memory management in terms of implementation details rather than actual goals. Instead, you should think of memory management in terms of object ownership and object graph.
  • Cocoa uses a simple naming convention to indicate when you hold objects returned by methods. (See the section on Memory Management Policies)
  • Although the basic memory management strategy is simple, there are steps you can take to simplify memory management and help ensure that programs remain reliable and robust while minimizing their resource requirements. (See the practical Memory Management section)
  • Automatic release of pool blocks provides a mechanism by which you can send “delay” to objectsreleaseThe message. This is useful in situations where you need to relinquish ownership of an object but you want to avoid releasing the object immediately (for example, when an object is returned from a method). In some cases, you may use your own autorelease pool block. (please refer to theUse Autorelease Pool BlocksChapter)

Use analysis tools to debug memory problems

To find code problems at compile time, you can use The Clang Static Analyzer built into Xcode. If memory management problems still occur, other tools and techniques can be used to identify and diagnose problems.

  • Technical Note TN2239, iOS Debugging MagicMany tools and techniques are described, especially for useNSZombie(zombie object) to help find overreleased objects.
  • You can use Instruments to track reference count events and look for memory leaks. See Instruments Help.

Memory Management Policy

The combination of memory management methods defined in the NSObject protocol and custom methods that follow the naming conventions for these methods provides the basic model for memory management in a reference counting environment. The NSObject class also defines a dealloc method, which is called automatically when the object is destroyed.

Basic memory management rules

Under MRC, we strictly follow the reference count memory management rules.

The memory management model is based on object ownership. Any object can have one or more owners. As long as an object has at least one owner, it will continue to exist. If the object has no owner, the runtime system destroys it automatically. To make sure you know when you own and don’t own objects, Cocoa sets the following policies:

Four rules

(1) 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 pass the custom method To create and hold objects, the method name should begin with the alloc/new/copy/mutableCopy, naming rules and should follow the hump, the object returned should also founded by these methods, such as:

- (id)allocObject
{
    id obj = [NSObject alloc] init];
    retain obj;
}
Copy the code

You can use the retainCount method to view the reference count value of an object, but try not to use it.

    NSLog(@"%ld", [obj retainCount]);
Copy the code

You can use retain to hold objects

We can use retain to hold an object. Objects created using methods other than the above are not held and have an initial RC value of 1. Note, however, that if you want to use (hold) this object, you need to retain it first, otherwise it may Crash the program. The reason is that these methods internally call the autoRelease method on objects, so those objects are added to the automatic release pool.

  • ① Case one: The iOS program is not manually specified@autoreleasepool

When the RunLoop iteration ends, the release method is automatically called for 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.

    /* Correct usage */

    id obj = [NSMutableArray array]; // Create object but do not hold it, add object to automatic release pool, 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
  • ② Case two: Manually specify@autoreleasepool

This is especially true if the @AutoReleasepool scope ends, the AutoRelease method is automatically called to the AutoRelease object. 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

If we pass the custom method Create but that does not hold objects, the method name is 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 pool. Such as:

- (id)object
{
    id obj = [NSObject alloc] init];
    [obj autorelease];
    retain 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.

Note: The difference between release and autorelease:

  • callreleaseObject,RCWill immediately -1;
  • callautoreleaseObject,RCInstead of -1 immediately, the object is added to the auto-release pool, which is automatically called to the object at an appropriate momentrelease, soautoreleaseThe release of the object is delayed.

(three) no longer need to hold their own object release

Call the release or autoRelease method when you don’t need to use (hold) an object torelease it (or “give it up”) and make it RC-1 to prevent memory leaks. When the object’s RC is 0, the dealloc method is called to destroy the object.

(4) They cannot release objects not held by 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

There’s another case, and this is the case where it’s not easy to spot the problem. Executing the following code may or may not be a problem. The memory occupied by the object is deallocated and just put back into the available memory pool. If the object’s memory has not been allocated to someone else, then the access is fine. If it has been allocated to someone else, the access will crash again.

    Person *person = [[Person alloc] init]; // Create and hold object, RC = 1
    [person release]; // If you're the owner, call release when you don't need it, RC = 0
    [person release]; / /!!!!!! It is not safe to send messages to already reclaimed objects
Copy the code

The above is the basic four rules of memory management, you compare the last article about the “lighting problem in the office”, is not better understood, you fine, you fine taste!

A simple example

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

{
    Person *aPerson = [[Person alloc] init];
    // ...
    NSString *name = aPerson.fullName;
    // ...
    [aPerson release];
}
Copy the code

Use autoRelease to send delayed release

You can use autoRelease when you need to send deferred release messages, usually when returning objects from methods. For example, you could implement the fullName method like this:

- (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@ % @ % @ "".self.firstName, self.lastName] autorelease];
    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. With autoRelease, you delay the release and return it until the NSString object is released.

You can also implement the fullName method like this:

- (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

Inside the fullName method we create and hold the object 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:

  • Never call another object directlydeallocThe method;
  • You must call it at the end of the implementation[super dealloc];
  • 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, it may not send to the objectdeallocThe message. Since the memory of a process is cleared automatically upon exit, having the operating system clean up resources is better than calling all objectsdeallocMethods are more effective.

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) do not apply to methods that return Objective-C objects. As in the following code snippet, you are not responsible for relinquishing ownership of myInstance. Because in the Cocoa use alloc/new/copy/mutableCopy method (or methods) begin with the methods of objects created, we just need to be released.

    MyClass * myInstance = [MyClass createInstance];
Copy the code

Practical memory management

Although the basic memory management strategy is simple, there are steps you can take to simplify memory management and help ensure that programs remain reliable and robust while minimizing their resource requirements.

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.

The @synthesize automatically generates the implementation of setter and getter methods and the underline instance variables, as explained in the next ARC article.

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
- (instancetype)initWithCount:(NSNumber *)startingCount {
    self = [super init];
    if (self) {
        _count = [startingCount copy];
    }
    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?

  • Let me give you a general ideaselfandsuper.selfIs an object pointer to the current message receiver.superIs a compiler instruction that usessuperThe calling method looks for the implementation of the method starting in the parent class of the current message receiver class, but the message receiver is still a subclass. The relevantselfandsuperFor a detailed explanation, seeThe Essence of Super.
  • call[super init]It is the subclass that calls the parent classinitMethod to initialize the parent class. Note that during the call, the parent classinitIn the methodselfAgain, subclasses.
  • performself = [super init]If the parent class is successfully initialized, then the subclass is initialized. If the parent class initialization fails[super init]Returns thenilAnd assigned to itself, the followingif (self)The contents of the statement will not be executed, subclassinitThe method also returnsnil. 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 unusable object that behaves unpredictably and may eventually cause your program to happenCrash.

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

  • In the initialization method anddeallocIn, the existence of the object is uncertain, it may not be initialized, so sending a message to the object may not succeed or cause some problems.
    • To further explain, suppose we were ininitThe use ofsetterMethod to initialize instance variables. ininit, we will callself = [super init]The parent class is initialized first, that is, the child class calls the parent class firstinitMethod (note: the parent class of the callinitIn the methodselfOr subclass object). If the parent classinitThe use ofsetterMethod initializes the instance variable, and the subclass overrides itsetterMethod is called when the parent class is initializedsetterMethods. At this point, the parent class is only being initialized, and the subclass initialization is not complete, so errors may occur.
    • When a subclass object is destroyed, the subclass is first calleddealloc, and finally call[super dealloc](this has to do withinitOn the contrary). If you’re in the parent classdeallocCall thesetterMethod and the method is overridden by a subclasssetterMethod, but at this point the subclass has been destroyed, so this can also be an error.
    • In Effective Objective-C 2.0:52 Effective Ways to Write High Quality iOS and OS X code, dealloc methods release only references and unlisten. 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.
    • 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 property isatomic.
  • Side effects are possible. Such as using theKVOThat will triggerKVOAnd 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 pass through the initialization methodsetterTo assign values to instance variables.

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.

In MRC, “weak reference” here means do not retain, not weak in ARC.

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 to Table Data Sources, Outline View items, Notification observers, and other targets and delegates.

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.

  2. 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.

  3. 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 createdretain count1.
  • Send to objectretainMessage when itsretain countWill be + 1.
  • Send to objectreleaseMessage when itsretain countTo 1.
  • Send to objectautoreleaseMessage when itsretain count-1 at the end of the current automatic pool block release.
  • If the object’sretain countIt goes down to 0, it’s going to bedealloc.

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.”

Use Autorelease Pool Blocks

Auto-release pool blocks provide a mechanism to give up ownership of an object, but avoid releasing it immediately (for example when an object is returned from a method). Usually, you don’t need to create your own autorelease pool block, but in some cases you must or it’s beneficial to do so.

Autorelease Pool Blocks

Autorelease Pool Blocks use the @autoreleasepool flag as shown in the following example:

    @autoreleasepool {
        // Code that creates autoreleased objects.
    }
Copy the code

At the end of @AutoReleasepool, the object that received an AutoRelease message in the block will be sent a Release message. Each time an object receives an AutoRelease message within the block, a Release message is sent. Like any other code block, @Autoreleasepool can be nested, but you usually don’t.

    @autoreleasepool {
        / /...
        @autoreleasepool {
            / /...}..}Copy the code

You can also create automatic release pools under MRC using NSAutoreleasePool. Instead, 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

Cocoa always wants code to be executed in @AutoReleasepool, otherwise the AutoRelease object won’t be released, causing a memory leak. If you send an AutoRelease message outside of @Autoreleasepool, Cocoa prints an appropriate error message. AppKit and UIKit frameworks create and process @AutoReleasepool in each iteration of RunLoop’s event loop, so you usually don’t have to create @AutoReleasepool yourself, or even know how to write the code that creates @AutoReleasepool.

However, there are three situations in which you might use your own @Autoreleasepool:

  • ① If you write an application that is not based on a UI framework, such as a command line tool;
  • If you write a loop that creates a lot of temporary objects; You can use it in a loop@autoreleasepoolThese objects are destroyed at the end of each loop. This reduces the maximum memory footprint of your application.
  • ③ If you create a worker thread. Once the thread starts executing, it must create its own@autoreleasepool; Otherwise, your application will have a memory leak. For more information, seeAutorelease Pool Blocks and threadsChapter.

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

Use Local Autorelease Pool Blocks to reduce peak memory usage

Many programs create temporary objects for autoReleases. These objects are added to the program’s memory footprint until the block ends. In many cases, temporary objects are allowed to accumulate until the end of the iteration of the current event loop without incurring excessive overhead. However, in some cases, you may create a large number of temporary objects that add significantly to your memory footprint, and you may want to destroy them more quickly. At this point, you can create your own @Autoreleasepool. At the end of the block, temporary objects are released, which allows them to dealloc as quickly as possible, reducing the memory footprint of the program.

The following example demonstrates how to use the Local Autorelease Pool block in a for loop.

    NSArray *urls = <# An array of file URLs #>;
    for (NSURL *url in urls) {
 
        @autoreleasepool {
            NSError *error;
            NSString *fileContents = [NSString stringWithContentsOfURL:url
                                             encoding:NSUTF8StringEncoding error:&error];
            /* Process the string, creating and autoreleasing more objects. */}}Copy the code

The for loop processes one file at a time. Any object that sends an AutoRelease message within @AutoReleasepool (such as fileContents) is released at the end of the block.

After @AutoReleasepool, you should treat any AutoRelease object in the block as “destroyed.” Do not send a message to this object or return it to your method caller. If you need an autoRelease temporary object to be available after @Autoreleasepool ends, you can do this by sending a retain message to the object within the block and then sending an Autorelease to it after the block, as shown in the following example:

- (id)findMatchingObject:(id)anObject {
 
    id match;
    while (match == nil) {
        @autoreleasepool {
 
            /* Do a search that creates a lot of temporary objects. */
            match = [self expensiveSearchForObject:anObject];
 
            if(match ! =nil) {
                [match retain]; /* Keep match around. */}}}return [match autorelease];   /* Let match go and return it. */
}
Copy the code

Sending a retain message to the match object in @Autoreleasepool and an AutoRelease message after @Autoreleasepool extends the life of the match object by allowing it to receive messages outside the while loop, And it can be returned to the caller of the findMatchingObject: method.

Autorelease Pool Blocks and threads

Each thread in Cocoa applications maintains its own autorelease pool blocks stack. If you’re writing a Foundation only program or if you’re using child threads, you’ll need to create your own @AutoReleasepool. Use @AutoReleasepool if your application or thread is long-running and likely to produce a large number of AutoRelease objects (e.g. AppKit and UIKit create @AutoReleasepool on the main thread). Otherwise, autoRelease objects will accumulate, causing your memory footprint to increase. If you are not making Cocoa calls on child threads, you do not need to use @Autoreleasepool.

Note: If you use PThreads (POSIX threads) instead of NSThreads to create child threads, you cannot use Cocoa unless Cocoa is in multithreaded mode. Cocoa only went into multithreaded mode after detach its first NSThread object. To use Cocoa on child threads created by pThreads, your application must first detach at least one NSThread object that can be immediately exited. You can test whether Cocoa is in threaded mode using the NSThread class method isMultiThreaded.