A, Category details
The compiled Category
- _category_t structure
struct _category_t {
const char *name; / / the main class name
struct _class_t *cls; / / a concrete class
const struct _method_list_t *instance_methods; // List of instance methods
const struct _method_list_t *class_methods; // List of class methods
const struct _protocol_list_t *protocols; // Protocol list
const struct _prop_list_t *properties; // Attribute list
};
Copy the code
- The construction of a category
static struct _category_t _OBJC_The $_CATEGORY_NSString_The $_Test __attribute__ ((used.section(" __DATA, __objc_const"))) =
{
"NSString".// name
0.// &OBJC_CLASS_$_NSString, // cls
0.// instance_methods
0.// class_methods
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_NSString_$_Test, // protocols
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NSString_$_Test, // properties
};
Copy the code
Category related source code
1. Category_t structure, you can see that the source code is not different from compiled
struct category_t {
const char *name;
classref_t cls;
WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
WrappedPtr<method_list_t, PtrauthStrip> classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t* _classProperties;
If it is a class, return the instance method. If it is a metaclass, return the class method
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
// Attribute list
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
// Protocol list
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else returnprotocols; }};Copy the code
- How are categories loaded?
Category is dynamically loaded into memory at Runtime with the following core code:
- The attachCategories function is used to concatenate all categories
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
/ /... Other code
constexpr uint32_t ATTACH_BUFSIZ = 64;
// Declare 64 length method array, property array, protocol array (all two-dimensional arrays)
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
// Each quantity is initialized to 0
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
/** class_rw_t -> extAllocIfNeeded */
// rwe is the class_rw_ext_t member of the current class
auto rwe = cls->data()->extAllocIfNeeded();
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
// category_t -> methodsForMeta
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) { // If this class has a list of methods, add the list of methods to declared mlists
if (mcount == ATTACH_BUFSIZ) { // If there are more than 64 entries, then the mlist should be fixed
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
// This sentence indicates that the original method is placed in the lower position of mlists,
// Mlists are two-dimensional array, store directly mlist
// The list traversed first is placed after the mlists, i.e. the first compiled categories are placed after
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
// Same with proplist
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
// Same with protolist
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0; } protolists[ATTACH_BUFSIZ - ++protocount] = protolist; }}if (mcount > 0) {
// Prepare the list of methods
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
// Concatenate the list of methods
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
/ /... Other code
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
Copy the code
- The attachLists function is mainly used to join the methods in each category
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
// oldCount is the number of methods in the class
uint32_t oldCount = array()->count;
// newCount is a combination of the original and the number of categories
uint32_t newCount = oldCount + addedCount;
// Create a new space and copy count to newCount
array_t *newArray = (array_t *)malloc(array_t: :byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
// Iterate through newArray in reverse order, putting the methods of this class in
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
// In the positive order traversal newArray, put the classification method into
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
// Free up space and assign newArray to itself
free(array());
setArray(newArray);
validate(a); }else if(! list && addedCount ==1) {
/ /... Other code
}
else {
/ /... Other code}}Copy the code
After concatenation, the structure of the method array of this class becomes [[class method list 1], [class method list 2],….. [class method list]].
Objc_msgSend () does not overwrite the original method, but returns it after finding the method
Can I add member variables to a Category?
This is a classic interview question. Category_t does not have ivAS member variables, so we cannot add member variables to Category directly
Category_t has a list of properties, so you can add properties to a property, but what’s the difference?
The compiler automatically generates setters and getters for properties in this class, but it doesn’t automatically generate setters and getters for properties in a Category, so when we declare a property in a Category, when we assign a value to that property, we’re going to say unrecognized selector
So, it’s up to us to manually add setters and getters to the properties of the categories ourselves. But classes can’t add member variables, so who does our setter method assign to? Who does the getter return?
3. Associated object technology
We can indirectly add something like a member variable to a Category by associating objects. There are three main functions for associated object correlation
Key: the unique key of the associated value value: the specific value of the associated value Policy: the associated policy (similar to the parameter used to declare properties) */
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
/** Obtain an associated object of an object. Object: The associated source object. Key: The unique key of the associated value
id objc_getAssociatedObject(id object, const void *key)
/** Remove the associated object of the object object: the source object to be removed */
void objc_removeAssociatedObjects(id object)
Copy the code
Using the associative object technique, we can assign a value to cName in the setter method and get the value of cName in the getter method
Four, the implementation principle of the associated object
- Through the source code of the associated object, you can see that the principle of the associated object is mainly composed of four parts
- AssociationsManager: Manages an AssociationsHashMap in the Manager
- AssociationsHashMap: This map uses the Object passed in by setXXX as the Key of the map after some other operations and uses an ObjectAssociationMap as the value
- ObjectAssociationMap: This map uses the Key passed in by setXXX as the Key and the Objcassociety consisting of the value and policy passed in by setXXX as the value
Such design idea, in AssociationsHashMap, for the same Object, Object as the Key to find the ObjectAssociationMap, there are all its associated objects {Key: Objcasassociation}, for different Objcet, different ObjectAssociationMaps can be found by Key, and all associationShashMaps are managed by AssociationsManager. The schematic diagram is as follows:
- Associated object related source
- Objc_setAssociatedObject () function
// objc_setAssociatedObject() directly calls the _object_set_associative_reference() function
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
// Implement it
void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
/ /... Abnormal judgment
// Do some processing on the object to generate a Key
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// Create ObjcAssociation based on policy and value
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
bool isFirstAssociation = false;
{
/ / declare AssociationsManager
AssociationsManager manager;
// Get AssociationsHashMap from manager
AssociationsHashMap &associations(manager.get());
if (value) { // If value is not nil, add the associated object
// Associations: Obtain the ObjectAssociationMap based on the key processed with object
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
auto &refs = refs_result.first->second;
// Set association by key in ObjectAssociationMap
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else { // Delete the associated object if value is nil
/ / get ObjectAssociationMap
auto refs_it = associations.find(disguised);
if(refs_it ! = associations.end()) {auto &refs = refs_it->second;
/ / for association
auto it = refs.find(key);
if(it ! = refs.end()) { association.swap(it->second);/ / erase association
refs.erase(it);
if (refs.size() == 0) {
// If there are no other associated objects, erase the ObjectAssociationMap
associations.erase(refs_it);
}
}
}
}
}
/ / /... Other code
}
Copy the code
- Objc_getAssociatedObject () function
// objc_getAssociatedObject() directly calls the _object_get_associative_reference() function
id objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
id _object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
/ / get the manager
AssociationsManager manager;
/ / get AssociationsHashMap
AssociationsHashMap &associations(manager.get());
// Associations: Obtain the ObjectAssociationMap based on the key processed with object
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if(i ! = associations.end()) { ObjectAssociationMap &refs = i->second;// Obtain the Association by key
ObjectAssociationMap::iterator j = refs.find(key);
if(j ! = refs.end()) {// Assign association if there is oneassociation = j->second; association.retainReturnedValue(); }}}return association.autoreleaseReturnedValue();
}
Copy the code