It started on my personal blog
Basic use of classification
- First we define a class YZPerson that inherits from NSObject
@interface YZPerson : NSObject
@end
Copy the code
- Then define a category YZPerson+test1.h
#import "YZPerson.h"
@interface YZPerson (test1)
-(void)run;
@end
#import "YZPerson+test1.h"
@implementation YZPerson (test1)
-(void)run{
NSLog(@"%s",__func__);
}
@end
Copy the code
- Used in controller ViewController
- (void)viewDidLoad {
[super viewDidLoad];
YZPerson *person = [[YZPerson alloc] init];
[person run];
}
Copy the code
- The execution result is
CateogryDemo[23773:321096] -[YZPerson(test1) run]
Note: If the original class and the classification have the same method, then the result of execution is the classification, for example
#import <Foundation/Foundation.h>
@interface YZPerson : NSObject
-(void)run;
@end
#import "YZPerson.h"
@implementation YZPerson
-(void)run{
NSLog(@"%s",__func__);
}
@end
Copy the code
- The execution result won’t change. It’s still
CateogryDemo[23773:321096] -[YZPerson(test1) run]
The reasons will be analyzed later
Structure of classification
Open the terminal, go to the project, and run the following command to generate the C language file
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc YZPerson+test1.m
Generate the YZPerson+test1.cpp file
The main code is extracted as follows
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;
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_YZPerson_$_test1 __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"run"."v16@0:8", (void *)_I_YZPerson_test1_run}}
};
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_YZPerson;
static struct _category_t _OBJC_$_CATEGORY_YZPerson_$_test1 __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"YZPerson",
0, // &OBJC_CLASS_$_YZPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_YZPerson_$_test1, 0, 0, 0,}; static void OBJC_CATEGORY_SETUP_$_YZPerson_$_test1(void ) {
_OBJC_$_CATEGORY_YZPerson_$_test1.cls = &OBJC_CLASS_$_YZPerson;
}
Copy the code
Note After compiling, each category generates one
_category_t
The name, the list of object methods, the list of class methods, the list of protocol methods, the list of properties, if the corresponding is empty, such as protocol is empty, property is empty, then the structure holds 0.
objc-runtime-new.h
Objc-runtime-new. h classifiesstructure objc-runtimenew.h classifiesstructure objc-runtimenew.h
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *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;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
Copy the code
Source code analysis
Sequence of source code interpretation
objc-os.mm _objc_init map_images map_images_nolock objc-runtime-new.mm _read_images remethodizeClass attachCategories AttachLists RealLOc, MEMmove, memCPyCopy the code
- Find the objC-os. mm class first
// Runtime initialization method void _objc_init(void) // {static bool initialized =false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
Copy the code
- Keep following
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
Copy the code
- To view
map_images_nolock
Copy the code
find
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
Copy the code
Objc-runtime-new.mm file
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, Int unoptimizedTotalClasses) {remethodizeClass(CLS); RemethodizeClass (CLS ->ISA()); }Copy the code
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true/*flush caches*/); free(cats); }}Copy the code
The main code comments are already shown in the code
Static void attachCategories(Class CLS, category_list *cats, bool flush_caches) {// CATSif(! cats)return;
if (PrintReplacedMethods) printReplacements(cls, cats); bool isMeta = cls->isMetaClass(); // Fixme rearrange to remove these intermediate allocations 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)); // Count backwards through cats to get newest categories first int mcount = 0; int propcount = 0; int protocount = 0; int i = cats->count; bool fromBundle = NO; / / thiswhileObject method property protocol in circular merge classificationwhileAuto & 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(); 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
memmove memcpy
The code above continues to follow
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); array()->count = newCount; // Memmove (array()-> Lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0])); // Memcpy (array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); }else if(! list && addedCount == 1) { // 0 lists -> 1 list list = addedLists[0]; }else{ // 1 list -> many lists List* oldList = list; uint32_t oldCount = oldList ? 1:0; uint32_t newCount = oldCount + addedCount;setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if(oldList) array()->lists[addedCount] = oldList; memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); }}Copy the code
The key code is
// Memmove (array()-> Lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0])); // Memcpy (array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));Copy the code
For the difference between memCPY and memmove, see the difference between memcpY and memmove
The simple summary is:
The difference is key RESTRICT. Memcpy assumes that two memory regions do not overlap, whereas memmove does not. If you use memcpy when two regions overlap, the results are unpredictable and may or may not work, so if you use memcpy, the programmer must make sure that there is no overlap between the two regions
conclusion
When merging classes, their list of methods, etc., does not overwrite the methods in the original class, but coexist. But the method in the class comes first, the method in the original class comes after, and when called, the method in the class will be called, and if multiple classes have the same method, the post-compiled class will be called.
The problem
Where are categories used?
- The functions of different modules can be separated and implemented using classes
How Category works
- After Category compilation, the underlying structure is struct category_t, which stores the classified object method, class method, attribute and protocol information. When the program runs, the Runtime will merge the Category data into the class information (class object, metaclass object).
What is the difference between a Category and a Class Extension?
When Class Extension is compiled, its data is already contained in the Class information, and when Category is run, the data is merged into the Class information
Is there a load method in Category? When is the load method called? Can the load method inherit?
The load method can be inherited when the Runtime calls the load method when the class or class is loaded. However, the load method is not automatically called by the system
Can you add member variables to a Category? If so, how do YOU add member variables to a Category?
- You can’t directly add member variables to a Category, but you can indirectly make it look like a Category has member variables, associated with objects
See detailed analysis of the association object
Github address github
References for this article:
The runtime source
Basic principles of iOS
Memcpy is different from memmove
More information, welcome to pay attention to the individual public number, not regularly share a variety of technical articles.