Basic implementation principles of iOS Category (I) : Concepts and data structures

With extension, this article will start with extension. ⛽ ️ ⛽ ️

The extension stretch

Extension, unlike category, can declare methods, attributes, and member variables, but generally private methods, attributes, and member variables.

Existing form of extension

Categories have.h and.m files, whereas extension has only one.h file, or can only be “parasitic” in.m (” parasitic “in.m is our most common form of existence).

  • Parasitic form

For example, in the baseViewController.m file, you might write an extension directly:

@baseViewController () {// Private member variables can be defined here //... } // Private attributes can be defined here //... // Private methods can be defined here. @endCopy the code
  • Define the.h file format

You can create a separate extension File, command + N -> Objective-C File, File Type select extension, Class Type enter the name of the Class to create extension, File Enter the extension name and click Next to generate an.h File with the class name +xxx.h.

The following example shows us using Extension as a.h file. CusObject + extension. H file:

#import <Foundation/Foundation.h>
#import "CusObject.h"

NS_ASSUME_NONNULL_BEGIN

@interface CusObject (a) {
    // Add member variables through extension
    NSString *name;
}

// Add attributes and methods through extension
@property (nonatomic, copy) NSString *nameTwo;
- (void)testMethod_Extension;

@end

NS_ASSUME_NONNULL_END
Copy the code

#import “CusObject+extension.h” in cusObject. m:

#import "CusObject. H "#import "CusObject+extension.h" @implementation -(void)testMethod_Extension {NSLog(@"%@", name); NSLog(@"%@", self.nameTwo); } - (void)dealloc {NSLog(@"🍀🍀🍀 CusObject deallocing"); } @endCopy the code

If the#import "CusObject+extension.h"Introduction onCusObject.mIn the saidextensionMember variables, attributes, and methods in the.

If #import “CusObject+extension.h” is introduced into CusObject.m, then member variables, attributes, and methods in extension can only be used inside the class.

Note: #import “CusObject+extension.h” at the top of cusObject. h At this point, CusObject+extension.h is in front of the definition of The CusObject class. The definition of CusObject has not been completed, so Extension must not find CusObject.

We can place #import “CusObject+extension.h” below as follows:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface CusObject : NSObject

@end

NS_ASSUME_NONNULL_END

#import "CusObject+extension.h"
Copy the code

Note: ⚠ ️ ⚠ ️

  • No matter in.mor.hThe introduction ofextension.extensionAll member variables defined in the.

  • in.mThe introduction ofextension, where the defined member variables, attributes, and methods are private.

  • Extension is introduced in.m, where member variables, attributes, and methods defined can only be used inside the class.

  • When extension is introduced in.h, attributes and methods are public, and member variables are private by default. We can add @public to make extension public, and use -> when accessing extension. (There are some differences between C/C++ and Objective-C in the use of. And ->. OC is a superset of C, but here it is not identical to C.)

  • Adding member variables directly to the class definition in.m will cause an error message indicating that the member variables are protected when accessed externally. You can also add @public.

object->array = @[@(1), @ (2)]; ❌ ❌// Instance variable 'array' is protected
objc->name = @"chm"; ❌ ❌// Instance variable 'name' is private
Copy the code

Extension is different from Cateogry

  1. extensionYou can add member variables,categoryMember variables cannot be added. The runtime does not load the class until the class is loaded into memory. At this point, the memory layout of the class has been determined (the compiler also optimizes the order of the member variables to ensure that the class uses the least amount of memory in accordance with the memory alignment principle). Adding member variables will destroy the memory layout of the class. The address for each member variable is determined at compile time, and the address offset for each member variable is fixed (memory offset (hard-coded) relative to the class’s starting address).
  2. extensionAt compile time it is decided that it is part of the class,categoryResolution at run time.extensionAt compile time and in header files@interfaceAnd in the implementation file@implementTogether to form a complete class,extensionWith the creation of the class came into being, but also with the extinction.categoryThe method in is determined at runtime and can run without implementation, whileextensionMethods are checked at the compiler, and no implementation will report an error.
  3. extensionGenerally used to hide the private information of the class, can not be directly extended for the system class, but you can create a system class subclass and then addextension.
  4. categoryYou can add categories to system-provided classes.
  5. extensioncategoryYou can add attributes, butcategoryProperty in cannot generate corresponding member variables as wellgettersetterMethod implementation.
  6. extensionCan’t be likecategoryThat has a separate implementation part (@implementationPart),extensionThe declared method must rely on the implementation part of the corresponding class.

The Category classification

Categories are a language feature added after Objective-C 2.0 that allows you to dynamically add methods to a class without changing or inheriting the original class. There are a few other application scenarios:

  1. You can separate the implementation of a class into several different files. There are several obvious benefits to doing this:
  • Can reduce the size of individual files.
  • Different functions can be organized into different onescategoryThe inside.
  • Multiple developers can work on a class.
  • You can load what you want on demandcategory.
  • Declare private methods.
  1. And then there’s the derivativecategoryA few other scenarios:
  • Emulate multiple inheritance (there are other ways to emulate multiple inheritanceprotocol).
  • theframeworkThe private method of the.

The category characteristics

  1. categoryMethods can only be extended to an existing class, not member variables.
  2. categoryYou can also add attributes to the@propertyWill only generatesettergetterIs not generatedsettergetterAnd member variables.
  3. ifcategoryThe method has the same name as the original method in the class and is called preferentially at runtimecategoryThe method in, that is,categoryMethods in the class will overwrite existing methods in the class, so try to ensure that the name of the method in the class is not the same as the name of the method in the original class. The solution to avoid this situation is to prefix the method name of the class, for examplecategory_.
  4. If multiplecategoryThe compiler decides which method to call at runtime, and the last method to participate in the compilation is called. We can do it atCompile SourcesDrag the order of different categories to test.
  5. Call priority,category> This class > Parent class. Priority callcategory, and then calls its own class method, and finally its parent class method. Note:categoryIt is added at run time, not compile time.

Note:

  • categoryMethod does not “completely replace” the existing method of the original class, that is, ifcategoryAnd the original classmethodA, thencategoryOnce the append is complete, there will be two methods in the class’s listmethodA.
  • categoryThe methods of the original class are placed at the front of the new method list, and the methods of the original class are placed at the back of the new method list, which is what we normally call itcategory“Overrides” methods of the same name. This is because the runtime looks up the list of methods and stops when it finds a method with the same name, not knowing that there may be other methods with the same name behind them.

Why can’t a category add a member variable?

Classes in Objective-C are represented by the Class type, which is actually a pointer to the objc_class structure as follows:

typedef struct objc_class *Class;
Copy the code

The objc_class structure is defined as follows:

// objc_class

struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;             // formerly cache pointer and vtable
class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

class_rw_t *data(a) const {
    return bits.data();
}

...
};

