Memory Management-Manual Memory Management in MRC Era

Keywords: iOS Mac less xcode

Manual Memory Management in MRC Era

In iOS, the memory of OC objects is managed by reference counting.

  • A newly created OC object reference count defaults to 1. When the reference count is reduced to 0, the OC object will be destroyed and its occupied memory space will be released by the system.
  • Calling retain causes the reference count of OC objects to be + 1, and calling release causes the reference count of OC objects to be - 1.

Principles of memory management

  • When an object is returned by calling alloc, new, copy, mutableCopy methods and no longer needed, release or autorelease are called to release it.
  • If you want to own an object, let its reference count + 1, and if you don't want to own another object, let its reference count - 1.

You can view the automatic release pool by looking at a private function

extern void _objc_autoreleasePoolPrint(void);

Next, we will analyze a wave through a case study.

//*********  main.m   ************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        CLPerson *person = [[CLPerson alloc] init];
        NSLog(@"retainCount ----- %zd",[person retainCount]);
        
    }
    return 0;
}

//*********  CLPerson.h   ************
#import <Foundation/Foundation.h>

@interface CLPerson : NSObject

@end

//*********  CLPerson.m   ************
#import "CLPerson.h"

@implementation CLPerson
-(void)dealloc {
    [super dealloc];
    NSLog(@"%s",__func__);
}
@end

We create a CLPerson instance through alloc in main.m, and we can see that its reference count is 1 by printing.

  2019-08-27 09:12:45.362995+0800 MRCManager[10928:615055] retainCount ----- 1

And you don't see the person calling the dealloc method, which means that the person is not released after the main function ends. Then we add a release after using person, as follows

CLPerson *person = [[CLPerson alloc] init];
NSLog(@"retainCount ----- %zd",[person retainCount]);
[person release];

The printout for this time is

2019-08-27 09:12:45.362995+0800 MRCManager[10928:615055] retainCount ----- 1
2019-08-27 09:12:45.363226+0800 MRCManager[10928:615055] -[CLPerson dealloc]

As you can see, person has gone through the dealloc method, that is to say, it has been released successfully, because the release method makes its reference count - 1, 1 - 1 = 0, and then the system releases person according to the value of the reference count. OC's memory management principle is very simple.

We know that the Mac command line project, main function is executed linearly from top to bottom until return 0, and the whole program exits. So, like the scenario in our case, it's easy to determine when to release the object.

In our commonly used iOS projects, due to the addition of RunLoop, the program will loop through the main function until it crashes, or manually close app. So when an object is created, it is difficult to determine when it will be used. If release is not called, it can be guaranteed that the object is safe to use at any time, but the problem is that when the object is no longer used, it will remain in memory and will not be released, which is what we often call ** [memory leak].

For this reason, Apple provides us with autorelease, which is called every time an object is created.

  CLPerson *person = [[[CLPerson alloc] init] autorelease];

In this way, we do not need to manually call [person release]; the system will automatically release the person at a suitable time, which is temporarily understood as the end of @autorelease pool {} brackets.

#import <Foundation/Foundation.h>
#import "CLPerson.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        CLPerson *person = [[[CLPerson alloc] init] autorelease];
        CLPerson *person2 = [[[CLPerson alloc] init] autorelease];
        NSLog(@" @autoreleasepool Coming to an end");
    }
    NSLog(@" @autoreleasepool It's already over.");
    return 0;
}

//**************************************** Print Information*****************************************
2019-08-27 09:40:29.388495+0800 MRCManager[10970:625654]  @autoreleasepool Coming to an end
2019-08-27 09:40:29.388727+0800 MRCManager[10970:625654] -[CLPerson dealloc]
2019-08-27 09:40:29.388736+0800 MRCManager[10970:625654] -[CLPerson dealloc]
2019-08-27 09:40:29.388756+0800 MRCManager[10970:625654]  @autoreleasepool It's already over.
Program ended with exit code: 0

The above cases are discussed only for a simple case of an object. In actual iOS projects, there are often many relationships between objects and objects. Let's continue to add a CLCat object to the above code

#import <Foundation/Foundation.h>

@interface CLCat : NSObject
-(void)run;
@end

***************************************************

#import "CLCat.h"

@implementation CLCat
-(void)run {
    NSLog(@"%s",__func__);
}

-(void)dealloc {
    [super dealloc];
    NSLog(@"%s",__func__);
}
@end

If CLPerson wants to own CLCat, it needs to be adjusted as follows

#import <Foundation/Foundation.h>
#import "CLCat.h"
@interface CLPerson : NSObject
{
    CLCat *_cat;
}
//Having a cat
-(void)setCat:(CLCat *)cat;
//Getting Cats
-(CLCat *)cat;
@end

***************************************************


#import "CLPerson.h"
@implementation CLPerson

-(void)setCat:(CLCat *)cat {
    _cat = cat;
}

-(CLCat *)cat {
    return _cat;
}

-(void)dealloc {
    [super dealloc];
    NSLog(@"%s",__func__);
}

@end

