1. Class extensions

Extension is a special form of classification.

1. Use characteristics

  • Can be called special classification, also known as anonymous classification
  • You can add member attributes to a class, but they are private variables
  • You can add methods to a class, also private methods

2. Usage

See the following code:

If the class extension is placed after the class implementation, the compiler will compile, as shown in the following figure:

The contents of a class extension are private, that is, the outside world is agnostic. We usually put methods, attributes, and so on in the class declaration that we want to expose to the outside world; Put private, unknowable methods, properties, and so on in a class extension.

3. Underlying implementation principles

Clang can be used to compile m files into CPP files, so we can learn more about the underlying implementation principles of class extension. Introduce a case, see below:

After clang is compiled, continue exploring class extensions! Open the compiled.cpp file and analyze the properties and methods, respectively.

  1. Properties. Search for the ext_age member variable in the class extension. In the class implementation, it is found that the related attributes and member variables of the class extension have been placed in the class implementation at compile time, along with the attributes and member variables in the class declaration. See below:

  2. Methods. In the.cpp file, the properties in the class extension automatically generate get and set methods, and in addition to the class methods, other object methods are also placed in the class during compilation. See below:

From the CPP file analysis above, we can conclude that the relevant content of the class extension is stored in the class at compile time.

4. Source tracking verification

In the process of class loading, the system will get the data corresponding to the class from the MachO file generated by compilation, and install it into class_ro_t, and assign the value to the created class_rw_t. If the ro data obtained includes information about the class extension, then we can be sure that the class extension was put into the class at compile time.

Set the class to non-lazy load and run the program, as shown below:

Classification of 2.

Adding method attributes to an already encapsulated class (system class, third party class) without changing the content of the class can be done by adding a category.

Divide a large bloated class into several different categories and implement the method of category declaration in different categories. In this way, the implementation of a class can be written to multiple.m files to facilitate management and collaborative development.

1. Use characteristics

  • Used specifically to add new methods to a class
  • You cannot add a member attribute to a class. If you add a member variable, you cannot fetch it
  • Note: It can actually passruntimeAdd attributes to the category
  • Classification using@propetryIf you define variables, only variables will be generatedgetter.setterMethod declaration that cannot generate method implementations and underlined member variables

2. Usage

See the following code:

    @interface LGPerson (LG)

    @end

    @implementation LGPerson (LG)

    @end
Copy the code
  • The file name is usually: main class name + class name
  • When a method is invoked, you simply send a message to the main class reference

3. Function verification

Category is a common syntax feature in Objective-C that makes it easy to add functions to existing classes. But categories do not allow you to add new attributes or member variables to existing classes. Create a class for LGPerson and add a property to the class as shown in the following code:

    @interface LGPerson (LGN)
    @property (nonatomic, copy) NSString * cate_name;
    @end
Copy the code

The cate_name set method is called, but it is compiled successfully. The cate_name set method is declared, but the cate_name set method is declared, and the cate_name member variable is not generated. See below:

4. Associated objects

The common approach is to access and generate the associated object by objc_getAssociatedObject and objc_setAssociatedObject in Runtime. h to simulate the generated properties. See the following code:

@interface LGPerson (LG)

@property (nonatomic, copy) NSString * cate_name;

@end

static const void *NameKey = &NameKey;

@implementation LGPerson (LG)

- (NSString *)cate_name{
    return objc_getAssociatedObject(self, NameKey);
}

