👨 🏻 💻 making Demo
Easy to remember:
- Call order: Call the load method of the class first, then call the load method of the class
- Add attributes for categories:
- Implementation: RunTime is the Category dynamically associated object, objc_setAssociatedObject method, internal call _object_set_associative_reference function
- Principle: The associated object is not placed inside the original object, but maintains a global map to store each object and its corresponding associated attribute table
The directory structure
When P calls run, the isa pointer of the class object finds the ISA pointer of the class object, and then looks for the object method in the class object. If not, the superclass pointer of the class object finds the superclass object. Then look for the run method.
The underlying implementation of a Category
Convert the Preson+ test. m file to a c++ file to see the compilation process
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+Test.m
Copy the code
The _category_t structure contains the class name, object method list, class method list, protocol list, and attribute list
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
Copy the code
Next, we see a structure of type _method_list_t
static struct/ * _method_list_t* / {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3].
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"test"."v16@0:8", (void *)_I_Person_Test_test},
{(struct objc_selector *)"setAge:"."v20@0:8i16", (void *)_I_Person_Test_setAge_},
{(struct objc_selector *)"age"."i16@0:8", (void *)_I_Person_Test_age}}
};
Copy the code
The struct _OBJC_CATEGORY_INSTANCE_METHODS_Preson_Test is named INSTANCE_METHODS and should be assigned values one by one. We can see that the structure stores the memory used by methods, the number of methods, and the list of methods. And find the corresponding object methods in classification, test, setAge, age three methods
static struct/ * _method_list_t* / {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1].
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"abc"."v16@0:8", (void *)_C_Person_Test_abc}}
};
Copy the code
As with the object method list above, this is the class method list structure _OBJC_CATEGORY_CLASS_METHODS_Preson_Test, which is the same as the object method structure, as well as the class method ABC that we implemented
Next is the list of protocol methods
static struct/ * _method_list_t* / {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1].
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"copyWithZone:"."@24@0:8^{_NSZone=}16".0}}};struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
0."NSCopying".0,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
0.0.0.0.sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
};
struct _protocol_t* _OBJC_LABEL_PROTOCOL_The $_NSCopying= & _OBJC_PROTOCOL_NSCopying;
static struct/ * _protocol_list_t* / {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1].
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSCopying
};
Copy the code
Store protocol methods in the _method_list_t structure. It is then stored in the _OBJC_CATEGORY_PROTOCOLS_Preson_Test by the _PROTOCOL_T structure, corresponding to the _protocol_LIST_t structure. Protocol_count is the number of protocols and the _PROTOCOL_T structure that stores protocol methods
Finally, we can see the property list
static struct/ * _prop_list_t* / {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1].
} _OBJC_$_PROP_LIST_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"age"."Ti,N"}}};Copy the code
The property list structure _OBJC_PROP_LIST_Preson_Test corresponds to the _prop_list_t structure, which stores the space occupied by the property, the number of property properties, and the property list, and we can see our own age property.
Finally, we can see that the _OBJC_CATEGORY_Preson_Test structure is defined and the structures we analyzed above are assigned values. Let’s compare the two images.
The two graphs correspond one by one, and we see that the OBJC_CLASS_Preson structure of type _class_T is defined, and finally the CLS pointer of _OBJC_CATEGORY_Preson_Test points to the OBJC_CLASS_Preson structure address. We can see here that the CLS pointer should point to the address of the main class object.
Through the above analysis we found that. The classified source code does store our defined object methods, class methods, attributes and so on in the catagory_t structure. Classification is implemented by placing category_T method, attribute, and protocol data in the category_T structure, and then copying the list of methods in the structure into the method list of the class object.
Categories can add attributes, but they do not automatically generate member variables and set/ GET methods. There are no member variables in the CATEGORY_t structure. We know from our previous analysis of the object that member variables are stored in the instance object and are determined at compile time. Categories are loaded at run time. Then we cannot add the member variables of the class to the structure of the instance object at runtime. Therefore, no member variables can be added to a classification.
The load and initialize
The load method is called when the program starts, when the class information is loaded. Call order look at the source code.
Through the source code, we found that the load method of the class is called first, and then call the load method of the class
Let’s verify this with code: we add Student inheriting Presen, and add Student+Test, override the +load method, and do nothing else by printing
It is true that the class’s load method is called before the class’s load method, but the class’s load method is called before the class’s parent has already called the load method. We then add the Initialize method for Preson, Student, and Student+Test.
We know that initialize is called the first time a class receives a message, which is equivalent to the initialize method being called the first time a class is used. The initialize method of the parent class is guaranteed to be called before the initialize method of the subclass is called. If initialize has been called before, the initialize method will not be called again. The class method is called first when the class overrides the Initialize method. However, the load method is not overridden, so let’s take a look at the initialize source code.
In the figure above, initialize is called by the message sending mechanism, which uses the ISA pointer to find the corresponding method and implementation. Therefore, the implementation of the classification method will be called first
Let’s take a look at the load method call source
We see that the load method gets the memory address of the load method and calls the method directly, instead of using the message sending mechanism
We can see that the class is also called by getting the address of the load method directly. So, as we tested earlier, overriding the load method in the class does not call the load method in the class in preference to the load method in the class.
RunTime is the Category dynamically associated object
To add attributes to a class in a system using the RunTime, you first need to understand the relationship between objects and attributes. We learned that when an object is initially initialized it has a property of nil, and assigning a value to a property is essentially referring to a block of memory where the content is stored, so that the properties of this object are associated with that block of memory.
So if you want to add attributes dynamically, you’re actually creating some kind of association dynamically. The only way to add attributes to a class in the system is through classification.
So here we add the name property to NSObject to create a class of NSObject
We can add attributes to a class using @property
@property(nonatomic.strong)NSString *name;
Copy the code
So if we look at the nature of categories, we know that even though you can add attributes to a Category with an @property, it doesn’t automatically generate private properties, it doesn’t automatically generate set, it doesn’t automatically generate set, the implementation of get, it just generates the declaration of set,get, and we need to implement that ourselves.
Method one: We can add attributes to a class by using static global variables
static NSString *_name;
- (void)setName:(NSString *)name {
_name = name;
}
- (NSString *)name {
return _name;
}
Copy the code
However, the _name static global variable is not associated with the class. Regardless of whether the object is created or destroyed, the _name variable exists as long as the program is running. It is not a real property.
The RunTime provides methods for dynamically adding and obtaining properties.
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self.@"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)name {
return objc_getAssociatedObject(self.@"name");
}
Copy the code
- 1. Dynamically add attributes
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
Parameter 1: ID object: To add an attribute to an object, in this case to add an attribute to yourself, use self.
Void * == ID Key: specifies the property name. The value of the property of the associated object is obtained by key. In **objc_getAssociatedObject, the value of the property is obtained by secondary key and returned.
Parameter 3: id value** : the associated value, that is, the value passed by the set method to the attribute to save.
Parameter 4: objc_AssociationPolicy Policy: indicates the policy in which attributes are saved.
There are several
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0.// Specify a weak reference to the associated object
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1.// Specifies a strong reference to the related object, which is non-atomic
OBJC_ASSOCIATION_COPY_NONATOMIC = 3.// Specifies that the associated object is copied, non-atomic
OBJC_ASSOCIATION_RETAIN = 01401.// Specify a strong reference to the related object, atomicity
OBJC_ASSOCIATION_COPY = 01403 // Specify that the related object is copied atomically
};
Copy the code
The key just needs to be a pointer, and we can pass in at sign selector name.
- 2. Get attributes
objc_getAssociatedObject(id object, const void *key);
Copy the code
Parameter 1: ID object: Gets the associated attribute in which object.
Parameter 2: void * == ID Key: an attribute that corresponds to the key in objc_setAssociatedObject.
- 3. Remove all associated objects
- (void)removeAssociatedObjects {
// Remove all associated objects
objc_removeAssociatedObjects(self);
}
Copy the code
At this point, the name attribute has been successfully added to NSObject, and the NSObject object can assign a value to the attribute using point syntax.
NSObject *objc = [[NSObject alloc]init];
objc.name = @"xx_cc";
NSLog(@ "% @",objc.name);
Copy the code
As you can see, associative objects are very simple to use, so let’s explore the underlying principles of associative objects
Objc_setAssociatedObject function
Go to the Runtime source code, first go to the objc_setAssociatedObject function and see its implementation
We see that the _object_set_associative_reference function is actually called internally, and we go to the _object_set_associative_reference function
_object_set_associative_reference function
Within the _object_set_associative_reference function we can find all the core objects that implement the associative object technique we mentioned above. Let’s take a look at the internal implementations one by one and see how they relate to each other.
AssociationsManager
Through the internal source code of AssociationsManager, it is found that there is an AssociationsHashMap object in AssociationsManager.
AssociationsHashMap
Let’s take a look at the source code inside AssociationsHashMap.
AssociationsHashMap inherits from Unordered_map by using AssociationsHashMap internal source code
From the source code of Unordered_MAP, we can see that _Key and _Tp, that is, the first two parameters correspond to the Key and Value in the map. Compared with the above source code in AssociationsHashMap, we can find that the disguised_ptr_T is passed in _Key. The value passed in _Tp is ObjectAssociationMap*.
Then we come to ObjectAssociationMap. In the figure above, ObjectAssociationMap has been marked. We find that ObjectAssociationMap also stores object Association in the form of key and Value.
And then we go to the ObjC Society
We see that objcasSociety stores _policy and _value, which we can see are the values passed in by calling objc_setAssociatedObject, That is, the value and policy that we pass in calling objc_setAssociatedObject are ultimately stored in the ObjcasSociety.
Now that we have a simple understanding of the relationship between AssociationsManager, AssociationsHashMap, ObjectAssociationMap, and ObjcasSociety, let’s read the source code carefully. Take a look at what the four parameters passed in the objc_setAssociatedObject function do on which object.
Return to the implementation of the _object_set_associative_reference function
AcquireValue (new_value) acquireValue (new_value) ¶ AcquireValue internally returns different values based on policy judgments
Then create the AssociationsManager manager and fetch the AssociationsHashMap inside the Manager. Then we see the first parameter object that we pass in. The object is transformed into the DISGUised_ptr_T type disguised_object through the DISGUISE function.
The DISGUISE function simply performs a bit operation on object
Then we see that the value that was treated as new_value, the same policy is stored in the Object Society.
And the object society key that we pass in is stored in the ObjectAssociationMap.
Disguised_object and ObjectAssociationMap are stored in associations in the form of key-value, that is, AssociationsHashMap.
If we set value to nil then the following code will be executed
As you can see from the code above, if we set value to nil, the associated object is removed from the ObjectAssociationMap.
Finally, we can clearly understand the relationship through a picture
From the above figure, we can conclude as follows: An instance object corresponds to an ObjectAssociationMap, which stores the keys and ObjcAssociation of multiple associated objects of this instance object. Stores the value and policy policies of the associated objects in the Object Society.
From this we can know that the associated object is not placed in the original object, but maintains a global map to store each object and its corresponding associated attribute table.
Objc_getAssociatedObject function
Objc_getAssociatedObject internally calls _object_get_associative_reference
_object_get_associative_reference function
From the inside of the _object_get_associative_reference function, we can see that the value is retrieved layer by layer in reverse as in the set method and then returned.
Objc_removeAssociatedObjects function
Objc_removeAssociatedObjects is used to remove all associated objects. The objc_removeAssociatedObjects function internally calls _object_remove_assocations
_object_remove_assocations function
The _object_remove_assocations function removes an object from all of its associated objects.
The associated object is not stored in the memory of the associated object itself, but stored in a global unified AssociationsManager. If the associated object is set to nil, it is equivalent to removing the associated object.
At this point we are going back to the objc_AssociationPolicy policy parameter: the policy for storing attributes.
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0.// Specify a weak reference to the associated object
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1.// Specifies a strong reference to the related object, which is non-atomic
OBJC_ASSOCIATION_COPY_NONATOMIC = 3.// Specifies that the associated object is copied, non-atomic
OBJC_ASSOCIATION_RETAIN = 01401.// Specify a strong reference to the related object, atomicity
OBJC_ASSOCIATION_COPY = 01403 // Specify that the related object is copied atomically
};
Copy the code
We can see that there is only RETAIN and COPY, but why not weak? In the face of the source code analysis, we know that the object is transformed into disguised_Ptr_T type DISGUised_object through the DISGUISE function.
disguised_ptr_t disguised_object = DISGUISE(object);
Copy the code
At the same time, we know that the weak attribute will be destroyed when there is no object and the pointer is set nil. So after the destruction of the object, although there is an AssociationsHashMap corresponding to the value object in the map, the address of object has been set nil, It causes bad address access and cannot be transformed into disguised_object according to the object object address.
Issues related to
Q: Is there a load method in a Category? When is the load method called? Can the load method inherit?
A: The Category has a load method, which is called when the program starts loading the class information. The load method can be inherited. The load method of the subclass is called before the load method of the subclass is called
Q: The difference between Load and initialize, and the order in which they are called when a category is overwritten.
Answer: The difference lies in how and when to call
- Call method: Load is called based on the function address, initialize is called through objc_msgSend
- Load is called when the Runtime loads a class or class (only once). Initialize is called when the class first receives a message. Each class is initialized only once (the parent class’s initialize method may be called multiple times).
- Call order: the load method of the class is called first, and the load method is called first when the class is compiled. The load method of the parent class is called before calling load. The load method in a class does not override the load method of the class. The first compiled class calls the load method first. Initialize first initializes the parent class and then the child class. If the subclass does not implement +initialize, the parent class’s +initialize is called (so the parent class’s +initialize may be called multiple times), and if the class implements +initialize, the class’s +initialize call is overridden.
Above post since: https://juejin.cn/post/6844903602524274696 and https://juejin.cn/post/6844903605347057672