Memory management portal 🦋🦋🦋

Memory management analysis (two) – timer problem

Memory management analysis (three) – iOS program memory layout

Memory management analysis (four) — Autorelease principle analysis

Memory management analysis (5) — weak pointer implementation principle

Manual memory management in the MRC era

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

  • The default reference count of a newly created OC object is 1. When the reference count is reduced to 0, the OC object is destroyed and its memory is freed by the system.
  • callretainWill make the reference count of the OC object +1, calledreleaseMakes the reference count of the OC object -1.

Principles of memory management

  • When callingalloc,new,copy,mutableCopyMethod returns an object that is no longer neededreleaseorautoreleaseTo release it.
  • If you want to own an object, make its reference count +1. If you don’t want to own an object, make its reference count -1.

You can use the following private functions to see how the pool is automatically freed

extern void _objc_autoreleasePoolPrint(void);

Let’s analyze a wave of cases

//********* 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
Copy the code

We create an instance of CLPerson by alloc in main.m and print it with a reference count of 1.

[10928:615055] retainCount ----- 1Copy the code

And you don’t see person calling the dealloc method, indicating that person is not freed after main ends. So after we use person, we add a release, like this

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

The print result this time is

2019- 0827 - 09:12:45.362995+0800 MRCManager[10928:615055] retainCount ----- 1
2019- 0827 - 09:12:45.363226+0800 MRCManager[10928:615055] -[CLPerson dealloc]
Copy the code

As you can see, the person uses the Dealloc method and is successfully released because the system uses the release method to make its reference count -1, 1-1 = 0 and then releases the person based on that reference count. The principle of OC memory management is very simple.

We know that the Mac command line project, main, runs from top to bottom, linearly, and when you return 0, the whole program exits and you’re done, so it’s easy to tell when to release an object like we did in our case.

In a typical iOS project, with RunLoop, the app will loop through the main function until it crashes or closes the app manually. It is therefore difficult to determine when an object will be used once it is created. If release is not called, the object is safe to use at any time, but the problem is that when the object is no longer in use, it will remain in memory and will not be released. This is often referred to as ** *.

To do this, Apple provides us with an AutoRelease, which is called every time an object is created

CLPerson *person = [[[CLPerson alloc] init] autorelease];
Copy the code

This way, we don’t need to manually call [Person Release]; The system will automatically release the person at an appropriate time when the @Autoreleasepool {} braces end.

#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 is finished);
    return 0;
}

//********************** Prints information *******************
2019- 0827 - 09:40:29.388495+0800 MRCManager[10970:625654]  @autoreleasepoolIs coming to an end2019- 0827 - 09:40:29.388727+0800 MRCManager[10970:625654] - [CLPerson dealloc]
2019- 0827 - 09:40:29.388736+0800 MRCManager[10970:625654] - [CLPerson dealloc]
2019- 0827 - 09:40:29.388756+0800 MRCManager[10970:625654]  @autoreleasepoolProgram ended with exit code:0
Copy the code

The above example deals with the simple case of a single object. In actual iOS projects, there are often many associations between objects. Let’s continue adding a CLCat object to the code above

#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
Copy the code

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;
}
/ / have a cat- (void)setCat:(CLCat *)cat;
/ / get a cat- (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
Copy the code

This allows the CLCat object to be set to the _cat member variable of CLPerson using the setCat method. Get the member variable _cat using the cat method. This means that a 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; } ***************** The result is ****************2019- 0827 - 10:22:11.086033+0800 MRCManager[11054:643966] - [CLCat run]
2019- 0827 - 10:22:11.086283+0800 MRCManager[11054:643966] - [CLCat dealloc]
2019- 0827 - 10:22:11.086294+0800 MRCManager[11054:643966] - [CLPerson dealloc]
Program ended with exit code: 0

Copy the code

From the print, it works. But notice [person.cat run]; Is in [cat release]; Before. If I go like this

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;
}
Copy the code

It’s going to look like thisThe reason for the error is shown in the figure, so just make sure[cat release];in [person.cat run];Then call it. But when the cards are actually issued,[person.cat run];It’s indeterminate when it’s called, and it’s indeterminate how many times it’s called, which means we can’t be sure[person.cat run];It is not certain when and where it was last called[cat release];Exactly where to put it. To make sure they don’t show upEXC_BAD_ACCESSIf you report an error, don’t write it at all[cat release];But this bringsA memory leakThe problem. The essence of the problem is thatCLPersonI don’t really own itCLCat. By “real” possession, I mean as long asCLPersonStill there, thenCLCatShould never have been released. So that’s what we can do

#import "CLPerson.h"

@implementation CLPerson- (void)setCat:(CLCat *)cat {
    [_cat retain];// Add a reference count of +1_cat = cat; } - (CLCat *)cat {
    
    return_cat; } - (void)dealloc {
    // I'm about to be released, and I don't need cat anymore
    [_cat release];
    _cat = nil;
    
    [super dealloc];
    NSLog(@"%s",__func__);
}
@end
Copy the code