// class_data_bits_t

struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_tbits; .public:

    class_rw_t* data(a) const {
        return (class_rw_t*)(bits & FAST_DATA_MASK); }...// Get the class's ro data, even in the presence of concurrent realization.
    // fixme this isn't really safe without a compiler barrier at least
    // and probably a memory barrier when realizeClass changes the data field
    const class_ro_t *safe_ro(a) {
        class_rw_t *maybe_rw = data(a);if (maybe_rw->flags & RW_REALIZED) {
            // maybe_rw is rw
            return maybe_rw->ro(a); }else {
            // maybe_rw is actually ro
            return (class_ro_t*)maybe_rw; }}... };// class_rw_t

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext; Class firstSubclass; Class nextSiblingClass; .public:...const method_array_t methods(a) const {
        auto v = get_ro_or_rwe(a);if (v.is<class_rw_ext_t* > ()) {return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t* > () - >baseMethods()};
        }
    }

    const property_array_t properties(a) const {
        auto v = get_ro_or_rwe(a);if (v.is<class_rw_ext_t* > ()) {return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t*>()->baseProperties}; }}const protocol_array_t protocols(a) const {
        auto v = get_ro_or_rwe(a);if (v.is<class_rw_ext_t* > ()) {return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t*>()->baseProtocols}; }}};// class_ro_t

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t*baseProperties; .method_list_t *baseMethods(a) const {
        returnbaseMethodList; }... };Copy the code

In the string of data structure definitions above, ivars is const ivar_list_t *. In Runtime, the objC_class structure is fixed in size and it is not possible to add data to the structure. The const modifier is added, so ivars points to a fixed region and cannot change the value or increase the number of member variables.

Can I add attributes to a category?

Category can’t add instance variables, so can it add @Property?

Starting with the category structure: category_t definition:

// classref_t is unremapped class_t*
typedef struct classref * classref_t;
Copy the code
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);
    
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else returnprotocols; }};Copy the code

You can see from the category definition that you can add instance methods, class methods, and even implement protocols and add attributes, but you can’t add member variables. So why don’t you add attributes? In fact, categories allow you to add properties, which you can do with @ Property, but just because you can add @property doesn’t mean you can add “full version” properties, By adding a property, we usually mean that the compiler generates the corresponding member variables and the corresponding setter and getter methods for accessing the property. While you can write @property in a category, you don’t generate _ member variables, and you don’t generate implementations of getter and setter methods for added properties, so you can’t use dot syntax to call setter and getter methods despite adding properties. (Actually, the dot syntax can be written, but when called at runtime the method is not found: unrecognized selector sent to instance….) . We can now manually implement setter and getter access methods for properties via the associated Object.

Compile the file from clang to verify the above two problems

We’ll start by compiling the file in Clang (I suggest you try it yourself in Xcode and on the terminal). First define the following class CustomObject to declare only one property:

// CustomObject.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface CustomObject : NSObject