- (void)setCate_name:(NSString *)cate_name{
    objc_setAssociatedObject(self, NameKey, cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

@end
Copy the code

In this case, setting cate_name and obtaining cate_name will not cause error.

3. Implementation principles of the associated object

  • Before looking at the implementation principle of the associated object, let’s first think about how to deal with such a function if we complete it ourselves.

    Maintaining a common data store area, such as a hashMap, with different data stores for each class, since a class may set multiple properties, the storage space is a two-dimensional hashMap.

Enter the source code below to explore its implementation principle. Let’s start by setting the associated objects, as shown in the following code:

Objc_setAssociatedObject calls the _object_set_associative_Reference method, which isolates the API from the application layer. If the underlying API changes, the application layer will not be affected. Look at the _object_set_associative_reference implementation. The red box area is the focus of our research, as shown in the following figure:

The _object_set_associative_reference method has three important parameters:

  • objectThe object to set, here refers toLGPerson object
  • key, to set the propertieskeyvalue
  • value, the value corresponding to the attributevalue

1. Object encapsulation

First, the DisguisedPtr constructor is called to encapsulate object in the DisguisedPtr data type. Implementation code:

    DisguisedPtr<objc_object> disguised{(objc_object *)object};
Copy the code

DisguisedPtr class, see the following figure:

2. Value encapsulation

Encapsulate value with the following code:

    ObjcAssociation association{policy, value};
Copy the code

Encapsulate value into an ObjcAssociation object, as shown below:

What is the underlying data structure so far?

  • object -> DisguisedPtr
  • value -> ObjcAssociation

3. AssociationsManager and AssociationsHashMap

AssociationsManager, as a manager of the process of dealing with associated objects, is used to process a global hashMap, which is AssociationsHashMap. AssociationsHashMap stores all the associated data of objects.

  1. AssociationsManager provides a constructor and a destructor, as shown in the following figure:

    • Constructor, mainly used to create an object, and complete some of the object properties initialization operations.
    • Destructor, whose main function is to free resources and avoid memory leaks.The destructorwithThe constructorInstead, the destructor is executed automatically when an object ends its life cycle. Destructors are often used to doClean upIn the work.

    Here’s a simulation of how to use a constructor and destructor, as shown below:

    After program execution completes, the declaration cycle of selfC ends, and the system automatically executes its destructor.

  2. Is AssociationsManager a singleton?

    To verify this problem, we can modify the source code, on this basis to create several AssociationsManager, output the address of AssociationsManager, if the address is the same, it is a singleton, otherwise it is not a singleton. See below:

    Sorry, it failed to run successfully, what is the reason? Look at the AssociationsManager constructor, which is locked during use. When the destructor is not called to release the lock, the lock is repeatedly called, resulting in collapse. Remove the lock in the constructor and continue running the program, as shown in the following figure:

    Through this case, we can verify that AssociationsManager is not a singleton.

  3. AssociationsHashMap singleton verification

    AssociationsHashMap = AssociationsHashMap = AssociationsHashMap = AssociationsHashMap = AssociationsHashMap

    We can also verify this from the source code, AssociationsHashMap is returned through a static Storage data, and the static variable address is fixed. At the same time, the static variable is declared in AssociationsHashMap in order to set a calling mode, which can only be called by AssociationsHashMap class. See below:

4. Data flow analysis

After initialization of related data, data is inserted or deleted. If value is not empty, data is inserted. Otherwise, associated data is erased. See below:

The following track process, explore the implementation principle!

  1. Associated object setup process

    First, the DisguisedPtr(object) gets the REFs_result from the AssociationsHashMap data structure of the singleton, then what is the data structure? See below:

    First of all, $3 has two data types: first and second. Second is a bool. What is first? It’s a pair, which is a pair of values, Ptr and End.

    So this next piece of code, what’s going on here?

         auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
    Copy the code

    The way to Overlap (premiere6.0) comes with two arguments, the object we want to associate with Premiere6.0 (Premiere6.0).

    In the try_emplace method, the LookupBucketFor method is first called, which is understood from the method name to find out if the bucket exists. LookupBucketFor (bucketfor)

    A method with the same name is called in this method, but the arguments are different. Both arguments to this method are const, and the implementation of the LookupBucketFor method is entered. Note that the BucketT address is passed to the AssociationsHashMap to find the BucketT of the object, and the address is assigned during the search, as shown in the following figure:

    In this method, we look for the bucket corresponding to the object and return true if the bucket contains a key and a value. Otherwise, set an empty bucket and return false.

    Back in the try_emplace method, after the LookupBucketFor process is completed, TheBucket is assigned to the address of an empty bucket, even if it is not found, and TheBucket is initialized. If found, a pair and a bool are returned. See below:

    The InsertIntoBucket method is implemented as follows:

    Through the above process, the second parameter in try_emplace has been set to true. So the first time you enter, you will set isFirstAssociation, as shown below:

    At this point, the bucket corresponding to the object is created, that is, the bueckt is created, but the value and key have not been set to the corresponding bucket. Let’s insert association, as shown in the following figure:

    When you enter the method again, since the first argument is now a key, not an object, a LookupBucketFor call, looking for a bucket, must return false. So the InsertIntoBucket method will definitely be invoked to insert association. See the following code:

    Summary of Settings:

    1. To create aAssociationsManagerManagement class
    2. Gets a unique global static hashMap 
    3. Determine whether the inserted associated value exists: There is a routing4Step; Go without: The associated object inserts an empty process
    4. Create an empty oneObjectAssociationMapTo get the key-value pair of the query
    5. And if it doesn’tkeyJust insert an empty oneBucketTGo in and return
    6. The tag object has an associated object
    7. With the current decorating policy and valueObjcAssociationReplace the originalBucketTThe empty
    8. Mark theObjectAssociationMapThe first time of isfalse
  2. The associated object inserts an empty process

    If the value passed in is empty, the data will be cleared, as shown in the following figure:

    1. According to theDisguisedPtrfindAssociationsHashMapIn theiteratorIterating query
    2. Clean iterator
    3. In fact, if the insert is empty, it’s cleared

5. Summary of associated objects

Through the above analysis, we can know the data structure relationship in the process of dealing with the associated object, which is consistent with the thought we expected in the design! In other words, there is a global data set, HashMap, which stores the associated data of all objects internally. Every object corresponds to a subHashMap, and the attributes associated with this object are stored in its subHashMap in the form of key and value pairs.

Get the associated object value from objc_getAssociatedObject. See the following figure:

  1. To create aAssociationsManagerManagement class
  2. Get a unique global staticHashMap.AssociationsHashMap
  3. According to theDisguisedPtrfindAssociationsHashMapIn theiteratorIterating query
  4. If this iterated query is not the last, get:ObjectAssociationMap(There are strategies andvalue)
  5. findObjectAssociationMapThe iterated query of thevalue
  6. return_value

Summary: It is a two-layer hash map, and the access is processed in two layers (like a two-dimensional array).

6. Associated object destruction process

The dealloc method is called when an object is destroyed and the associated object is released.

RootDealloc ->_objc_rootDealloc->rootDealloc ->rootDealloc

Continue tracking code: rootDealloc-> object_Dispose ->objc_destructInstance

Finally, the associated objects are cleared by calling _object_remove_assocations, as shown below: