【iOS】ARC implementation

ARC is implemented by the following tools:

  • clang (LLVM compiler) 3.0 or above
  • objc4 Objective-C runtime library 493.9 or above

Next, we will explore the ARC implementation around the clang assembly output and the source code of the objc4 library.

1. __strong modifier

1.1 Assignment to a variable with the __strong modifier

Look at the code below

{
    
    
	id __strong obj = [[NSObject alloc] init];
}

In fact, this source code can be transformed into calling the following function.

// 编译器的模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);

As shown in the original source code, the objc_msgSend method is called twice, and the object is released through objc_release when the variable field ends.

Although the release method cannot be used when ARC is valid, it can be seen that the compiler automatically inserts release.

1.2 Use methods other than alloc/new/copy/mutableCopy

{
    
    
	id __strong obj = [NSMutableArray array];
}

The conversion is as follows:

// 编译器的模拟代码
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

The objc_retainAutoreleasedReturnValue() function is mainly used to optimize program operation. It is used for functions that hold (retained) objects themselves, but the object they hold should be a method that returns an object registered in the autoreleasepool or the return value of a function. Like this source code, when calling methods other than alloc/new/copy/mutableCopy, the compiler inserts this function.

Let’s take a look at the source code of the array method of the NSMutableArray class:

+ (id)array {
    
    
	return [[NSMutableArray alloc] init];
}

The following is a transformation of the source code:

+ (id)array 
{
    
    
	id obj = objc_msgSend(NSMutableArray, @selector(alloc));
	objc_msgSend(obj, @selector(init));
	return objc_autoreleaseReturnValue(obj);
}

The objc_autoreleaseReturnValue() function is used to implement objects returned by methods other than the alloc/new/copy/mutableCopy method. Like this source code, returns the object registered in the autoreleasepool. However, the objc_autoreleaseReturnValue function is different from the objc_autorelease function and is generally not limited to registering objects into the autoreleasepool.

In the above example, the objc_autoreleaseReturnValue() function will check the execution command list of the method or function caller using the function. If the function caller calls the objc_retainAutoreleasedReturnValue() function immediately after calling the method or function, then it will not return The object is registered in the autoreleasepool and is passed directly to the caller of the method or function.

The objc_retainAutoreleasedReturnValue() function is different from the objc_retain function. Even if the object is returned without registering it in the autoreleasepool, the object can be obtained correctly.

Through the cooperation of the above two functions, the object can be transferred directly without registering it in the autoreleasepool. This process has been optimized.

Insert image description here

2. __weak modifier

2.1 Assign a value to a variable with the __weak modifier;

{
    
    
	id _weak obj1 = obj;
}

Suppose the variable obj is appended with the __strong modifier and the object is assigned.

// 编译器的模拟代码
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);

After the objc_initWeak function initializes the variable with the __weak modifier to 0, it will call the objc_storeWeak function with the assigned object as a parameter.

The objc_destroyWeak function calls the objc_storeWeak function with 0 as a parameter.

That is, the previous source code is the same as the following source code:

// 编译器模拟代码
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);

The objc_storeWeak function uses the assignment object of the second parameter as the key value and registers the address of the first parameter (the variable with the __weak modifier) ​​into the weak table. If the second parameter is 0, the address of the variable is deleted from the weak table.

A weak table is the same as a reference counted table, implemented as a hash table. By retrieving the address of the discarded object as a key value, you can quickly obtain the address of the corresponding __weak modifier variable. For a key value, the addresses of multiple variables can be registered.

2.2 The program’s actions when discarding an object that no one holds;

(1) objc_release
(2) dealloc is executed because the reference count is 0
(3) _objc_rootDealloc
(4) object_dispose
(5) objc_destructInstanse
(6) objc_clear_deallocating

The actions of the objc_clear_deallocating function are as follows:

  1. Get the address of the discarded object from the weak table as the key value;
  2. Assign nil to all addresses of variables with the __weak modifier included in the record;
  3. Delete the record from the weak table;
  4. Delete the record whose address is the key value of the abandoned object from the reference counting table.

Through the above steps, the function of assigning nil to the variable is achieved when the object referenced by the variable with the __weak modifier is discarded.