@property (nonatomic, copy) NSString *customProperty;

@end
NS_ASSUME_NONNULL_END

// CustomObject.m
#import "CustomObject.h"
@implementation CustomObject
@end
Copy the code

Then open the terminal and go to the folder where the customObject. m file is located. Execute clang-rewrite-objc CustomObject.m and generate the customObject. CPP file.

Struct CustomObject_IMPL CustomObject_IMPL

extern "C" unsigned long OBJC_IVAR_$_CustomObject$_customProperty;
struct CustomObject_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString * _Nonnull _customProperty;
};

// @property (nonatomic, copy) NSString *customProperty;

/* @end */
Copy the code

Seeing that the _customProperty member variable has been added for us, NSObject_IVARS is a member variable that each inherits from NSObject. Implementation CustomObject section:

// @implementation CustomObject

static NSString * _Nonnull _I_CustomObject_customProperty(CustomObject * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_CustomObject$_customProperty)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool.bool);

static void _I_CustomObject_setCustomProperty_(CustomObject * self, SEL _cmd, NSString * _Nonnull customProperty) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct CustomObject, _customProperty), (id)customProperty, 0.1); }
// @end
Copy the code

As you can see from our setter and getter methods for customProperty, the add property compiler automatically generates the member variables and corresponding setter and getter methods for the class. (This is just in contrast to the category not generated.) Now look at the implementation of the getter function:

return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_CustomObject$_customProperty));
Copy the code

Self is our input parameter, CustomObject * self, and it does a pointer addition. The OBJC_IVAR_$_CustomObject$_customProperty is the pointer offset of _customProperty relative to self.

// 1 is an unsigned long
extern "C" unsigned long OBJC_IVAR_$_CustomObject$_customProperty;

// 2 _customProperty member variable position relative to struct CustomObject
#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
extern "C" unsigned long int OBJC_IVAR_$_CustomObject$_customProperty __attribute__ ((used, section ("__DATA,__objc_ivar"))) =
__OFFSETOFIVAR__(struct CustomObject, _customProperty);

// 3 member variable list, see only our _customProperty
static struct/ * _ivar_list_t* / {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count;
    struct _ivar_t ivar_list[1].
} _OBJC_$_INSTANCE_VARIABLES_CustomObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_ivar_t),
    1{{(unsigned long int *)&OBJC_IVAR_$_CustomObject$_customProperty, "_customProperty"."@\"NSString\"".3.8}}};/ / _ivar_t definition
struct _ivar_t {
    // A pointer to the ivar offset
    unsigned long int *offset;  // pointer to ivar offset location
    const char *name;
    const char *type;
    unsigned int alignment;
    unsigned int  size;
};
Copy the code

See that member variables are accessed by pointer offsets that are fixed to the structure’s internal storage layout. By the time a category is integrated into its corresponding class, the layout of the class is fixed, and you cannot add new member variables to it.

Clang: NSObject customcategory.h

#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (customCategory)

@property (nonatomic, copy) NSString *categoryProperty_one;
@property (nonatomic, strong) NSMutableArray *categoryProperty_two;

- (void)customInstanceMethod_one;
- (void)customInstanceMethod_two;
+ (void)customClassMethod_one;
+ (void)customClassMethod_two;

@end
NS_ASSUME_NONNULL_END
Copy the code

NSObject + customCategory. M file:

#import "NSObject+customCategory.h"
@implementation NSObject (customCategory)
- (void)customInstanceMethod_one {
    NSLog(@"🧑 ‍ 🍳 % @ invokeing".NSStringFromSelector(_cmd));
}
- (void)customInstanceMethod_two {
    NSLog(@"🧑 ‍ 🍳 % @ invokeing".NSStringFromSelector(_cmd));
}
+ (void)customClassMethod_one {
    NSLog(@"🧑 ‍ 🍳 % @ invokeing".NSStringFromSelector(_cmd));
}
+ (void)customClassMethod_two {
    NSLog(@"🧑 ‍ 🍳 % @ invokeing".NSStringFromSelector(_cmd));
}
@end
Copy the code

Browse excerpts from NSObject+ customcategory.cpp file:

// @implementation NSObject (customCategory)
static void _I_NSObject_customCategory_customInstanceMethod_one(NSObject * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_24_5w9yv8jx63bgfg69gvgclmm40000gn_T_NSObject_customCategory_740f85_mi_0, NSStringFromSelector(_cmd));
}
static void _I_NSObject_customCategory_customInstanceMethod_two(NSObject * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_24_5w9yv8jx63bgfg69gvgclmm40000gn_T_NSObject_customCategory_740f85_mi_1, NSStringFromSelector(_cmd));
}
static void _C_NSObject_customCategory_customClassMethod_one(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_24_5w9yv8jx63bgfg69gvgclmm40000gn_T_NSObject_customCategory_740f85_mi_2, NSStringFromSelector(_cmd));
}
static void _C_NSObject_customCategory_customClassMethod_two(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_24_5w9yv8jx63bgfg69gvgclmm40000gn_T_NSObject_customCategory_740f85_mi_3, NSStringFromSelector(_cmd));
}
// @end
Copy the code

