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.