This will not cause problems even if multiple CLPerson objects are using CLCat

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 the above [CLCat alloc]
        [cat release];
        
        [person.cat run];
        [person2.cat run];
        [person release];
        [person2 release];
        
    }
    return 0;
}
Copy the code

The taincount of CLCat must be zero before the last CLPerson instance is released (meaning CLCat is no longer needed). Can be released) is set to 0 and released successfully. The results can also be verified

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

In general, the principle of manual memory management is to keep the retainCount balance for instance objects, with a +1 for a -1, and ensure that the object eventually becomes 0 and can be freed successfully.

So far, the above treatment of setCat method is still not perfect. Let’s continue to discuss the details. First look at 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; } **************** The result is ****************2019- 0827 - 11:23:20.185060+0800 MRCManager[11164:667802] - [CLCat dealloc]
2019- 0827 - 11:23:20.185318+0800 MRCManager[11164:667802] - [CLPerson dealloc]
Program ended with exit code: 0
Copy the code

The print shows that CAT1 has a memory leak. According to the tracking of the retainCount of each object in the code comments, it can be seen that when Person sets cat2 as a member variable in the setCat method, cat1 eventually makes one less release, resulting in leakage. Therefore, the setCat method needs to be adjusted as follows

- (void)setCat:(CLCat *)cat {
    [_cat release];// Reference the object previously pointed to by _cat to count -1 and no longer hold it
    [cat retain];// The passed object reference counts +1, guaranteed to hold
    _cat = cat;
}
Copy the code

Above we solved the problem of setCat setting with different CLCat objects. Next we need to see if it is safe to use the same CLCat object for setCat setting

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;
}
Copy the code

The code above can safely walk to the breakpoint shown belowIf we go ahead, we’ll be insetCatThe following error appears in the methodYou can see[cat retain]To quote theEXC_BAD_ACCESSError, descriptioncatAt this point they’ve been released. So let’s analyze it. The way to get into this is,catThe object’sretainCountis1When the cat object is passed againsetCatAnd the way to do that is becausepersonthe_catIt also points tocat, so[_cat release]It actually leads tocattheretainCount-1, that is,1-1 = 0, socatReleased by the system. So the following code is used again[cat retain]The result is a wild pointer error. The solution therefore needs to be communicatedcatObject to determine if is equal to the current_cat, there is no need to perform the reference counting operation, modify the code as follows

- (void)setCat:(CLCat *)cat {
    if(cat ! = _cat) {// Only if the object passed in is different from the current object
        [_cat release];// Reference the object previously pointed to by _cat to count -1 and no longer hold it
        [cat retain];// The passed object reference counts +1, guaranteed to hold_cat = cat; }}Copy the code

In this way, the setCat method of processing put ah is perfect. Below I post a complete code for reference

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

@interface CLPerson : NSObject
{
    CLCat *_cat;
    int _age;
}
/ / have a cat- (void)setCat:(CLCat *)cat;
/ / get a cat- (CLCat *)cat;
@end* * * * * * * * * * * * * * * * * * *CLPerson.m ******************


#import "CLPerson.h"

@implementation CLPerson- (void)setCat:(CLCat *)cat {
    if(cat ! = _cat) {// Only if the object passed in is different from the current object
        [_cat release];// Reference the object previously pointed to by _cat to count -1 and no longer hold it
        [cat retain];// The passed object reference counts +1, guaranteed to hold
        _cat = cat;
    }
    
}

-(CLCat *)cat {
    
    return_cat; } - (void)dealloc {
    // I'm about to be released, and I don't need cat anymore
// [_cat release];
// _cat = nil;
    self.cat = nil;// The effect of the above two sentences is similar
    
    [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
Copy the code

For member variables that are not OC object types, there is no need to worry about memory management, for example

@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
Copy the code

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

Evolution of compilers

(1)@property + @synthesize

@property (nonatomic, retain) cat; The getters and setters function automatically declares getter and setter methods

- (void)setCat:(CLCat*)age ; - (CLCat *)cat;
Copy the code

@synthesize cat = _cat; The role of:

  • For attributescatGenerating member variables_cat
{
   CLCat *_cat;
}
Copy the code
  • Automatically generategetter,setterMethod implementation
- (void)setCat:(CLCat *)cat {
   if(cat ! = _cat) { [_cat release]; [catretain];
       _cat = cat;
   }
}

-(CLCat *)cat {
   return _cat;
}
Copy the code
(2)@property + @synthesize

And then Apple went one step further, and they didn’t even have to write the at sign synthesize, an at sign property

  • Creation of a member variable
  • getter,setterMethod statement
  • getter,setterMethod implementation

But it’s important to note that,@propertyDon’t do it for medeallocThe handler inside (releases member variables that are not needed), thereforedealloc The method still needs to be written manually.

Well, the ancient MRC manual memory management is introduced here.

Memory management portal 🦋🦋🦋

Memory management analysis (two) – timer problem

Memory management analysis (three) – iOS program memory layout

Memory management analysis (four) — Autorelease principle analysis

Memory management analysis (5) — weak pointer implementation principle