We see that we have just two instance methods and two class methods, no setter and getter methods for adding member variables or any properties. You can’t add attributes to a category.

// Two instance methods
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_NSObject_$_customCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"customInstanceMethod_one"."v16@0:8", (void *)_I_NSObject_customCategory_customInstanceMethod_one},
    {(struct objc_selector *)"customInstanceMethod_two"."v16@0:8", (void *)_I_NSObject_customCategory_customInstanceMethod_two}}
};

// Two class methods
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_NSObject_$_customCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"customClassMethod_one"."v16@0:8", (void *)_C_NSObject_customCategory_customClassMethod_one},
    {(struct objc_selector *)"customClassMethod_two"."v16@0:8", (void *)_C_NSObject_customCategory_customClassMethod_two}}
};

// Two attributes
static struct/ * _prop_list_t* / {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[2].
} _OBJC_$_PROP_LIST_NSObject_$_customCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,
    {{"categoryProperty_one"."T@\"NSString\",C,N"},
    {"categoryProperty_two"."T@\"NSMutableArray\",&,N"}}};Copy the code

See the structure for class methods, instance methods, and properties:

static struct _category_t _OBJC_The $_CATEGORY_NSObject_The $_customCategory __attribute__ ((used.section(" __DATA, __objc_const"))) = 
{
    "NSObject".0.// &OBJC_CLASS_$_NSObject,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_customCategory,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_customCategory,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NSObject_$_customCategory,
};
Copy the code

The above three constitute an instance of the _category_T structure.

The category principle

Even if we don’t import the category header, the methods in the category are added to the main class, and we can call the methods in the category by performing Selector: and so on:

  • willcategoryAnd its main class (or metaclass) registered in the hash table, forming a mapping relationship. (ExplicitInitDenseMap<Class, category_list>)
  • If the main class (or metaclass) is implemented, rebuild its list of methods.

Category related data structures

I don’t know where to start. I know that the category starts to load when the Runtime is initialized, so I’m not going to describe the loading process of the Runtime. Let’s start by peeling away the relevant data structures layer by layer.

A diagram like this can be drawn:

category_t

typedef struct category_t *Category;

// classref_t is unremapped class_t*
typedef struct classref * classref_t;

struct category_t {
    const char *name; // The name of the class
    classref_t cls; // The class to which it belongs
    struct method_list_t *instanceMethods; // List of instance methods
    struct method_list_t *classMethods; // List of class methods
    struct protocol_list_t *protocols; // Protocol list
    struct property_list_t *instanceProperties; // Instance property list
    
    // Fields below this point are not always present on disk.
    struct property_list_t* _classProperties; // Class attributes?
    
