Load, C++ constructor, initialize call sequence

Load function call timing

  • loadMethods in thedyldThe 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

  • initializeIs called when the class is first loaded, throughlookUpImpOrForwardFunction call.
  • Read more about the OC Principles of Exploration: a slow search process for methods.

C++ constructor function call timing

  • ifC++The constructor is inobjcSource code, then it is instatic_initIs called in, we are inIOS basic principle: class loading principleAnalysis.
    • The order of its function calls isdoModInitFunctions -> libSystem_initializer -> _objc_init -> static_init -> getLibobjcInitializers.
    • static_initin_dyld_objc_notify_registerBefore, so inloadMethods before.
  • ordinaryC++Constructors are also indoModInitFunctionsInside is called, but is inlibSystem_initializerAfter, so inloadMethod.

Call order summary

  1. objcIn the sourceC++Constructor.
  2. loadMethods.
  3. ordinaryC++Constructor.
  4. initializeFunction.

What is Runtime

  • runtimeIs made up ofC,C++,assemblyA set of implementationsAPIforOCThe 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,rweAnd so on.
  • Peacetime writtenOCThe code, as the program runs, will actually be converted toRuntimetheCLanguage code,RuntimeisObject-CBehind the scenes.

Third, the essence of the method, what is SEL? What is IMP? What is the relationship between the two?

  1. The essence of a method is to send a message, which has the following flow:
    1. Quick find ~ (objc_msgSend) ~ cache_tCache messages.
    2. Slow to find ~ recursively their own living parent ~lookUpImpOrForward.
    3. Message not found ~ dynamic method resolution ~resolveInstanceMethod.
    4. The message is quickly forwarded ~forwardingTargetForSelector.
    5. The message is forwarded slowly ~methodSignatureForSelector & forwardInvocation.
  2. selIs the method number, inread_imagesAnd then it’s compiled into memory.
  3. impThat’s our function implementation pointer, findimpIt’s the process of finding the function.
  4. selIt’s like a table of contents for a booktittle.
  5. impIt’s the page number of a book.
  6. To look for specific functions is to look for specific chapters in this book
    1. We first know what we want to see ~tittle(sel).
    2. According to the page number of the directory ~ (imp).
    3. Turn to the details.

Can I add instance variables to the compiled class? Can I add instance variables to classes created at run time?

  1. You cannot add instance variables to a compiled class.
    • Our compiled instance variables are stored in theroOnce compiled, the memory structure is completely determined and cannot be modified.
    • You can add methods and properties (associated objects) to a class by classification
  2. 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
  • SSLStudentInherited fromSSLPerson.
  • SSLStudentIn the implementationinitMethod, 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_msgSendSuperFunction 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
  • throughclass_getSuperclassFunction 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 isobjc_msgSendSuper2Its structure is similar toobjc_msgSendSuperIt’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 callobjc_msgSendSuper2Function, is passed inThe current class, obtained by assembly source codeThe parent classCall again,CacheLookupThe delta function, I’m going to sum hereObjc_msgSend processThe same.
  • If I call thetaobjc_msgSendSuperFunction, which is passed in directly to get goodThe parent classAnd then jump toL_objc_msgSendSuper2_bodyIs transferred toCacheLookupFunction.

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
  • selfIt’s the hidden parameter that we pass in which isSSLStudentInstance object of theisaNature isSSLStudentClass.

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 bothobjc_msgSendThe call,Objc_msgSend processWe have analyzed inCompile the source codeChinese will pass firstisa & ISA_MASKAccess 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 becausesslStored in theclassThe first address of,Compile the source codeThrough theClass address & ISA_MASKOf course you can get itclassAnd 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_nameThe 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:

  1. <ViewController: 0x7fc7b050a940>for- (void)viewDidLoadtheid selfPressure stack.
  2. viewDidLoadfor- (void)viewDidLoadtheSEL _cmdPressure stack.
  3. ViewControllerOf the structureclassPressure stack.
  4. <ViewController: 0x7fc7b050a940>Of the structurereceiverPressure stack.
  5. SSLPersonforsslPressure stack.
  6. <SSLPerson: 0x7ffee876e1e8>forpersonPressure stack.

What would weigh on the stack:

  1. This methodParameters can be pushed,viewDidLoadthe(id self, SEL _cmd).
  2. forobjc_superThis structure argument is equivalent to the code below creating onessl_objc_superA 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, useThe singletonMore 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 momentselfisSSLPersonInstance,SSLPersonThere is nothing in thestudentInstanceMethodtheSELCan’t find the correspondingSELSo 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_replaceMethodFunction:
    • If theClassNo specification existsSEL,class_replaceMethodThe action of theta is thetaclass_addMethodThe same;
    • If theClassExists the specifiedSEL,class_replaceMethodThe action of theta is thetamethod_setImplementationThe same.

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