In this way, the CLCat object can be set to the CLPerson _cat member variable (owns) by the setCat method, and the member variable _cat (gets) by the cat method. That is to say, the CLPerson object can manipulate a CLCat object through the _cat pointer.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CLCat *cat = [[CLCat alloc] init];
        CLPerson *person = [[CLPerson alloc] init];
        [person setCat:cat];
        [person.cat run];
        
        [cat release];
        [person release];
        
    }
    return 0;
}

***************** Print results  ****************
2019-08-27 10:22:11.086033+0800 MRCManager[11054:643966] -[CLCat run]
2019-08-27 10:22:11.086283+0800 MRCManager[11054:643966] -[CLCat dealloc]
2019-08-27 10:22:11.086294+0800 MRCManager[11054:643966] -[CLPerson dealloc]
Program ended with exit code: 0

From the print results, it seems to work. But notice [person.cat run]; before [cat release]. If this is the case

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CLCat *cat = [[CLCat alloc] init];
        CLPerson *person = [[CLPerson alloc] init];
        [person setCat:cat];

        [cat release];
        [person.cat run];
        [person release];
        
    }
    return 0;
}

It turns out to be like this.The reason for the error has been shown in the diagram, so you just need to ensure [cat release]; in [person. cat run]; and then be called. But in actual card issuance, [person.cat run]; when to be called is uncertain, and the number of times is uncertain, that is to say, we can not determine [person.cat run]; when and where to be last called, so we can not determine [cat release]; where to write. In order to ensure that no EXC_BAD_ACCESS error occurs, you can simply not write [cat release]; but this brings about memory leaks.
The essence of the problem is that CLPerson does not really own CLCat. The so-called "real" possession means that CLCat should not be released as long as the CLPerson is still there. So we can do that.

#import "CLPerson.h"

@implementation CLPerson


-(void)setCat:(CLCat *)cat {
    [_cat retain];//Count references + 1
    _cat = cat;
}

-(CLCat *)cat {
    
    return _cat;
}

-(void)dealloc {
    //I'm about to be released. I don't need cat anymore.
    [_cat release];
    _cat = nil;
    
    [super dealloc];
    NSLog(@"%s",__func__);
}
@end

This way, even if there are multiple CLPerson objects using CLCat, there will be no problem.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //RC+1
        CLCat *cat = [[CLCat alloc] init];
        
        CLPerson *person = [[CLPerson alloc] init];
        CLPerson *person2 = [[CLPerson alloc] init];
        
        //Internal RC+1 (setCat) - -> RC-1 (dealloc)
        [person setCat:cat];
        
        //Internal RC+1 (setCat) - -> RC-1 (dealloc)
        [person2 setCat:cat];
        
        //RC-1, to correspond to [CLCat alloc] above
        [cat release];
        
        [person.cat run];
        [person2.cat run];
        [person release];
        [person2 release];
        
    }
    return 0;
}

Judging from CLCat's retainCount change process, it will eventually become 0, which does not affect the release of CLCat instance objects. At the same time, it ensures that CLCat's retainCount must be 0 before the release of the last CLPerson instance object (meaning that CLCat is no longer needed and can be released) and successfully released. Operational results can also be verified.

2019-08-27 10:55:41.799859+0800 MRCManager[11120:657618] -[CLCat run]
2019-08-27 10:55:41.800096+0800 MRCManager[11120:657618] -[CLCat run]
2019-08-27 10:55:41.800111+0800 MRCManager[11120:657618] -[CLPerson dealloc]
2019-08-27 10:55:41.800117+0800 MRCManager[11120:657618] -[CLCat dealloc]
2019-08-27 10:55:41.800123+0800 MRCManager[11120:657618] -[CLPerson dealloc]
Program ended with exit code: 0

Generally speaking, the principle of manual memory management is to keep the retainCount balance of instance objects. If there is + 1, there will be corresponding - 1, which ensures that eventually it will become 0, and the instance objects can be released successfully.

So far, the above processing of setCat method is still not perfect. Next, let's move on to the details. Let's start with the following scenario

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //person_rc + 1 = 1
        CLPerson *person = [[CLPerson alloc] init];
        
        //cat1_rc + 1 = 1
        CLCat *cat1 = [[CLCat alloc] init];
        
        //cat2_rc + 1 = 1
        CLCat *cat2 = [[CLCat alloc] init];
        
        //cat1_rc + 1 = 2
        [person setCat:cat1];
        
        //cat2_rc + 1 = 2
        [person setCat:cat2];
        
        //cat1_rc - 1 = 1
        [cat1 release];
        
        //cat2_rc - 1 = 1
        [cat2 release];
        
        //cat2_rc - 1 = 0
        //person_rc - 1 = 0
        [person release];
    }
    return 0;
}

**************** Print results ****************
2019-08-27 11:23:20.185060+0800 MRCManager[11164:667802] -[CLCat dealloc]
2019-08-27 11:23:20.185318+0800 MRCManager[11164:667802] -[CLPerson dealloc]
Program ended with exit code: 0

The print results show that cat1 has a memory leak. According to the tracing of retainCount objects in the code comment, it can be seen that when person sets cat2 as a member variable in the setCat method, cat1 is eventually release d less, which leads to leakage. So we need to adjust the setCat method as follows.