    // Returns a list of class/metaclass methods
    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    
    // Protocol list, metaclass has no protocol list
    protocol_list_t *protocolsForMeta(bool isMeta) {
        // nullptr is returned if it is a metaclass.
        // But in the load_categories_nolock function
        // There is an indication that protocols was added to the metaclass
        // But at attachCategories
        The protocolsForMeta function returns nullptr
        // There should be no actual addition
        if (isMeta) return nullptr;
        else returnprotocols; }};/* * category_t::propertiesForMeta * Return a category's instance or class properties. * Return the category's instance or class properties. * hi is the image containing the category. * Hi is the image containing the category. * /
property_list_t *
category_t::propertiesForMeta(bool isMeta, struct header_info *hi)
{
    if(! isMeta)return instanceProperties;
    else if (hi->info() - >hasCategoryClassProperties()) return _classProperties;
    else return nil;
}
Copy the code

method_t

Method data structure, very simple.

struct method_t {
    SEL name; // Method name, selectors
    const char *types; // Method type
    
    // using MethodListIMP = IMP;
    MethodListIMP imp; // Method implementation

    // Sort by the address of the selectors
    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator(a) (const method_t& lhs,
                         const method_t& rhs)
        { returnlhs.name < rhs.name; }}; };Copy the code

Refer to STL for STD ::binary_function use

entsize_list_tt

Let’s start with the very long entsize_list_TT, which can be thought of as a data container with its own iterator for iterating through all elements. (ENT should be short for Entry)

/*********************************************************************** * entsize_list_tt
      
        * Generic implementation of an array of non-fragile structs. * * Element is the struct type (e.g. method_t) * Element is a structural type, such as: Method_t * List is the specialization of entsize_list_tt (e.g., method_list_t) Method_list_t * FlagMask is used to stash extra bits in the entsize field (e.g. method list fixup markers FlagMask used in excess of a hidden in the * entsize fields such as: list of methods to repair tag * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
      ,>
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    // The capacity of the container
    uint32_t count;
    // The first element
    Element first;
    
    // Size of the element
    uint32_t entsize(a) const {
        return entsizeAndFlags & ~FlagMask;
    }
    
    / / remove the flags
    uint32_t flags(a) const {
        return entsizeAndFlags & FlagMask;
    }

    Return a reference to the specified element based on the index, where I can be equal to count
    // this means you can return after the last element
    Element& getOrEnd(uint32_t i) const {
        // assert that I cannot exceed count
        ASSERT(i <= count);
        // Set the first address by I * ensize()
        // It then converts to an Element pointer and returns the pointer to the content
        // The return type is Element reference
        return *(Element *)((uint8_t *)&first + i*entsize()); 
    }
    
    // Return the Element reference in the index range
    Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }
    
    // Total memory occupied by the container, in bytes
    size_t byteSize(a) const {
        return byteSize(entsize(), count);
    }
    
    Entsize Specifies the memory size of a single element. Count is the number of elements
    static size_t byteSize(uint32_t entsize, uint32_t count) {
        Struct entsize_list_tt (struct entsize_list_tt);
        // uint32_t entsizeAndFlags + uint32_t count + Element first
        // The total length of the three member variables, then plus the length of (count - 1) elements
        return sizeof(entsize_list_tt) + (count- 1)*entsize;
    }

    // Copy a List
    List *duplicate(a) const {
        // set the byteSize() length space to 1
        auto *dup = (List *)calloc(this->byteSize(), 1);
        // Member variable assignment
        dup->entsizeAndFlags = this->entsizeAndFlags;
        dup->count = this->count;
        // Copy the contents of the original data from begin() to end() to dup->begin()
        // is in the space of the start address
        std::copy(begin(), end(), dup->begin());
        return dup;
    }
    
    // The declaration of a custom iterator is implemented below
    struct iterator;
    
    const iterator begin(a) const {
        // static_cast is a c++ operator that converts an expression to a type,
        // But there is no runtime type checking to ensure the safety of the conversion.
        // 把 this 强制转换为 const List *
        // 0 corresponds to the following implementation of the iterator constructor.
        // Point element to the first element
        
        // returns an iterator to the first element of the container
        return iterator(*static_cast<const List*>(this), 0); 
    }
    
    // Same as above, two const qualifiers are missing. Const means the function returns const immutable
    // Const indicates that the function is executing without changing the contents of the original object
    iterator begin(a) { 
        return iterator(*static_cast<const List*>(this), 0); 
    }
    
    // Return the iterator after the last element of the container,
    // Note that this is not pointing to the last element,
    // It points after the last one
    const iterator end(a) const { 
        return iterator(*static_cast<const List*>(this), count); 
    }
    
    // Same as above, two const constraints are removed
    iterator end(a) { 
        return iterator(*static_cast<const List*>(this), count); 
    }
    
    // Here are the custom iterators
    struct iterator {
        // The size of each element
        uint32_t entsize;
        // Index of the current iterator
        uint32_t index;  // keeping track of this saves a divide in operator-
        // Element pointer
        Element* element;

        // Type definition
        typedef std::random_access_iterator_tag iterator_category;
        typedef Element value_type;
        typedef ptrdiff_t difference_type;
        typedef Element* pointer;
        typedef Element& reference;
        
        // constructor
        iterator() {}// constructor
        iterator(const List& list, uint32_t start = 0)
            : entsize(list.entsize()),index(start)
            , element(&list.getOrEnd(start))
        { }

        // Override the operator
        const iterator& operator+ = (ptrdiff_t delta) {
            // The pointer is offset
            element = (Element*)((uint8_t *)element + delta*entsize);
            / / update the index
            index += (int32_t)delta;
            / / return * this
            return *this;
        }
        
        const iterator& operator- = (ptrdiff_t delta) {
            element = (Element*)((uint8_t *)element - delta*entsize);
            index -= (int32_t)delta;
            return *this;
        }
        
        // All of the following are += and -= applications
        const iterator operator + (ptrdiff_t delta) const {
            return iterator(*this) += delta;
        }
        const iterator operator - (ptrdiff_t delta) const {
            return iterator(*this) -= delta;
        }

        iterator& operator ++ () { *this+ =1; return *this; }
        iterator& operator- () {*this- =1; return *this; }
        iterator operator+ + (int) {
            iterator result(*this); *this+ =1; return result;
        }
        iterator operator- (int) {
            iterator result(*this); *this- =1; return result;
        }
        
        // The distance between two iterators
        ptrdiff_t operator - (const iterator& rhs) const {
            return (ptrdiff_t)this->index - (ptrdiff_t)rhs.index;
        }

        // Returns an element pointer or reference
        Element& operator * () const { return *element; }
        Element* operator- > ()const { return element; }
        operator Element& () const { return *element; }

        // Compare the address of element directly
        // No, == can be overloaded by the abstract Element type
        bool operator= = (const iterator& rhs) const {
            return this->element == rhs.element;
        }
        / / range
        bool operator! = (const iterator& rhs) const {
            return this->element ! = rhs.element; }/ / to compare
        bool operator < (const iterator& rhs) const {
            return this->element < rhs.element;
        }
        bool operator > (const iterator& rhs) const {
            return this->element > rhs.element; }}; };Copy the code

method_list_t

// Two bits of entsize are used for fixup markers.
// The last two digits of entsize are used to fix the tag
struct method_list_t : entsize_list_tt<method_t.method_list_t.0x3> {
    bool isUniqued(a) const;
    bool isFixedUp(a) const;
    void setFixedUp(a);
    
    // Return index of specified meth
    // (pointer distance divided by element width)
    uint32_t indexOfMethod(const method_t *meth) const {
        uint32_t i = 
            (uint32_t) (((uintptr_t)meth - (uintptr_t)this) / entsize());
        ASSERT(i < count);
        returni; }};Copy the code

Objc-runtime-new. mm

static uint32_t uniqued_method_list = 1;
bool method_list_t::isUniqued(a) const {
    return (flags() & uniqued_method_list) ! =0;
}

static uint32_t fixed_up_method_list = 3;
bool method_list_t::isFixedUp(a) const {
    return flags() == fixed_up_method_list;
}

void method_list_t::setFixedUp(a) {
    runtimeLock.assertLocked(a);ASSERT(!isFixedUp());
    entsizeAndFlags = entsize() | fixed_up_method_list;
}
Copy the code
/* Low two bits of mlist->entsize is used as the fixed-up marker. Method_list_t PREOPTIMIZED VERSION: PREOPTIMIZED VERSION: Method Lists from shared cache are 1 (uniqued) or 3 (uniqued and sorted) Method lists from shared cache are 1 (unique) or 3 (unique and sorted) (Protocol Method lists are not sorted because of their extra parallel data) Runtime fixed-up method lists get 3. FlagMask HardCode is 0x3 UN-PreOptimized VERSION of entsize_list_tt. Non-pre-optimized version: Method lists from shared cache are 1 (uniqued) or 3 (uniqued and sorted) Method lists from shared cache are 1 (uniqued) or 3 (unique and sorted) Shared cache's sorting and uniquing are not trusted, but do affect the location of the selector name string. Runtime fixed-up method lists get 2. */
// Static global variables
static uint32_t fixed_up_method_list = 3;
static uint32_t uniqued_method_list = 1;
Copy the code

