I. Introduction to Category
Categories are objective-C2.0 language features that add methods to existing classes. A Category extends or separates a class by adding methods to an existing class without subclassing or modifying the source code of the class. While inheritance can also add new methods and attributes to an existing class, inheritance adds unnecessary code complexity that makes it indistinguishable from the parent class’s methods at run time.
1. The Category
- Separate class implementations into separate files. There are several obvious benefits to doing this:
A) reduce the size of a single file b) organize different functions into different categories c) create a class by multiple developers d) load desired categories on demand e)…
- Declare private methods.
- Simulate multiple inheritance.
- Expose framework private methods.
2. How to distinguish between Category and Extension
Extension looks a lot like an anonymous Category, but Extension and named Category are almost entirely different things. Extension is defined at compile time as part of a class, formed at compile time along with @Interface in the header file and @Implement in the implementation file as a complete class, which is born and dies with the class. Extensions are generally used to hide private information about classes. You must have the source code of a class to add an Extension to a class, so you cannot add an Extension to a system class such as NSString.
But categories are completely different, and are determined at run time. From the difference between Category and Extension, we can derive the obvious fact that Extension can add instance variables, whereas Category cannot (because at runtime, the memory layout of the object is already determined, Adding instance variables breaks the internal layout of the class, which is disastrous for compiled languages.)
Second, the nature of categories
In objc-Runtime-new.h, all OC classes and objects are struct structures at the Runtime layer, including category. Category is defined as category_T structure. The category_T structure has the following data structure:
typedef struct category_t *Category;
struct category_t {
const char *name; / / the name of the class
classref_t cls; // Class, which corresponds to the class object at runtime via clasee_name (class name)
struct method_list_t *instanceMethods; // List of all added object methods in the Category
struct method_list_t *classMethods; // List of all added class methods in the Category
struct protocol_list_t *protocols; // List of all protocols implemented in Category
struct property_list_t *instanceProperties; // All attributes added to the Category
};
Copy the code
You can also see from the structure definition of a Category that a Category can add object methods, class methods, protocols, and attributes to a class. But categories can’t add member variables.
1. Observe the nature of categories through their C++ source code
To understand the nature of a Category, we need to analyze it from the C++ source code. Here’s an example of a Category we wrote: Categoryessence.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol CategoryEssenceProtocol <NSObject>
- (void)categoryEssenceProtocolMethod;
+ (void)categoryEssenceProtocolClassMethod;
@end
@interface CategoryEssence : NSObject
- (void)printName;
+ (void)printClassName;
@end
@interface CategoryEssence (Addition) <CategoryEssenceProtocol>
- (void)printName;
+ (void)printClassName;
@end
NS_ASSUME_NONNULL_END
Copy the code
CategoryEssence.m
#import "CategoryEssence.h"
@implementation CategoryEssence
- (void)printName {
NSLog(@"CategoryEssence printName");
}
+ (void)printClassName {
NSLog(@"CategoryEssence printClassName");
}
@end
@implementation CategoryEssence(Addition)
- (void)printName {
NSLog(@"CategoryEssence(Addition) printName");
}
+ (void)printClassName {
NSLog(@"CategoryEssence(Addition) printClassName");
}
#pragmaMark -- Protocol method
- (void)categoryEssenceProtocolMethod {
NSLog(@"categoryEssenceProtocolMethod");
}
+ (void)categoryEssenceProtocolClassMethod {
NSLog(@"categoryEssenceProtocolClassMethod");
}
@end
Copy the code
Will CategoryEssence. M through
clang -rewrite-objc CategoryEssence.m
Copy the code
Get a 4.5m, 11W+.cpp file (created under iOS project). Ignoring extraneous code, let’s analyze a wave: The essence of a Category is the _category_t structure type, which contains the following sections:
_method_list_t
Type object method list structure;_method_list_t
Class method list structure of type;_protocol_list_t
Type protocol list structure;_prop_list_t
Type property list structure;
But _category_t does not contain the _ivar_list_t member variable structure, which indirectly illustrates the fact that categories cannot add member variables.
// @implementation CategoryEssence
static void _I_CategoryEssence_printName(CategoryEssence * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_CategoryEssence_88f40a_mi_0);
}
static void _C_CategoryEssence_printClassName(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_CategoryEssence_88f40a_mi_1);
}
// @end
// @implementation CategoryEssence(Addition)
static void _I_CategoryEssence_Addition_printName(CategoryEssence * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_CategoryEssence_88f40a_mi_2);
}
static void _C_CategoryEssence_Addition_printClassName(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_CategoryEssence_88f40a_mi_3);
}
static void _I_CategoryEssence_Addition_categoryEssenceProtocolMethod(CategoryEssence * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_CategoryEssence_88f40a_mi_4);
}
static void _C_CategoryEssence_Addition_categoryEssenceProtocolClassMethod(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_CategoryEssence_88f40a_mi_5);
}
// @end
#pragma warning(disable:4273)
static struct/ * _method_list_t* / {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1].
} _OBJC_$_INSTANCE_METHODS_CategoryEssence __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"printName"."v16@0:8", (void *)_I_CategoryEssence_printName}}
};
static struct/ * _method_list_t* / {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1].
} _OBJC_$_CLASS_METHODS_CategoryEssence __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"printClassName"."v16@0:8", (void *)_C_CategoryEssence_printClassName}}
};
static struct _class_ro_t _OBJC_METACLASS_RO_The $_CategoryEssence __attribute__ ((used.section(" __DATA, __objc_const"))) = {
1.sizeof(struct _class_t), sizeof(struct _class_t),
(unsigned int)0.0."CategoryEssence",
(const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_CategoryEssence,
0.0.0.0};static struct _class_ro_t _OBJC_CLASS_RO_The $_CategoryEssence __attribute__ ((used.section(" __DATA, __objc_const"))) = {
0.sizeof(struct CategoryEssence_IMPL), sizeof(struct CategoryEssence_IMPL),
(unsigned int)0.0."CategoryEssence",
(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_CategoryEssence,
0.0.0.0};extern "C" __declspec(dllimport) struct _class_t OBJC_METACLASS_The $_NSObject;
extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_The $_CategoryEssence __attribute__ ((used.section(" __DATA, __objc_data"))) = {
0.// &OBJC_METACLASS_$_NSObject,
0.// &OBJC_METACLASS_$_NSObject,
0.// (void *)&_objc_empty_cache,
0.// unused, was (void *)&_objc_empty_vtable,
&_OBJC_METACLASS_RO_$_CategoryEssence,
};
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_The $_NSObject;
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_The $_CategoryEssence __attribute__ ((used.section(" __DATA, __objc_data"))) = {
0.// &OBJC_METACLASS_$_CategoryEssence,
0.// &OBJC_CLASS_$_NSObject,
0.// (void *)&_objc_empty_cache,
0.// unused, was (void *)&_objc_empty_vtable,
&_OBJC_CLASS_RO_$_CategoryEssence,
};
static void OBJC_CLASS_SETUP_$_CategoryEssence(void ) {
OBJC_METACLASS_$_CategoryEssence.isa = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_CategoryEssence.superclass = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_CategoryEssence.cache = &_objc_empty_cache;
OBJC_CLASS_$_CategoryEssence.isa = &OBJC_METACLASS_$_CategoryEssence;
OBJC_CLASS_$_CategoryEssence.superclass = &OBJC_CLASS_$_NSObject;
OBJC_CLASS_$_CategoryEssence.cache = &_objc_empty_cache;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CLASS_SETUP[] = {
(void *)&OBJC_CLASS_SETUP_$_CategoryEssence,
};
static struct/ * _method_list_t* / {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2].
} _OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryEssence_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"printName"."v16@0:8", (void *)_I_CategoryEssence_Addition_printName},
{(struct objc_selector *)"categoryEssenceProtocolMethod"."v16@0:8", (void *)_I_CategoryEssence_Addition_categoryEssenceProtocolMethod}}
};
static struct/ * _method_list_t* / {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2].
} _OBJC_$_CATEGORY_CLASS_METHODS_CategoryEssence_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"printClassName"."v16@0:8", (void *)_C_CategoryEssence_Addition_printClassName},
{(struct objc_selector *)"categoryEssenceProtocolClassMethod"."v16@0:8", (void *)_C_CategoryEssence_Addition_categoryEssenceProtocolClassMethod}}
};
static struct/ * _method_list_t* / {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1].
} _OBJC_PROTOCOL_OPT_INSTANCE_METHODS_NSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"debugDescription"."@ @ 0:8 16".0}}};static struct/ * _prop_list_t* / {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[4].
} _OBJC_PROTOCOL_PROPERTIES_NSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
4,
{{"hash"."TQ,R"},
{"superclass"."T#,R"},
{"description"."T@\"NSString\",R,C"},
{"debugDescription"."T@\"NSString\",R,C"}}};struct _protocol_t _OBJC_PROTOCOL_NSObject __attribute__ ((used=)) {
0."NSObject".0,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSObject,
0,
(const struct method_list_t *)&_OBJC_PROTOCOL_OPT_INSTANCE_METHODS_NSObject,
0,
(const struct _prop_list_t *)&_OBJC_PROTOCOL_PROPERTIES_NSObject,
sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSObject
};
struct _protocol_t* _OBJC_LABEL_PROTOCOL_The $_NSObject = &_OBJC_PROTOCOL_NSObject;
static const char *_OBJC_PROTOCOL_METHOD_TYPES_CategoryEssenceProtocol [] __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"v16@0:8"."v16@0:8"
};
static struct/ * _protocol_list_t* / {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1].
} _OBJC_PROTOCOL_REFS_CategoryEssenceProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSObject
};
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_CategoryEssenceProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"categoryEssenceProtocolMethod"."v16@0:8".0}}};static struct/ * _method_list_t* / {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1].
} _OBJC_PROTOCOL_CLASS_METHODS_CategoryEssenceProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"categoryEssenceProtocolClassMethod"."v16@0:8".0}}};struct _protocol_t _OBJC_PROTOCOL_CategoryEssenceProtocol __attribute__ ((used=)) {
0."CategoryEssenceProtocol",
(const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_CategoryEssenceProtocol,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_CategoryEssenceProtocol,
(const struct method_list_t *)&_OBJC_PROTOCOL_CLASS_METHODS_CategoryEssenceProtocol,
0.0.0.sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_CategoryEssenceProtocol
};
struct _protocol_t* _OBJC_LABEL_PROTOCOL_The $_CategoryEssenceProtocol = &_OBJC_PROTOCOL_CategoryEssenceProtocol;
static struct/ * _protocol_list_t* / {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1].
} _OBJC_CATEGORY_PROTOCOLS_$_CategoryEssence_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_CategoryEssenceProtocol
};
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_The $_CategoryEssence;
static struct _category_t _OBJC_The $_CATEGORY_CategoryEssence_The $_Addition __attribute__ ((used.section(" __DATA, __objc_const"))) =
{
"CategoryEssence".0.// &OBJC_CLASS_$_CategoryEssence,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryEssence_$_Addition,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_CategoryEssence_$_Addition,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_CategoryEssence_$_Addition,
0};static void OBJC_CATEGORY_SETUP_$_CategoryEssence_$_Addition(void ) {
_OBJC_$_CATEGORY_CategoryEssence_$_Addition.cls = &OBJC_CLASS_$_CategoryEssence;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_CategoryEssence_$_Addition,
};
static struct _class_t *L_OBJC_LABEL_CLASS_$[1] __attribute__((used.section(" __DATA, __objc_classlist.regular.no_dead_strip"))) = {
&OBJC_CLASS_$_CategoryEssence,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$[1] __attribute__((used.section(" __DATA, __objc_catlist.regular.no_dead_strip"))) = {
&_OBJC_$_CATEGORY_CategoryEssence_$_Addition,
};
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0.2 };
Copy the code
2. Category loading method
As we know, Objective-C relies on the Objective-C Runtime, which, like other system libraries, is dynamically loaded by OS X and iOS via DyLD.
1. General process of DYLD loading
Dyld (the Dynamic Link Editor) code can be downloaded from the Apple open source website: DYLD Apple open source code about dyLD can be moved to the dyLD details
Dyld loading procedure:
- Configure environment variables;
- Load the shared cache;
- Initialize the main APP;
- Insert dynamic cache library;
- Link main program;
- Link-inserted dynamic library;
- Initialize the main program:
OC
.C++
Global variable initialization; - Returns the main program entry function.
2. The loading process of Category
The Runtime is initialized at step 7, so the Category is also loaded at this step.
Let’s take a look at the call stack when the main program is initialized:
dyldbootstrap::start ---> dyld::_main ---> initializeMainExecutable ---> runInitializers ---> recursiveInitialization ---> doInitialization ---> doModInitFunctions ---> _objc_init
Copy the code
The last call to _objc_init is a method in the LibobJC library that initializes the Runtime and is an Objective-C entry point.
In the _objc_init step: Runtime binds a callback to DYLD. When image is loaded into memory, DYLD notifies Runtime for processing. Runtime takes over and calls MAP_images for parsing and processing. Call the _read_images method to add the object methods, protocols, and attributes of the Category to the class, and the class methods and protocols of the Category to the MetaClass of the class. The call_load_methods method is then called in load_images to iterate over all the loaded classes, calling the load method of the Class and the load method of its Category in order of inheritance and compilation.
Loading the Category call stack:
_objc_init --> map_images --> map_images_NOLock --> _read_images (load classification) --> load_imagesCopy the code
As for the loading process of Category, I will briefly taste it here and mainly understand the loading process. If you are interested, you can download the official source code for detailed reading and analysis.
Category and Class +load methods
The methods, attributes, and protocols in a Category are appended to the class before the + load method is executed. That is, the methods, attributes, and protocols in the Category are already loaded in the class before the + load method is executed.
The order of the Category and Class + load methods is as follows:
- First call the main class, according to the compile order, according to the inheritance relationship from the parent class to the child class call;
- Call the main class, then call the classification, according to the compile order, call;
+ load
Methods are called only once unless actively called.
Using this call rule, we know that the main class’s + load method call must precede the class’s + load method call. However, the classification + load method is not called in accordance with the inheritance relationship, but in accordance with the compile order, which also leads to the call order of the + load method is not always determined. One order might be:
Parent class -> Subclass -> Parent class -> Subclass classCopy the code
May also be
Parent class -> Subclass -> Subclass category -> Parent class categoryCopy the code
The +initialize method for Category and Class
The + Initialize method is called the first time the class receives a message.
Call order: call + initialize of the parent class first, then + initialize of the child class. (First initialize the parent class, then initialize the child class, each class is initialized only once)
The big difference between + initialize and + load is that + initialize is called via objc_msgSend, so it has the following features:
- If the subclass is not implemented
+ initialize
, will call the parent class,+ initialize
. (So superclass+ initialize
May be called multiple times) - If the classification is implemented
+ initialize
Override the class itself+ initialize
The call.
Caregory adds a member variable from the associated object
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;
// The class structure has a structure pointer properties where the declared properties are stored
const struct _prop_list_t *properties;
};
Copy the code
Attributes can be added to a classification, and the set and GET method declarations for attributes are automatically generated, but the member variables and implementation of the set and GET methods are not. Instead of automatically generating set and GET methods, you can define the set and get methods for attributes. To make a classification generate member variables requires the method of associating objects.
1. Specific use of associated object methods
// 1. Set the associated attribute of the object in the form of key: value
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
// 2. Use key to obtain the associated attribute object
id objc_getAssociatedObject(id object, const void *key);
// 3. Remove the attributes associated with the object
void objc_removeAssociatedObjects(id object);
Copy the code
To give a network example: adding a network address attribute to a UIImage class
/ * * * * * * * * * * * * * * * * * * * * * UIImage + Property. H file * * * * * * * * * * * * * * * * * * * * * /
#import <UIKit/UIKit.h>
@interface UIImage (Property)
/* Image network address */
@property (nonatomic.copy) NSString *urlString;
// Clear associated objects
- (void)clearAssociatedObjcet;
@end
/ * * * * * * * * * * * * * * * * * * * * * UIImage + Property. The m file * * * * * * * * * * * * * * * * * * * * * /
#import "UIImage+Property.h"
#import <objc/runtime.h>
@implementation UIImage (Property)
/ / set methods
- (void)setUrlString:(NSString *)urlString {
objc_setAssociatedObject(self.@selector(urlString), urlString, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
/ / get methods
- (NSString *)urlString {
return objc_getAssociatedObject(self.@selector(urlString));
}
// Clear the associated object
- (void)clearAssociatedObjcet {
objc_removeAssociatedObjects(self);
}
@end
Copy the code
With the aid of the associated object, we successfully added the urlString associated attribute to the UImage class in UIImage classification, and implemented getter and setter methods. Note: You can disconnect all associations using objc_removeAssociatedObjects. It is generally not recommended because it breaks all connections. If you want to disassociate the association you can use objc_setAssociatedObject, just pass the associated object to nil.
2. About associated objects
IOS Development · Runtime Principles and Practices: Associated Objects. I won’t go into details here. It’s easy to use, but don’t use it too often. After all, associated objects are Runtime apis and are handled at Runtime.
Reference documentation
-
Cocoa Core Competencies (Official Apple document
-
“IOS Development:” Runtime “detail (3) Category” basic principles of walking young lang
-
Deep Understanding objective-C: Category Beautiful Tuan technical team