IOS Memory Management

Memory allocation in Objective-C

In Objective-C, objects are typically created on the heap using the alloc method. The [NSObject alloc] method allocates a chunk of memory on the heap, filling it with the internal structure of NSObject.

Once the object is created, it is impossible to move it. Because there are probably a lot of Pointers to this object that aren’t being traced. So there is no way to update all of these Pointers after moving the position of the object.

MRC and ARC

Objective-c provides two memory management mechanisms, MannulReference Counting (MRC) and Automatic Reference Counting (ARC), which respectively provide manual and Automatic memory management to meet different requirements. Apple now recommends using ARC for memory management.

MRC

Four categories of object operations

The object operation Corresponding method in OC Corresponding retainCount changes
Generate and hold objects Alloc/new/copy/mutableCopy, etc + 1
Hold the object retain + 1
Release object release – 1
Discarded objects dealloc

Four principles

  • Self generated objects, own.
  • Objects that are not generated by themselves can be held by themselves.
  • Release objects when you no longer need to hold them yourself.
  • Objects that are not owned by the owner do not need to be released.

Here is an example of code for the four golden Rules:

*/ id obj0 = [[NSObeject alloc] init]; id obj1 = [NSObeject new];Copy the code
/* * hold object not generated */ id obj = [NSArray array]; [obj retain]; // Own the objectCopy the code
*/ obj = [[NSObeject alloc] init]; // hold object [obj release]; // Free object /* * The pointer to the object is still kept in the variable obj * but the object has been freed and cannot be accessed */Copy the code
/* * obj = [NSArray array]; /* * obj = [NSArray array]; [obj release]; [obj release]; // ~~~ In this case, the compiler will report issues when crash or error~~~ is not ARC. The behavior of this operation is undefined and may cause runtime crash or other unknown behaviorCopy the code

Where the object is not generated by itself, and the object exists, but does not own this feature is implemented using autoRelease, the example code is as follows:

- (id) getAObjNotRetain { id obj = [[NSObject alloc] init]; // own object [obj autoRelease]; // The obtained object exists, but you do not own itreturn obj;
}
Copy the code

Autorelease makes it possible for objects to be released correctly after they have expired (by calling the Release method). When release is called, the object is released immediately. When AutoRelease is called, the object is not released immediately. Instead, the object is registered in autoReleasepool. In the absence of an Autorelease Pool, the Autorelease object is released at the end of the current runloop iteration, The reason it can be released is that the automatic release pool Push and Pop(Page26 AutoreleasePoolPage) are added to each runloop iteration.

In MRC memory management mode, the methods associated with managing variables are retain, release, and Autorelease. The retain and release methods operate on the reference count, and when the reference count reaches zero, memory is automatically freed. And you can use the NSAutoreleasePool object to manage variables that are added to the autorelease pool (autorelease calls) and reclaim memory when drain.

Think about:

  1. Does the autoRelease method affect reference counting? If so, how?
  2. Will all generated objects in the OC be added to the automatic release pool?

Answer:

  1. The autoRelease method does not affect the reference count of an object.
	- (id)autorelease {
		[NSAutoreleasePool addObject:self];
	}
Copy the code

Autoreleasepool also explains how to free memory later in this article.

2. The obvious answer is no.

ARC

ARC is an automatic memory management mechanism introduced by Apple that automatically monitors the lifetime of objects based on reference counts by automatically inserting appropriate memory management code into existing code at compile time and making some optimizations at Runtime.

Variable identifier

Variable identifiers associated with memory management in ARC include the following:

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

__strong is the identifier used by default. As long as there is a strong pointer to an object, that object will always exist.

__weak states that this reference does not keep the referenced object alive, and if the object has no strong references, weak references are set to nil

__unsafe_unretained statement This reference does not keep the referenced object alive. If the object is not strongly referenced, it is not set to nil. If the object it references is reclaimed, the pointer becomes a wild pointer (dangling pointer).

__autoreleasing identifies parameters (ID *) that are passed by reference and is automatically released when the function returns.

Variable identifiers can be used as follows:

Number* __strong num = [[Number alloc] init]; Note that __strong should be placed between * and the variable name. Placing it anywhere else is technically incorrect, but the compiler does not report an error.

Attribute identifier

Attributes in a class can also be tagged with an identifier:

@property (assign/retain/strong/weak/unsafe_unretained/copy) Number* num
Copy the code

Assign indicates that a setter is just a simple assignment operation, and is usually used for basic numeric types, such as CGFloat and NSInteger.

Strong indicates that the property defines an owner relationship. When assigning a new value to a property, the value is retained, the old value is released, and then the assignment is performed.

Weak indicates that the attribute defines a non-owner relationship. When assigning a new value to a property, the value is not retained and the old value is not released. Instead, an operation similar to assign is performed. But the property is set to nil when the object it points to is destroyed.

Unsafe_unretained is similar to the semantics of assign, but applies to an object type, meaning an unowned, and non-nil relationship across destruction.

Copy is similar to strong, but does a copy operation instead of a retain operation on assignment. This is usually used when you want to preserve an immutable object (NSString is the most common) and prevent it from being accidentally changed.

@interface ViewController ()

@property (nonatomic, copy) NSString *Str1;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    NSString *str2 = @"copyTest";
    self.Str1 = str2;
    
    str2 = @"changeTest";
    NSLog(@"% @", self.Str1);
}
Copy the code

Consequences of incorrect use of attribute identifiers

If we set strong\weak\copy to a primitive type, the compiler will report an error:

Property with ‘retain (or strong)’ attribute must be of object type

Unsafe_unretained can be compiled, but it works the same as assigned.

Instead, we assign an NSObject property, and the compiler alerts us:

Assigning retained object to unsafe property; object will be released after assignment

As the warning says, when the object is released immediately after the assignment, the corresponding property becomes a wild pointer, and the operation related to the property will crash at runtime. It works the same as unsafe_unretained (weak won’t crash).

The usefulness of unsafe_unretained

Unsafe_unretained is almost the least used identifier in practice. Its uses are as follows:

  1. Compatibility considerations. Weak was not introduced in iOS4 or earlier, so you can only use unsafe_unretained weak references. That’s rare now.
  2. Performance considerations. useweakIt has some impact on performance, so it can be used in places with high performance requirementsunsafe_unretainedreplaceweak. One example isYYModelThe implementation, in pursuit of higher performance, is used extensively among themunsafe_unretainedAs a variable identifier.

Reference cycle

A reference loop is created when two objects hold strong references to each other, and neither object has a reference count of zero.

To break the reference loop, do the following:

  • Note the variable scope and use autoRelease to let the compiler handle references
  • Using weak references
  • When the instance variable is done, set it to nil

Autorelease Pool

Autorelase Pool provides a mechanism that allows you to delay sending release messages to an object. The Autorelease Pool comes in handy when you want to give up ownership of an object but don’t want the object to be released immediately (for example, when you return an object in a method).

Delayed release messages refer to when we mark an object as autoRelease:

NSString* str = [[[NSString alloc] initWithString:@"hello"] autorelease];
Copy the code

The retainCount for this object will be +1, but no release will occur. When the autoreleasepool on which this statement is based applies drain, the retainCount of all objects marked autoRelease will be -1. The release message is delayed until the pool is released.

In the ARC environment, Apple introduced the @Autoreleasepool syntax, eliminating the need to manually call methods like AutoRelease and drain.

The use of Autorelease Pool

In ARC, memory can be managed properly without having to manually call autoRelease methods or even knowing that autoRelease exists. This is because Cocoa Touch runloops automatically create and release Autorelease pools in each Runloop circle.

When you need to create and destroy a large number of objects, using a manually created AutoReleasepool can effectively avoid memory spikes. Drain is created by the outer system after the entire runloop circle ends, whereas drain is created by the outer system after the block ends. Please refer to apple’s official documentation for details. A commonly used example is as follows:

for (int i = 0; i < 100000000; i++)
{
    @autoreleasepool
    {
        NSString* string = @"abc"; NSArray* array = [string componentsSeparatedByString:string]; }}Copy the code

If you do not use AutoReleasepool, you need torelease 100 million strings at the end of the loop. If you use AutoReleasepool, you release at the end of each loop.

The time when Autorelease Pool drains

As mentioned above, autoReleaspool created by the system in Runloop will be released when an event in Runloop ends. Autoreleasepool, which we created manually, does the drain operation after the block completes. Note that:

  • When a block ends with an exception, the pool is not drained
  • Pool drain reduces the reference count of all objects marked as autorelease by one, but this does not mean that the object will be released. We can manually retain objects in the autoRelease Pool, To extend its life cycle (in MRC).

Autorelease Pool in main.m

Everyone knows that in the main. M file of an iOS application there is a statement like this:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        returnUIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

If you ask about autorelease pools in an interview, you will probably ask what the function of the pool is and whether it can be removed. Let’s analyze it here.

According to Apple’s official documentation, UIApplicationMain is the entry point to the entire app and is used to create application objects (singletons) and application delegates. Although this function returns a value, it never actually returns, and when the Home button is pressed, the app is simply switched to the background state.

Also referring to Apple’s official documentation on Lifecycle, UIApplication itself will create a main run loop and we can conclude roughly as follows:

  1. The UIApplicationMain in main.m never returns, and only frees up memory when the entire app is killed.
  2. Because (1) UIApplicationMain never returns, the AutoRelease pool never gets to the release stage;
  3. On the basis of (2), assuming that some variables actually enter the pool in main.m (not captured by a deeper pool), then these variables are actually leaked. The autoRelease pool hides the leak;
  4. UIApplication creates its own main runloop, and Cocoa runloops actually automatically contain autoRelease pools, so the pool in main.m is considered unnecessary;

In Mac OS development based on AppKit framework, there is no autorelease pool in main.m, which further verifies our conclusion. However, since we can’t see the lower-level code, and apple’s documentation doesn’t recommend changing main.m, there’s no reason to just delete it.

Autorelease Pool and function return value

If the return value of a function is a pointer to an object, the object must not be released before the function returns, so that the caller gets a wild pointer when the function is called, and cannot release immediately after the function returns. Because we don’t know whether the caller retained the object or not, if we release it directly, it might become nil later when we use it.

Retained return value and unretained return value are different across and across object Pointers in Objective-C. The former means that the caller owns the return value, and the latter means that the caller does not own the return value. According to the principle of “who owns who releases”, the caller is responsible for releasing the former, but does not need the latter.

Across return value, a method that starts with methods like alloc, copy, init, mutableCopy, and new returns the same as apple’s naming convention. For example, [[NSString alloc] initWithFormat:], and others are unretained return value, such as [NSString stringWithFormat:]. We should follow this convention when we write code.

Let’s examine the differences between the two return value types in the MRC and ARC cases respectively.

MRC

We need to pay attention to the difference between these two function return types in MRC, otherwise we may cause memory leaks.

Across return value, you need to release it

Suppose we have a property defined as follows:

@property (nonatomic, retain) NSObject *property;
Copy the code

When assigning to it, we should use:

self.property = [[[NSObject alloc] init] autorelease];
Copy the code

Then add to the dealloc method:

[_property release];
_property = nil;
Copy the code

Memory looks something like this:

1. Init increments retain count to 1 Assign to self.property and increase retain count to 2 3. When the runloop circle ends, the autorelease pool executes drain to reduce retain count to 1 4. When the entire object is dealloc, release reduces retain count to 0 and the object is released 5. You can see that no memory leaks have occurred.Copy the code

If we just use:

self.property = [[NSObject alloc] init];
Copy the code

This statement would cause retain count to increase to 2, and one less release would result in retain count not being reduced to 0.

Alternatively, we can use temporary variables:

NSObject * a = [[NSObject alloc] init];
self.property = a;
[a release];
Copy the code

In this case, the retain count cannot be reduced to 0 because a release was performed on A.

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

When writing our own code, we should also follow the above principle and use autoRelease for factory method methods:

+ (MyCustomClass *) MyCustomClass {return[[[MyCustomClass alloc] init] autorelease]; // autorelease is required - (MyCustomClass *) initWithName:(NSString *) name {// autorelease is not requiredreturn[[MyCustomClass alloc] init]; / / for unretainedreturnValue does not need to be responsible for releasing}Copy the code

When we call a non-alloc init method to initialize an object (usually a factory method), we are not responsible for releasing the variable and can use it as a normal temporary variable:

NSString *name = [NSString stringWithFormat:@"% @ % @", firstName, lastName]; Self.name = name // No need to execute [name release]Copy the code

ARC

In ARC we don’t need to worry about the difference between the two return value types. ARC automatically adds the necessary code, so we can safely write:

self.property = [[NSObject alloc] init];
self.name = [NSString stringWithFormat:@"% @ % @", firstName, lastName];
Copy the code

And in my own function:

+ (MyCustomClass *) myCustomClass {
    return[[MyCustomClass alloc] init]; // no autorelease}Copy the code

Across return value, Clang does this:

When returning from such a function or method, ARC retains the value at the point of evaluation of the return statement, before leaving all local scopes.

When receiving a return result from such a function or method, ARC releases the value at the end of the full-expression it is contained within, subject to the usual optimizations for local values.

ARC basically helps us release at the end of the block:

NSObject * a = [[NSObject alloc] init]; self.property = a; //[a release]; We don't need to write this sentence, because ARC will add this sentence for usCopy the code

For unretained return value:

When returning from such a function or method, ARC retains the value at the point of evaluation of the return statement, then leaves all local scopes, and then balances out the retain while ensuring that the value lives across the call boundary. In the worst case, this may involve an autorelease, but callers must not assume that the value is actually in the autorelease pool.

ARC performs no extra mandatory work on the caller side, although it may elect to do something to shorten the lifetime of the returned value.

This is not exactly what we did in MRC. ARC extends the life of the object to make sure that the caller can get and use the return value, but autoRelease is not always used, the document is only used in worst case cases, Therefore, the caller cannot assume that the return value is actually in the AutoRelease pool. This is also understandable from a performance perspective. If we knew how long an object should live, we wouldn’t need to use AutoRelease. We could just use Release. If many objects use autoRelease, the entire pool can degrade its performance at drain.