This article belongs to “Jane Book — Liu Xiaozhuang” original, please note:
< Jane books – Liu Xiaozhuang > https://www.jianshu.com/p/0dc2513e117b
Category
With the foundation of the previous Runtime, some of the internal implementations are well understood. In the OC, attributes, methods, and protocols can be added by Category. In the Runtime, classes and categories are implemented by struct.
Extension syntax is similar to the Category syntax, except that Extension is compiled directly with the original class at compile time, whereas categories are added dynamically to the original class at runtime.
Based on the previous source code analysis, let’s take a look at how categories are implemented.
In the _read_images function, a loop is nested, with the outer loop going through all the classes and fetching the Category array for the current class. Out of the inner loop will traverse the Category array, remove every category_t object, eventually perform addUnattachedCategoryForClass function is added to a Category in the hash table.
// Add category_t to the list and update the Category list of the category_t Category by NXMapInsert
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
// Get the unadded Category hash table
NXMapTable *cats = unattachedCategories();
category_list *list;
// Get the value from buckets and add category_t to the value array
list = (category_list *)NXMapGet(cats, cls);
if(! list) { list = (category_list *) calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
// Replace the previous list field
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
Copy the code
Category maintains a hash table named CATEGORY_map that stores all category_T objects.
// Get the category hash table not added to the Class
static NXMapTable *unattachedCategories(void)
{
// Category hash table not added to Class
static NXMapTable *category_map = nil;
if (category_map) return category_map;
// fixme initial map size
category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
return category_map;
}
Copy the code
This is just adding to the Category hash table, where all category_T objects are stored. You then need to call the remethodizeClass function to add the Category information to the corresponding Class.
The remethodizeClass function looks for the Category array for the Class parameter passed in, and then passes the array to the attachCategories function to perform the specific addition.
// Add the Category information to the Class, including method, property, and protocol
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
isMeta = cls->isMetaClass();
// Look up the category_t object from the Category hash table and remove the found object from the hash table
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
attachCategories(cls, cats, true /*flush caches*/); free(cats); }}Copy the code
In the attachCategories function, find the Category method list, attribute list, and protocol list, and then add them to the class_rw_t structure corresponding to the Class using the corresponding attachLists function.
// Get the Protocol list, Property list, and Method list of the Category, and then add them to the class using attachLists
static void attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if(! cats)return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// Allocate memory space according to the number of categories
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
// find Protocol list, Property list, Method list
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
// Perform the add operation
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
Copy the code
The process is to add information from a Category to the corresponding Class, so there may be more than one Category in a Class, and in this process all the information from all the categories will be merged into the Class.
Methods cover
When multiple categories and methods of the original class are defined repeatedly, the methods of the original class and all categories will exist and will not be overwritten. Given a method called Method, both the Category and the methods of the original class are added to the list of methods, but in different order.
When a method call is made, the methods of the Category are traversed first, and the categories that are later added to the project are called first. The above example call order is Category3 -> Category2 -> Category1 -> TestObject. If you find a method in the list of methods, you don’t look back, which is why class methods are “overridden” by categories.
The problem
If there are multiple categories with the same name as the methods of the original class, how do I call all the methods of a Category and the methods of the original class after the methods of one Category are called?
After a Category method is called, you can iterate through the list of methods and call other methods with the same name. It is important to note, however, that you should not call your own methods during traversal, otherwise it will result in recursive calls. To avoid this problem, you can determine whether the invoked method IMP is the IMP of the current method before invoking it.
So how do you call only the original class method after any of the Category methods are called?
Based on the above analysis of method calls, the Runtime calls all categories before calling a method, so it is possible to iterate backwards through the list of methods, just the first one, which is the original class’s method.
Category Associate
Categories are often used in projects, and when you need to add attributes to a Category, the associated Runtime API is used. For example, in the following example, you need to dynamically add implementations in the property’s set and get methods.
// Declaration file
@interface TestObject (Category)
@property (nonatomic.strong) NSObject *object;
@end
// Implementation file
#import <objc/runtime.h>
#import <objc/message.h>
static void *const kAssociatedObjectKey = (void *)&kAssociatedObjectKey;
@implementation TestObject (Category)
- (NSObject *)object {
return objc_getAssociatedObject(self, kAssociatedObjectKey);
}
- (void)setObject:(NSObject *)object {
objc_setAssociatedObject(self, kAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
Copy the code
When you add attributes to a Category, there are no methods implemented by default, and if you call the attributes you will crash with the following two warnings.
Property 'object' requires method 'object' to be defined - use @dynamic or provide a method implementation in this category
Property 'object' requires method 'setObject:' to be defined - use @dynamic or provide a method implementation in this category
Copy the code
Runtime dynamically adds set and get via Runtime. The following is the implementation code for the objc_getAssociatedObject function. The objc_setAssociatedObject implementation is similar, but I won’t post any savings here.
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if(i ! = associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key);if(j ! = refs->end()) { ObjcAssociation &entry = j->second; value = entry.value(); policy = entry.policy();if(policy & OBJC_ASSOCIATION_GETTER_RETAIN) { objc_retain(value); }}}}if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
Copy the code
All attributes added by associated are stored in a separate hash table, AssociationsHashMap. The objc_setAssociatedObject and objc_getAssociatedObject functions are essentially manipulating the hash table to access objects by mapping the hash table.
The associated API sets some memory management keywords, such as OBJC_ASSOCIATION_ASSIGN, which specifies the memory management of the object. These keywords are also handled in the Runtime source code.
Due to typesetting problems, the reading experience is not good, such as layout, picture display, code and many other problems. So go to Github and download the Runtime PDF collection. Put all the Runtime articles together in this PDF, with a table of contents on the left for easy reading.
Please give me a thumbs up, thank you! 😁