Method_list_t’s FlagMask is 0x3, which is binary: 0b11, FlagMask calls the prepareMethodLists function before appending the category method to the class to determine if it needs to change the method list to uniqued and sorted.

protocol_list_t

struct protocol_list_t {
    // count is pointer-sized by accident.
    // count is the pointer width
    
    uintptr_t count;
    
    // typedef uintptr_t protocol_ref_t; // protocol_t *, but unremapped
    // protocol_ref_t is protocol_t *
    // The array length is 0, but it is run-time variable
    // Is a C99 notation that allows us to dynamically allocate memory at runtime.
    protocol_ref_t list[0]; // variable-size
    
    Entsize_list_tt = entsize_list_tt = entsize_list_tt
    // Because the array starts with 0
    size_t byteSize(a) const {
        return sizeof(*this) + count*sizeof(list[0]);
    }

// static inline void *
// memdup(const void *mem, size_t len)
/ / {
// void *dup = malloc(len);
// memcpy(dup, mem, len);
// return dup;
/ /}

// void *memcpy(void *destin, void *source, unsigned n);
// Copy n bytes from the start of the memory address indicated by source to the start of the memory address indicated by destination destin.

    // Copy the function
    protocol_list_t *duplicate(a) const {
        return (protocol_list_t *)memdup(this.this->byteSize());
    }

    // Type definition
    typedef protocol_ref_t* iterator;
    typedef const protocol_ref_t* const_iterator;
    
    / / pointer to begin
    const_iterator begin(a) const {
        return list;
    }
    
    iterator begin(a) {
        return list;
    }
    
    // End position pointer
    const_iterator end(a) const {
        return list + count;
    }
    iterator end(a) {
        returnlist + count; }};Copy the code

property_list_t

struct property_list_t : entsize_list_tt<property_t.property_list_t.0> {};Copy the code

Inheriting from entsize_list_TT, its FlagMask hardcode is 0.

property_t

struct property_t {
    const char *name;
    const char *attributes;
};
Copy the code

locstamped_category_t

struct locstamped_category_t {
    category_t *cat;
    / / the header data
    struct header_info *hi;
};
Copy the code

category_list

// The class nocopy_t constructor and destructor use the compiler's default generation to remove the copy constructor and assignment function
class category_list : nocopy_t {
    // combine variable _u
    union {
        // lc is formed with the struct below,
        Is_array represents an array or just a locstamped_category_t
        locstamped_category_t lc; // It takes 16 bytes
        