-(void)setCat:(CLCat *)cat {
    [_cat release];//Count - 1 of the object references pointed to by the previous_cat, not held
    [cat retain];//Ensure holding by counting the incoming object reference + 1
    _cat = cat;
}

Above, we solved the problem of setCat settings with different CLCat objects. Next, we need to see if setCat settings are safe with the same CLCat object. The code is as follows

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //person_rc + 1 = 1
        CLPerson *person = [[CLPerson alloc] init];
        
        //cat_rc + 1 = 1
        CLCat *cat = [[CLCat alloc] init];
        
        //cat_rc + 1 = 2
        [person setCat:cat];
        
        //cat_rc - 1 = 1
        [cat release];
        
        
        [person setCat:cat];
        
        
        [person release];
        
    }
    return 0;
}

The above code can safely go to the breakpoint shown in the following figureIf we continue executing, we will see the following error in the setCat methodYou can see that [cat retain] reported an EXC_BAD_ACCESS error, indicating that cat was released at this time. Let's analyse that the retainCount of the cat object is 1. When the cat object is passed to the setCat method again, because the person's _cat points to the cat, so [_cat release] actually causes the cat's retainCount-1, that is, 1-1 = 0, so the cat is released by the system. So the subsequent code uses [cat retain] again, which results in wild pointer errors. Therefore, the solution is to judge the cat object, if it is equal to the current _cat, there is no need to perform the operation of reference counting, modify the code as follows

-(void)setCat:(CLCat *)cat {
    if (cat != _cat) {//Only when the incoming object is different from the current object does subsequent operations need to be performed
        [_cat release];//Count - 1 of the object references pointed to by the previous_cat, not held
        [cat retain];//Ensure holding by counting the incoming object reference + 1
        _cat = cat;
    }
    
}

In this way, the setCat method is perfected. Next, I will post a complete code for your reference.

******************* CLPerson.h ******************
#import <Foundation/Foundation.h>
#import "CLCat.h"

@interface CLPerson : NSObject
{
    CLCat *_cat;
    int _age;
}
//Having a cat
-(void)setCat:(CLCat *)cat;
//Getting Cats
-(CLCat *)cat;
@end

******************* CLPerson.m ******************


#import "CLPerson.h"

@implementation CLPerson
-(void)setCat:(CLCat *)cat {
    if (cat != _cat) {//Only when the incoming object is different from the current object does subsequent operations need to be performed
        [_cat release];//Count - 1 of the object references pointed to by the previous_cat, not held
        [cat retain];//Ensure holding by counting the incoming object reference + 1
        _cat = cat;
    }
    
}

-(CLCat *)cat {
    
    return _cat;
}


-(void)dealloc {
    //I'm about to be released. I don't need cat anymore.
//    [_cat release];
//    _cat = nil;
    self.cat = nil;//Equivalent to the effect of the above two sentences
    
    [super dealloc];
    NSLog(@"%s",__func__);
}
@end


*****************CLCat.h ***************
#import <Foundation/Foundation.h>
@interface CLCat : NSObject
-(void)run;
@end

*****************CLCat.m ***************
#import "CLCat.h"
@implementation CLCat
-(void)run {
    NSLog(@"%s",__func__);
}

-(void)dealloc {
    [super dealloc];
    NSLog(@"%s",__func__);
}
@end

For member variables of non-OC object types, there is no need to consider memory management issues, such as

@interface CLPerson : NSObject
{
    int _age;
}
-(void)setAge:(int)age ;
-(int)age;
@end

@implementation CLPerson
-(void)setAge:(int)age {
    _age = age;
}

-(int)age {
    return _age;
}
@end

The above process includes all the core points of manual memory management in non-ARC era. Later, with the gradual development of Xcode, the compiler automatically generated a lot of code for us, making my code writing more concise.

Evolution of Compilers

(1)@property + @synthesize

@ Function of property (nonatomic, retain) cat: Automatically declare getter, setter methods

-(void)setCat:(CLCat *)age ;
-(CLCat *)cat;

@ Synthesize cat = cat; function:

  • Generate member variable_cat for attribute cat
{
   CLCat *_cat;
}
  • Implementation of Automatic Generation of getter and setter
-(void)setCat:(CLCat *)cat {
   if (cat != _cat) {
       [_cat release];
       [cat retain];
       _cat = cat;
   }
}

-(CLCat *)cat {
   return _cat;
}

(2)@property + @synthesize

Later, Apple went a step further. Even @synthesize didn't need us to write it. An @property fixes it.

  • Creation of member variables
  • getter, setter method declaration
  • Implementation of getter and setter methods

But it's important to note that @property doesn't help me with dealloc processing (releasing unnecessary member variables), so dealloc methods still need to be written manually.

In fact, in the ARC era, the underlying mechanism of iOS memory management has not changed, but a lot of memory management code ARC (compiler) is written for us automatically. Okay, so that's how the ancient MRC manual memory management was introduced.

Posted by nileshkulkarni on Thu, 29 Aug 2019 22:39:37 -0700