It can be seen that if a large number of variables with the __weak modifier are used, corresponding CPU resources will be consumed. So we only use the __weak modifier when we need to avoid circular references.

2.3 Assign the object generated and held by yourself to the variable with the __weak modifier;

{
    
    
	id __weak obj = [[NSObject alloc] init];
}

Because a variable with the __weak modifier cannot hold an object, the object will be released and discarded, thus causing a compiler warning.
Insert image description here

Let's see what the compiler does with this source code.

// 编译器的模拟代码
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_masSend(tmp, @selector(init));
objc_initWeak(&obj, tmp);
objc_release(tmp);
objc_destroyWeak(&object);

Although the object generated and held by itself is assigned to a variable with the __weak modifier through the objc_initWeak function, the compiler determines that it has no holder, so the object is immediately released and discarded through the objc_release function.

2.4 Others

As described in the book "Objective-C Advanced Programming", under ARC, using a variable with the __weak modifier is to use an object registered in the autoreleasepool. In fact, it may be because the version I have is too old and has gone through too many version changes. I have verified that, at least in Xcode13.3.1, this statement no longer applies.

Look at the source code below:

    @autoreleasepool {
    
    
        id obj = [[NSObject alloc] init];
        
        {
    
    
            id __weak o = obj;
            NSLog(@"%@", o);
            NSLog(@"%@", o);
            NSLog(@"%@", o);
            _objc_autoreleasePoolPrint();
        }
    }

The source code runs as follows:Insert image description here

It can be seen that although the variable with the __weak modifier is used multiple times, the object is not registered in the autoreleasepool.

Modified slightly:

    @autoreleasepool {
    
    
        id obj = [[NSObject alloc] init];
        
        {
    
    
            id __weak o = obj;
            id __autoreleasing tmp = o;
            NSLog(@"%@", o);
            NSLog(@"%@", o);
            NSLog(@"%@", o);
            _objc_autoreleasePoolPrint();
        }
    }

The running results are as follows:
Insert image description here

As you can see, the object is only id __autoreleasing tmp = o;registered in the autoreleasepool at runtime.

3. __autoreleasing modifier

Assigning the object to a variable with the __autoreleasing modifier is equivalent to calling the object's autorelease method when ARC is invalid.

3.1 Assign a value to a variable with the __autoreleasing modifier;

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

The following transformations can be made:

// 编译器模拟代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

Although ARC behaves differently on the source code when it is enabled and disabled, the functionality of autorelease is exactly the same.

3.2 Use methods other than alloc/new/copy/mutableCopy

    @autoreleasepool {
    
    
        id __autoreleasing obj = [NSMutableArray array];
    }

The following transformations can be made:

// 编译器的模拟代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutireleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

Although the method of holding the object has changed from the alloc method to the objc_retainAutireleasedReturnValue function, the method of registering the autoreleasepool is still the objc_autorelease function.

4. How ARC works

The working principle of ARC is roughly this: when we compile source code, the compiler will analyze the life cycle of each object in the source code, and then add corresponding reference counting operation codes retain and release in appropriate places based on the life cycle of these objects. and autorelease.

ARC is a technical solution that works at compile time. The benefits are:

  1. After compilation, there is no difference between ARC and non-ARC code, so the two can coexist in the source code. In fact, you can turn off the ARC feature of some source codes by compiling the parameter -fno-objc-arc.
  2. Compared with memory management solutions such as garbage collection, ARC does not bring additional runtime overhead, so it will not affect the running efficiency of the application. On the contrary, because ARC can deeply analyze the life cycle of each object, it can be more efficient than manually managing reference counting. For example, in a function, if there is an operation of +1 reference count for an object at the beginning, and then there is an operation of -1, then the compiler can optimize out both operations.

What does ARC do at compile time and run time?

  1. During compilation, ARC will simplify the retain, release, and autorelease operations that cancel each other out.
  2. ARC contains a runtime component that can detect the redundant operations of autorelease and retain at runtime. In order to optimize the code, a special function is executed when returning an automatically released object in a method.

Guess you like

Origin blog.csdn.net/m0_63852285/article/details/131783635