        struct {
            / / locstamped_category_t pointer
            locstamped_category_t *array; // 8 bytes, below 8 bytes
            
            // Switch storage modes according to the amount of data. A data structure similar to Weak_entry_t,
            // Use an array of fixed length 4 to store the weak reference pointer, and then use an array of fixed length greater than 4 to store the weak reference pointer.
            // It is similar to class_rw_ext_t, which holds a pointer to the list of methods, or an array. Each array element is a pointer to the list of methods
            
            // this aliases with locstamped_category_t::hi
            // which is an aliased pointer
            / / a domain
            uint32_t is_array :  1;
            uint32_t count    : 31;
            uint32_t size     : 32;
        };
    } _u;

public:
    // constructor
    // _u initializers lc and struct are nullptr
    category_list() : _u{{nullptr.nullptr}} {}// _u lc initializes
    category_list(locstamped_category_t lc) : _u{{lc}} { }
    
    // Enter category_list &&
    category_list(category_list &&other) : category_list() {
        std::swap(_u, other._u);
    }
    
    // destructor
    ~category_list()
    {
        if (_u.is_array) {
            free(_u.array); }}// conunt represents the number of category_t
    uint32_t count(a) const
    {
        if (_u.is_array) return _u.count;
        return _u.lc.cat ? 1 : 0;
    }

    // Memory capacity
    // sizeof(locstamped_category_t) should be 16
    uint32_t arrayByteSize(uint32_t size) const
    {
        return sizeof(locstamped_category_t) * size;
    }
    
    / / locstamped_category_t pointer
    const locstamped_category_t *array(a) const
    {
        return _u.is_array ? _u.array : &_u.lc;
    }

    / / stitching
    void append(locstamped_category_t lc)
    {
        if (_u.is_array) {
            // If it is an array
            if (_u.count == _u.size) {
                // If the storage is full
                / / capacity
                // Have a typical malloc growth:
                // - size <= 8: grow by 2
                // - size <= 16: grow by 4
                // - size <= 32: grow by 8
                // ... etc
                _u.size += _u.size < 8 ? 2 : 1< < (fls(_u.size) - 2);
                _u.array = (locstamped_category_t *)reallocf(_u.array, arrayByteSize(_u.size));
            }
            // Place locstamped_category_t in the array
            _u.array[_u.count++] = lc;
        } else if (_u.lc.cat == NULL) {
            // If no data has been saved, use lc member variables
            _u.lc = lc;
        } else {
            // Convert the original locstamped_category_t LC into an array of Pointers to store locstamped_category_t
            locstamped_category_t *arr = (locstamped_category_t *)malloc(arrayByteSize(2));
            arr[0] = _u.lc;
            arr[1] = lc;

            _u.array = arr;
            _u.is_array = true;
            _u.count = 2;
            _u.size = 2; }}// Erase, (just erase the content, not free the original 16 bytes of space)
    void erase(category_t *cat)
    {
        if (_u.is_array) {
            // If it is already saved as an array, it is traversed
            for (int i = 0; i < _u.count; i++) {
                if (_u.array[i].cat == cat) {
                    // shift entries to preserve list order
                    // Move the array and delete cat
                    memmove(&_u.array[i], &_u.array[i+1].arrayByteSize(_u.count - i - 1));
                    return; }}}else if (_u.lc.cat == cat) {
            // Set to nil if there is only one cat
            _u.lc.cat = NULL;
            _u.lc.hi = NULL; }}};Copy the code

UnattachedCategories

// unattachedCategories is a static global variable belonging to namespace objC that holds unappended categories.
static UnattachedCategories unattachedCategories;
Copy the code
// a Class that publicly inherits from ExplicitInitDenseMap
      ,>
// The abstract arguments are Class and category_list
Key = category_list; Class = value = category_list
class UnattachedCategories : public ExplicitInitDenseMap<Class, category_list>
{
public:
    // Add locstamped_category_t to the specified CLS
    void addForClass(locstamped_category_t lc, Class cls)
    {
        runtimeLock.assertLocked(a);if (slowpath(PrintConnecting)) {
            _objc_inform("CLASS: found category %c%s(%s)",
                         cls->isMetaClass()?'+' : The '-',
                         cls->nameForLogging(), lc.cat->name);
        }

        // Add 
      
        to unattachedCategories
      ,>
        auto result = get().try_emplace(cls, lc);
        if(! result.second) {// If the CLS already has category_list, add LC to the category_list array
            // append is the append function of category_list
            // result.first->second is the category_list corresponding to CLS
            result.first->second.append(lc); }}// Add previously categories data to CLS
    void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked(a);ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get(a);auto it = map.find(previously);

