Welcome to the iOS Exploration series.
- IOS explores the alloc process
- IOS explores memory alignment &malloc source code
- IOS explores ISA initialization & pointing analysis
- IOS exploration class structure analysis
- IOS explores cache_T analysis
- IOS explores the nature of methods and the method finding process
- IOS explores dynamic method resolution and message forwarding mechanisms
- IOS explores the dyLD loading process briefly
- The loading process of iOS explore classes
- IOS explores the loading process of classification and class extension
- IOS explores isa interview question analysis
- IOS Explore Runtime interview question analysis
- IOS explores KVC principles and customization
- IOS explores KVO principles and customizations
- IOS explores the principle of multithreading
- IOS explores multi-threaded GCD applications
- IOS explores multithreaded GCD underlying analysis
- IOS explores NSOperation for multithreading
- IOS explores multi-threaded interview question analysis
- IOS Explore the locks in iOS
- IOS explores the full range of blocks to read
Writing in the front
After analyzing the loading process of classes in iOS, this article will take a closer look at how classes are loaded
(Please have some understanding of the class loading process before starting this article.)
First, classification preliminary exploration
1. Output CPP clang
Create a new category for FXPerson, FXPerson-FX
The terminal outputs CPP using CLANG
clang -rewrite-objc FXPerson+FX.m -o cate.cpp
Copy the code
2. Low-level analysis
Starting at the bottom of the CPP file, we first see that the classification is stored in the __objc_catList of the __DATA section of the MachO file
static struct _category_t *L_OBJC_LABEL_CATEGORY_$[1] __attribute__((used.section(" __DATA, __objc_catlist.regular.no_dead_strip"))) = {
&_OBJC_$_CATEGORY_FXPerson_$_FX,
};
Copy the code
Second, you can see the structure of the FXPerson classification
static struct _category_t _OBJC_The $_CATEGORY_FXPerson_The $_FX __attribute__ ((used.section(" __DATA, __objc_const"))) =
{
"FXPerson".0.// &OBJC_CLASS_$_FXPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_FXPerson_$_FX,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_FXPerson_$_FX,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_FXPerson_$_FX,
};
Copy the code
Go to the objC source code and search category_t to see the structure of the underlying category (_category_t search failed)
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t* _classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
Copy the code
According to the FXPerson classification structure and the underlying classification structure comparison:
name
: The class name, not the class namecls
Class object:instanceMethods
: instance methods stored on a classificationclassMethods
: Class methods stored on a classificationprotocols
: Protocol implemented by classificationinstanceProperties
: Instance properties defined by a class, but we generally add attributes to a class by associating objects_classProperties
: Class attributes defined by the classification
Why do classified methods keep instance methods and class methods separate?
Because classes and metaclasses are constantly compiled before, instance methods exist in classes, and class methods exist in metaclasses, where their methods belong has been determined; The categories were added later
Second, the loading of classification
From the last article we know that classes are divided into lazy loading classes and non-lazy loading classes, their loading timing is different, so what is the classification? Let’s explore them in turn
1. Lazy classes and lazy classes
Classes and classes do not implement the +load method
A lazy-loaded class is known to be tired and only loaded when it is called to send a message
Add categories in two places: _read_images and methodizeClass
// Discover categories.
// Discover and process all categories
for (EACH_HEADER) {
// The outer loop iterates through the current class to find the Category array corresponding to the class
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
// The inner loop iterates through all categories of the current class
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
// First, register a Category with the class to which it belongs. If the class is already implemented, the list of methods for the class is reconstructed.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// Add a Category to the Class's value, which is an array of all categories corresponding to the Class
addUnattachedCategoryForClass(cat, cls, hi);
// Add method, protocol, and property for the Category to the Class
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : ""); }}// This is the same logic as above except that this is the same logic as above except that this is the same logic as above except that this is the same logic as above
// According to the following logic, it is possible to add a Category to the original class from a code point of view
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)", cls->nameForLogging(), cat->name); }}}}Copy the code
static void methodizeClass(Class cls)
{...// Attach categories.
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/); . }Copy the code
To avoid the other class calls to _read_images and methodizeClass (red box) being difficult to debug, add some code to each (green box) to better explore FXPerson’s classes and categories
From the function call stack on the left:
- to
Lazy loading class
Send a message,lookupOrForward
->realizeClassMaybeSwiftAndLeaveLocked
realizeClassMaybeSwiftMaybeRelock
->realizeClassWithoutSwift
Start loading memorymethodizeClass
Handle superclass, metaclass relationship, call print twiceunattachedCategoriesForClass
returnNULL
- another
_read_images
The load class is not called
Accidentally overturned, first change the non-lazy loading class and lazy loading classification study it
2. Non-lazy loading classes and lazy loading classes
Only the class implements the +load method
① Same research method, run the project
Dabble in the dyLD loading process
Class loading process
- Program started
dyld
->libSystem_initializer
->libdispatch_init
->_os_object_init
_objc_init
->map_images
->map_images_nolock
->_read_images
realizeClassWithoutSwift
->methodizeClass
Load the class into memorymethodizeClass
Handle superclass, metaclass relationship, call print twiceunattachedCategoriesForClass
returnNULL
- another
_read_images
The load class is not called
② It’s the same with lazy loading classes and lazy loading classes… Continue exploring RW (Structural analysis of reading Classes without understanding)
(lldb) p/x cls
(Class) $0 = 0x0000000100001188
(lldb) p (class_data_bits_t *)0x00000001000011a8
(class_data_bits_t *) The $1 = 0x00000001000011a8
(lldb) p The $1->data()
(class_rw_t *) $2 = 0x0000000103200060
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
version = 7
ro = 0x00000001000010e8
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000010b0
arrayAndFlag = 4294971568
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff9383faa0
demangledName = 0x0000000000000000
}
(lldb) p $3.methods
(method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000010b0
arrayAndFlag = 4294971568
}
}
}
(lldb) p $4.list
(method_list_t *) A $5 = 0x00000001000010b0
(lldb) p *A $5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 2
first = {
name = "cate_doClass"
types = 0x0000000100000faa "v16@0:8"
imp = 0x0000000100000e00 (objc-debug`+[FXPerson(FX) cate_doClass] at FXPerson+FX.m:23)
}
}
}
(lldb) p A $5->get(0)
(method_t) $7 = {
name = "cate_doClass"
types = 0x0000000100000faa "v16@0:8"
imp = 0x0000000100000e00 (objc-debug`+[FXPerson(FX) cate_doClass] at FXPerson+FX.m:23)
}
(lldb) p A $5->get(1)
(method_t) $8 = {
name = "load"
types = 0x0000000100000faa "v16@0:8"
imp = 0x0000000100000e90 (objc-debug`+[FXPerson load] at FXPerson.m:12)
}
(lldb)
Copy the code
The first call handles the metaclass relationship metacls = realizeClassWithoutSwift(remapClass(CLS ->ISA()));
The metaclass stores the +load method of the FXPerson class and the +cate_doClass method of the FXPerson classification
-cate_doInstance
Instructions before methodizeClass unattachedCategoriesForClass has taken the classification method of load to the class
(3) Put the debug code before the methods of rW, and find that the methods are not assigned
(lldb) p *A $5
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory methodizeClass: class name: fxPerson-0x100001188Copy the code
After the breakpoint comes to the methods assignment, the classified methods are already lying in it
Method_list_t *list = ro->baseMethods() this step just puts ro->baseMethods into rW
(4) Run the project again, print ro at the first breakpoint, the classification method already exists…
Conclusion: The lazy-loaded classification is determined at compile time for both lazy-loaded and non-lazy-loaded classes
3. Non-lazy loading classes and non-lazy loading classes
Both classes and categories implement the +load method
Revert to the original debug code and run the project
MethodizeClass (methodizeClass
Two unattachedCategoriesForClass return is NULL
② The breakpoint then goes to Discover categories of _read_images and calls them in the order shown in the figure
addUnattachedCategoryForClass
Make an association mapping between classes/metaclasses and categories- call
remethodizeClass
callattachCategories
Deal with classification
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats); }}Copy the code
RemethodizeClass calls attachCategories to process the categories
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if(! cats)return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
method_list_t **mlists = (method_list_t* *)malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t* *)malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t* *)malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if(protolist) { protolists[protocount++] = protolist; }}auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
Copy the code
AttachCategories analysis:
- (Suppose both classifications are realized
+load
)while (i--)
“Is not what the Internet saysCompile Sources
It’s loaded in reverse order. It’s loaded firstFXPerson+FX
And as far as I’m concerned, it’s probably written for convenience
- call
attachLists
Add classification methods, attributes, protocols (Class loading processMore details)memmove
Move the original data to the endmemcpy
Copy the new data to the starting location
- The methods of the classification do not replace the methods that the class already has, that is, if both the classification and the class have
doInstance
After the class is attached, there will be two methods in the class’s listdoInstance
- The classified method is placed first in the list of new methods, and the class method is placed second in the list of new methods. This is why we usually say that the classified method “overwrites” the method of the same name, because
The runtime
inTo find the way
Is searched in the order of the list of methods, as long as it finds the corresponding name of the method, it will returnimp
But there may be another method with the same name behind
4. Lazy and non-lazy classes
Only classes implement the +load method
(1) First break to _read_images Discover categories, not remethodizeClass
(2) the breakpoint to methodizeClass, finally through unattachedCategoriesForClass take to value this time, and then through attachCategories added
load_images
prepare_load_methods
realizeClassWithoutSwift
methodizeClass
5. Summary of class and classification loading
-
Lazy loading class + lazy loading classification
- Class loading in
The first message is sent
While the classification load is inCompile time
- Class loading in
-
Non-lazy loading class + lazy loading class
- Class loading in
_read_images
, the loading of classification is inCompile time
- Class loading in
-
Non-lazy loading class + non-lazy loading class
- Class loading in
_read_images
The class is loaded after the class is loadedreMethodizeClass
- Class loading in
-
Lazy loading class + non-lazy loading class
- Class loading in
load_images
The class is loaded after the class is loadedmethodizeClass
- Class loading in
6. Disputes over the namesake methods of classes and classifications
- When a class and classification method have the same name, they must respond to the classification method (regardless of whether the class and classification are implemented or not)
+load
) - Class with the same name as more than one classification method
- If the classification is not implemented
+load
Method, then the responseCompile Sources
The last category - If one of them is implemented
+load
And the responseNon-lazy load classification
– becauseLazy load classification
It’s already loaded into memory at compile time, andNon-lazy load classification
Only loaded at runtime - If both are implemented
+load
And the responseCompile Sources
The last category
- If the classification is not implemented
Third, the loading of class extension
Class extension Extension is also called an anonymous classification to add attributes and methods to the current class
There are two forms:
- Directly in the
.m
New class extension in file - New class extension
.h
file
1. Loading of class extensions
The data comes to _read_image very early on, which is exactly what we do when we’re working with classes
But when you think about it, you already have a method implementation in the class, and do_hExtension is not enough
You can verify this by looking at the setter and getter methods for the property
From the figure above, it can be concluded that:
- Class extensions are compiled as part of the class at compile time
- Class extension reads ro directly at read time
2. Details of class expansion
Class extensions are not compiled into memory if they are not referenced (#import)
Four, load_image
In the previous article, it was mentioned that dyLD initializing image would trigger load_image. In the case of lazy loading class and non-lazy loading class, load_image is in the call stack when the class is loaded into memory, so we will explore in this case
When I press a breakpoint on the load_image implementation, I find that neither the class nor the class prints the +load method — load_image precedes the +load method
- Discover the load the methods –
prepare_load_methods
- Call + load the methods –
call_load_methods
1.prepare_load_methods
Discover and prepare the +load method
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if(! cls)continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods"); } realizeClassWithoutSwift(cls); assert(cls->ISA()->isRealized()); add_category_to_loadable_list(cat); }}/*********************************************************************** * prepare_load_methods * Schedule +load for classes in this image, any un-+load-ed * superclasses in other images, and any categories in this image. **********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
if(! cls)return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
/*********************************************************************** * add_class_to_loadable_list * Class cls has just become connected. Schedule it for +load if * it implements a +load method. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if(! method)return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
Copy the code
Prepare_load_methods analysis:
- through
_getObjc2NonlazyClassList
To obtainNon-lazy-loaded classes
The list of - through
schedule_class_load
Walk through these classes- The recursive call traverses the parent class
+load
Method to ensure that the parent class+load
The method order precedes the subclass - call
add_class_to_loadable_list
The class of+load
methodsloadable_classes
inside
- The recursive call traverses the parent class
- call
_getObjc2NonlazyCategoryList
Take out theNon-lazy load classification
The list of - Iterate over the category list
- through
realizeClassWithoutSwift
To prevent the class from not being initialized (if it is already initialized) - call
add_category_to_loadable_list
Loading classification+load
Methods toloadable_categories
- through
Now you can see the function call stack for the lazy and non-lazy classes
2.call_load_methods
Wake up +load method
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
Copy the code
- through
objc_autoreleasePoolPush
Push an automatic release pool do-while
Loop begins- Loop through the class
+load
Method until you can’t find it - Call once in a class
+load
methods
- Loop through the class
- Push an automatic release pool with objc_autoreleasePoolPop
5, initalize analysis
The Apple documentation on Initalize says so
Initializes the class before it receives its first message. Called before the class receives the first message. Discussion The runtime sends initialize to each classin a program exactly one time just before the class, or any class that inherits from it, is sent its first message from within the program. (Thus the method may never be invoked if the class is not used.) The runtime sends the initialize message to classes inA threadsafe spam. Superclasses receive this message before their subclasses. Runtime Send a threadsafe spam. Superclasses receive this message before their subclasses. Or any class it inherits from, it sends the first message in the program. (Therefore, the method may never be called when the class is not in use.) The runtime sends a thread-safe initialization message. The parent class must be called before the child class.Copy the code
Then we find lookUpImpOrForward in objC source code
lookUpImpOrForward
->initializeAndLeaveLocked
->initializeAndMaybeRelock
->initializeNonMetaClass
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{...if(initialize && ! cls->isInitialized()) { cls = initializeAndLeaveLocked(cls, inst, runtimeLock); . }... }static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
return initializeAndMaybeRelock(cls, obj, lock, true);
}
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{... initializeNonMetaClass (nonmeta); ...}Copy the code
Call the parent class Initialize recursively in initializeNonMetaClass, and then call callInitialize
/*********************************************************************** * class_initialize. Send the '+initialize' message on demand to any * uninitialized class. Force initialization of superclasses first. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void initializeNonMetaClass(Class cls)
{... supercls = cls->superclass;if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
...
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]", pthread_self(), cls->nameForLogging()); }}... }Copy the code
CallInitialize is a normal message to send
void callInitialize(Class cls)
{((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
Copy the code
Conclusions about Initalize:
initialize
Called before the first method of the class or its subclass is called (before the message is sent)- Add only in the class
initialize
But if you don’t use it, you won’t call itinitialize
- Of the parent class
initialize
Methods are executed before subclasses - When a subclass is not implemented
initialize
Method, the parent class is calledinitialize
Methods; The subclass implementationinitialize
Method overrides the parent classinitialize
methods - When more than one classification is implemented
initialize
Method, which overrides the methods in the class and executes only one (which executes the classifiers last loaded into memory)
Write in the back
From class structure, message sending, DYLD to the loading process of class and classification, the author has carried on a small wave of exploration of the process of loading -> use, next will start to share the dry matter – the bottom level of the questions