Load, C++ constructor, initialize call sequence
Load function call timing
load
Methods in thedyld
The loading flow is called, and the function call flow is:doModInitFunctions
->libSystem_initializer
->_objc_init
->_dyld_objc_notify_register
->load_images
– >.- Read more about the underlying principles of iOS: DyLD application loading.
Initialize function call timing
initialize
Is called when the class is first loaded, throughlookUpImpOrForward
Function call.- Read more about the OC Principles of Exploration: a slow search process for methods.
C++ constructor function call timing
- if
C++
The constructor is inobjc
Source code, then it is instatic_init
Is called in, we are inIOS basic principle: class loading principleAnalysis.- The order of its function calls is
doModInitFunctions
->libSystem_initializer
->_objc_init
->static_init
->getLibobjcInitializers
. static_init
in_dyld_objc_notify_register
Before, so inload
Methods before.
- The order of its function calls is
- ordinary
C++
Constructors are also indoModInitFunctions
Inside is called, but is inlibSystem_initializer
After, so inload
Method.
Call order summary
objc
In the sourceC++
Constructor.load
Methods.- ordinary
C++
Constructor. initialize
Function.
What is Runtime
runtime
Is made up ofC
,C++
,assembly
A set of implementationsAPI
forOC
The language adds object-oriented, runtime functionality.- The runtime (
Runtime
) is when the determination of data types is deferred from compile time to run time, for examplecategory
,rwe
And so on. - Peacetime written
OC
The code, as the program runs, will actually be converted toRuntime
theC
Language code,Runtime
isObject-C
Behind the scenes.
Third, the essence of the method, what is SEL? What is IMP? What is the relationship between the two?
- The essence of a method is to send a message, which has the following flow:
- Quick find ~ (
objc_msgSend
) ~cache_t
Cache messages. - Slow to find ~ recursively their own living parent ~
lookUpImpOrForward
. - Message not found ~ dynamic method resolution ~
resolveInstanceMethod
. - The message is quickly forwarded ~
forwardingTargetForSelector
. - The message is forwarded slowly ~
methodSignatureForSelector & forwardInvocation
.
- Quick find ~ (
sel
Is the method number, inread_images
And then it’s compiled into memory.imp
That’s our function implementation pointer, findimp
It’s the process of finding the function.sel
It’s like a table of contents for a booktittle
.imp
It’s the page number of a book.- To look for specific functions is to look for specific chapters in this book
- We first know what we want to see ~
tittle
(sel
). - According to the page number of the directory ~ (
imp
). - Turn to the details.
- We first know what we want to see ~
Can I add instance variables to the compiled class? Can I add instance variables to classes created at run time?
- You cannot add instance variables to a compiled class.
- Our compiled instance variables are stored in the
ro
Once compiled, the memory structure is completely determined and cannot be modified. - You can add methods and properties (associated objects) to a class by classification
- Our compiled instance variables are stored in the
- It can be added as long as it is not registered in memory, as shown in the following code:
Void createClassAddIvar(void) {// Create a Class with objc_allocateClassPair const char * className = "MyClass"; Class SelfClass = objc_getClass(className); if (! SelfClass){ Class superClass = [NSObject class]; SelfClass = objc_allocateClassPair(superClass, className, 0); } // Add three member variables using class_addIvar class_addIvar(SelfClass, "name", sizeof(NSString *), log2(_Alignof(NSString *)), @encode(NSString *)); class_addIvar(SelfClass, "age", sizeof(NSString *), log2(_Alignof(NSString *)), @encode(NSString *)); class_addIvar(SelfClass, "sex", sizeof(NSString *), log2(_Alignof(NSString *)), @encode(NSString *)); objc_registerClassPair(SelfClass); } // Print the member of MyClass void logIvars(void) {const char * className = "MyClass"; Class SelfClass = objc_getClass(className); unsigned int outCount; Ivar * ivars = class_copyIvarList(SelfClass, &outCount); for (unsigned int i = 0; i < outCount; i ++) { Ivar ivar = ivars[i]; NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)]; NSLog (@ "-- -- -- -- -- -- -- -- the key: % @", key); }} / / print result -- -- -- -- -- -- -- -- the key: the name -- -- -- -- -- -- -- -- the key: the age -- -- -- -- -- -- -- -- the key: sexCopy the code
[self class] and [super class]
Create the SSLPerson and SSLStudent classes:
@interface SSLPerson : NSObject
@end
@implementation SSLPerson
@end
@interface SSLStudent : SSLPerson
@end
@implementation SSLStudent
- (instancetype)init
{
if (self = [super init]) {
NSLog(@"%@ - %@",[self class],[super class]);
}
return self;
}
@end
Copy the code
SSLStudent
Inherited fromSSLPerson
.SSLStudent
In the implementationinit
Method, print in method[self class]
.[super class]
And what do they print out?
Results analysis
SSLStudent: [self class] SSLStudent: [self class] SSLStudent: [self class] SSLStudent: [self class]
Clang-rewrite-objc sslStudent.m -o sslStudent.cpp
Objc_msgSend (self, Sel_registerName ("class")) // [super class] compiled C++ code objc_msgSendSuper({self,class_getSuperclass(objc_getClass("SSLStudent"))},sel_registerName("class"))Copy the code
[super class]
Programming intoC++
After isobjc_msgSendSuper
Function call, look at its structure:objc_msgSendSuper(struct objc_super *super, SEL op, ... ) struct objc_super { __unsafe_unretained _Nonnull id receiver; __unsafe_unretained _Nonnull Class super_class; }; Copy the code
- through
class_getSuperclass
Function get toThe parent class
, as a parameter.
The objc_msgSendSuper method is the old version of the call method, assembly and source check the actual function call:
- What’s really called is
objc_msgSendSuper2
Its structure is similar toobjc_msgSendSuper
It’s the same:objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) Copy the code
See the implementation of objc_msgSendSuper2:
- You can see the call
objc_msgSendSuper2
Function, is passed inThe current class
, obtained by assembly source codeThe parent class
Call again,CacheLookup
The delta function, I’m going to sum hereObjc_msgSend processThe same. - If I call theta
objc_msgSendSuper
Function, which is passed in directly to get goodThe parent class
And then jump toL_objc_msgSendSuper2_body
Is transferred toCacheLookup
Function.
The difference between self class and super class is that [super class] starts with the parent class, and ultimately depends on the implementation of class methods.
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
Copy the code
self
It’s the hidden parameter that we pass in which isSSLStudent
Instance object of theisa
Nature isSSLStudent
Class.
5. Memory offset interview questions
Memory offset interview question 1
Create the SSLPerson class:
@interface SSLPerson : NSObject
- (void)say1;
@end
@implementation SSLPerson
- (void)say1
{
NSLog(@"%s",__func__);
}
@end
Copy the code
Add the following code to viewDidLoad, and you can guess what it will print:
- (void)viewDidLoad {
[super viewDidLoad];
SSLPerson *person = [[SSLPerson alloc] init];
[person say1];
Class cls = [SSLPerson class];
void *ssl = &cls;
[(__bridge id)ssl say1];
}
Copy the code
View the print result:
-[SSLPerson say1]
-[SSLPerson say1]
Copy the code
Results analysis:
- The essence of both
objc_msgSend
The call,Objc_msgSend processWe have analyzed inCompile the source code
Chinese will pass firstisa & ISA_MASK
Access to theclass
, and then search the method. [person say1]
It can be called successfully, and we all understand that without too much explanation.[(__bridge id)kc say1]
It can also be called successfully becausessl
Stored in theclass
The first address of,Compile the source code
Through theClass address & ISA_MASK
Of course you can get itclass
And the following process is the same.
Here is:
Memory offset interview question 2
Modify the SSLPerson class:
@interface SSLPerson : NSObject
@property (nonatomic, copy) NSString *ssl_name;
- (void)say1;
@end
@implementation SSLPerson
- (void)say1
{
NSLog(@"%s - %@",__func__,self.ssl_name);
}
@end
Copy the code
Add code to viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
SSLPerson *person = [[SSLPerson alloc] init];
person.ssl_name = @"SSL";
[person say1];
Class cls = [SSLPerson class];
void *ssl = &cls;
[(__bridge id)ssl say1];
}
Copy the code
View the print result:
-[SSLPerson say1] - SSL
-[SSLPerson say1] - <SSLPerson: 0x6000015e48d0>
Copy the code
Results analysis:
Clang-rewrite-objc sslPerson. m -o sslPerson. CPP to get sslPerson. CPP:
OBJC_IVAR_$_SSLPerson$_ssl_name = __OFFSETOFIVAR__(struct SSLPerson, _ssl_name); Ssl_name _I_SSLPerson_ssl_name(SSLPerson * self, SEL _cmd) {return self + OBJC_IVAR_$_SSLPerson$_ssl_name; }Copy the code
- You can see
_ssl_name
The value of, is calculated first its offset, and then the memory translation to get the value.
LLDB debugging verification:
Here is:
Memory offset interview question 3
Look at the following code, stack structure print:
- (void)viewDidLoad { // (id self, SEL _cmd) [super viewDidLoad]; // struct {receiver, class} class CLS = [SSLPerson class]; void *ssl = &cls; SSLPerson *person = [SSLPerson alloc]; person.ssl_name = @"SSL"; Void *sp = (void *)&self; void *end = (void *)&person; long count = (sp - end) / 0x8; for (long i = 0; i < count; i++) { void *address = sp - 0x8 * i; if (i == 1) { NSLog(@"%p : %s",address, *(char **)address); } else { NSLog(@"%p : %@",address, *(void **)address); }}}Copy the code
View the print result:
0x7ffee876e208 : <ViewController: 0x7fc7b050a940>
0x7ffee876e200 : viewDidLoad
0x7ffee876e1f8 : ViewController
0x7ffee876e1f0 : <ViewController: 0x7fc7b050a940>
0x7ffee876e1e8 : SSLPerson
0x7ffee876e1e0 : <SSLPerson: 0x7ffee876e1e8>
Copy the code
Results analysis:
<ViewController: 0x7fc7b050a940>
for- (void)viewDidLoad
theid self
Pressure stack.viewDidLoad
for- (void)viewDidLoad
theSEL _cmd
Pressure stack.ViewController
Of the structureclass
Pressure stack.<ViewController: 0x7fc7b050a940>
Of the structurereceiver
Pressure stack.SSLPerson
forssl
Pressure stack.<SSLPerson: 0x7ffee876e1e8>
forperson
Pressure stack.
What would weigh on the stack:
This method
Parameters can be pushed,viewDidLoad
the(id self, SEL _cmd)
.- for
objc_super
This structure argument is equivalent to the code below creating onessl_objc_super
A temporary variable, so it can also be pressed.struct objc_super ssl_objc_super; ssl_objc_super.super_class = class; ssl_objc_super.receiver = receiver; Copy the code
Method Swizzling
The basic principle of
Take a look at the Method Swizzling code:
@interface SSLRuntimeTool : NSObject /** switch method @param CLS switch object @param oriSEL Original method number @param swizzledSEL switch method number */ + (void)ssl_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL; @end @implementation SSLRuntimeTool + (void)ssl_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL { if (! CLS) NSLog(@" The exchange class passed cannot be empty "); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); method_exchangeImplementations(oriMethod, swiMethod); } @endCopy the code
Method Swizzling uses:
@interface SSLPerson : NSObject
- (void)personInstanceMethod;
@end
@implementation SSLPerson
- (void)personInstanceMethod {
NSLog(@"%s",__func__);
}
@end
@interface SSLStudent : SSLPerson
@end
@implementation SSLStudent
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[SSLRuntimeTool ssl_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(studentInstanceMethod)];
});
}
- (void)studentInstanceMethod {
[self studentInstanceMethod];
NSLog(@"%s",__func__);
}
@end
Copy the code
- Prevent repeated exchange, use
The singleton
More secure.
Method Swizzling principle diagram:
A pit point
The code above makes the relevant call:
- (void)viewDidLoad {
[super viewDidLoad];
SSLPerson *person = [[SSLPerson alloc] init];
[person personInstanceMethod];
}
Copy the code
Error:
'-[SSLPerson studentInstanceMethod]: unrecognized selector sent to instance 0x600002fbc400'
Copy the code
- Because of method swapping, we actually call this method:
- (void)studentInstanceMethod { [self studentInstanceMethod]; NSLog(@"%s",__func__); } Copy the code
- At this moment
self
isSSLPerson
Instance,SSLPerson
There is nothing in thestudentInstanceMethod
theSEL
Can’t find the correspondingSEL
So it willCRASH
.
Solve a problem:
@implementation SSLRuntimeTool + (void)ssl_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{ if (! CLS) NSLog(@" The exchange class passed cannot be empty "); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); // personInstanceMethod(sel) - studentInstanceMethod(imp) // Try adding the method you want to swap - studentInstanceMethod BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod)); If (success) {studentInstanceMethod (SEL) -personInstancemethod (imp) class_replaceMethod(CLS, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); } else {// implementations has - exchangemethod_exchangeImplementations (oriMethod, swiMethod); } } @endCopy the code
class_replaceMethod
Function:- If the
Class
No specification existsSEL
,class_replaceMethod
The action of theta is thetaclass_addMethod
The same; - If the
Class
Exists the specifiedSEL
,class_replaceMethod
The action of theta is thetamethod_setImplementation
The same.
- If the
Pit point 2
If personInstanceMethod is not implemented, run the program and an infinite loop will occur:
Solve a problem:
@implementation SSLRuntimeTool + (void)ssl_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{ if (! CLS) NSLog(@" The exchange class passed cannot be empty "); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); if (! OriMethod) {// If oriMethod is nil, After replacement, copy swizzledSEL as an empty implementation that does nothing class_addMethod(CLS, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod)); Method_setImplementation (swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){NSLog(@" there's an empty IMP "); })); } // try adding the method you want to swap - studentInstanceMethod BOOL success = class_addMethod(CLS, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod)); Override a class_replaceMethod(CLS, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); } else {// implementations has - exchangemethod_exchangeImplementations (oriMethod, swiMethod); } } @endCopy the code