        if(it ! = map.end()) {
            category_list &list = it->second;
            if (flags & ATTACH_CLASS_AND_METACLASS) {
                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                
                The attachCategories function appends categories to the class
                
                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
            } else {
                attachCategories(cls, list.array(), list.count(), flags);
            }
            map.erase(it); }}void eraseCategoryForClass(category_t *cat, Class cls)
    {
        runtimeLock.assertLocked(a);auto &map = get(a);auto it = map.find(cls);
        if(it ! = map.end()) {
            category_list &list = it->second;
            // Remove cat (locstamped_category_t) from category_list
            list.erase(cat);
            if (list.count() = =0) {
                // If category_list is empty, 
      
        is removed
      ,>
                map.erase(it); }}}void eraseClass(Class cls)
    {
        runtimeLock.assertLocked(a);// Delete 
      
        for specified CLS
      ,>
        get().erase(cls); }};Copy the code

The category_t data structure is not complicated. We saw the _category_t structure generated when we compiled our class and classification files with clang. Now let’s look at the.cpp file after clang:

_OBJC__CATEGORY_INSTANCE_METHODS_NSObject__customCategory

The list of compiler-generated instance methods is stored in the objc_const section of the DATA segment (struct /*_method_list_t*/).

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_NSObject_$_customCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"customInstanceMethod_one"."v16@0:8", (void *)_I_NSObject_customCategory_customInstanceMethod_one},
    {(struct objc_selector *)"customInstanceMethod_two"."v16@0:8", (void *)_I_NSObject_customCategory_customInstanceMethod_two}}
};
Copy the code

_OBJC__CATEGORY_CLASS_METHODS_NSObject__customCategory

The list of compiler-generated class methods is stored in the objc_const section of the DATA segment (struct /*_method_list_t*/).

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_NSObject_$_customCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"customClassMethod_one"."v16@0:8", (void *)_C_NSObject_customCategory_customClassMethod_one},
    {(struct objc_selector *)"customClassMethod_two"."v16@0:8", (void *)_C_NSObject_customCategory_customClassMethod_two}}
};
Copy the code

_OBJC__PROP_LIST_NSObject__customCategory

The compiler generates a list of attributes stored in the objc_const section of the DATA segment (struct /*_prop_list_t*/).

static struct/ * _prop_list_t* / {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[2].
} _OBJC_$_PROP_LIST_NSObject_$_customCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,
    {{"categoryProperty_one"."T@\"NSString\",C,N"},
    {"categoryProperty_two"."T@\"NSMutableArray\",&,N"}}};Copy the code

Also note the fact that category names are used to name lists and the category structure itself, and are static, so we can’t repeat category names in the same compilation unit or we’ll get a compilation error.

_OBJC__CATEGORY_NSObject__customCategory

The compiler generates _category_t itself _OBJC_$_CATEGORY_NSObject_$_customCategory and initializes it with the instance methods, class methods, and property list generated earlier. You also use OBJC_CLASS_$_NSObject to dynamically specify the class to which _OBJC_$_CATEGORY_NSObject_$_customCategory belongs.

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_The $_NSObject;

static struct _category_t _OBJC_The $_CATEGORY_NSObject_The $_customCategory __attribute__ ((used.section(" __DATA, __objc_const"))) = 
{
    "NSObject".0.// &OBJC_CLASS_$_NSObject,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_customCategory,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_customCategory,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NSObject_$_customCategory,
};

/ / set the CLS
static void OBJC_CATEGORY_SETUP_$_NSObject_$_customCategory(void ) {
    _OBJC_$_CATEGORY_NSObject_$_customCategory.cls = &OBJC_CLASS_$_NSObject;
}

#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
    (void *)&OBJC_CATEGORY_SETUP_$_NSObject_$_customCategory,
};
Copy the code

L_OBJC_LABEL_CATEGORY_$

Finally, the compiler stores a struct _category_T * array L_OBJC_LABEL_CATEGORY_$in the objc_catList section of the DATA section. If there are multiple categories, An array of length is generated for the run-time category loading, and this is where the compiler comes to an end.

static struct _category_t *L_OBJC_LABEL_CATEGORY_$[1] __attribute__((used.section(" __DATA, __objc_catlist.regular.no_dead_strip"))) = {
    &_OBJC_$_CATEGORY_NSObject_$_customCategory,
};
Copy the code

When will the _category_T data be appended to the class? Or is it stored in memory somewhere waiting for us to call an instance function or class function inside it? We know that all the classification data is appended to the class itself. It is not similar to the weak mechanism or the associated object mechanism, and then prepare another hash table to store data, and then query and process data according to the object address.

Let’s look at how the data of the classification is appended to the class.

Refer to the link

Reference link :🔗

  • Analyze the Runtime in OC2.0 with the working principle of category
  • Understand Objective-C: categories in depth
  • IOS has a Category loading process and +load
  • IOS Runtime (17) : _dyLD_OBJC_notify_register
  • IOS Development Runtime (27): _read_images