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.m
In the saidextension
Member 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.m
or.h
The introduction ofextension
.extension
All member variables defined in the. -
in.m
The 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
extension
You can add member variables,category
Member 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).extension
At compile time it is decided that it is part of the class,category
Resolution at run time.extension
At compile time and in header files@interface
And in the implementation file@implement
Together to form a complete class,extension
With the creation of the class came into being, but also with the extinction.category
The method in is determined at runtime and can run without implementation, whileextension
Methods are checked at the compiler, and no implementation will report an error.extension
Generally 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
.category
You can add categories to system-provided classes.extension
和category
You can add attributes, butcategory
Property in cannot generate corresponding member variables as wellgetter
和setter
Method implementation.extension
Can’t be likecategory
That has a separate implementation part (@implementation
Part),extension
The 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:
- 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 ones
category
The inside. - Multiple developers can work on a class.
- You can load what you want on demand
category
. - Declare private methods.
- And then there’s the derivative
category
A few other scenarios:
- Emulate multiple inheritance (there are other ways to emulate multiple inheritance
protocol
). - the
framework
The private method of the.
The category characteristics
category
Methods can only be extended to an existing class, not member variables.category
You can also add attributes to the@property
Will only generatesetter
和getter
Is not generatedsetter
和getter
And member variables.- if
category
The method has the same name as the original method in the class and is called preferentially at runtimecategory
The method in, that is,category
Methods 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_
. - If multiple
category
The compiler decides which method to call at runtime, and the last method to participate in the compilation is called. We can do it atCompile Sources
Drag the order of different categories to test. - Call priority,
category
> This class > Parent class. Priority callcategory
, and then calls its own class method, and finally its parent class method. Note:category
It is added at run time, not compile time.
Note:
category
Method does not “completely replace” the existing method of the original class, that is, ifcategory
And the original classmethodA
, thencategory
Once the append is complete, there will be two methods in the class’s listmethodA
.category
The 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:
- will
category
And 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