preface

In the previous cache article, we found objc_msgSend, or Runtime, while looking for the call process. Let’s take a look at the Runtime documentation, and then we’ll look at the Runtime.

The Runtime profile

Runtime we know is Runtime, when we think of Runtime we think of compile time, what’s the difference between them?

  • Compile time: Compilation isThe compiler helps you translate the source code into code that the machine can recognizeCompile time is only the most basic language check error detection, including lexical analysis, syntax analysis and so on.Just because a program compiles does not mean it will run successfully. You could say it’s one at compile timeStatic phaseType errors can obviously be detected directly.
  • The runtime:The code runs and is loaded into memory.At this timeSpecific pair typeInstead of simply scanning and analyzing the code, if something goes wrong, the program will crash. The runtime isDynamic stage, and begin to integrate concretely with the operating environment.

We can call the Runtime in three ways:

    1. OC, for example:[person sayNB]
    1. NSObject, for example:isKindOfClass
    1. RuntimeApi, such as:class_getInstanceSize

There are three ways to call the Runtime:

Nature of method

Nature of method

    1. Code preparation:

Define a WSPerson class, call the method, and compile it into C++ code with clang:

@interface WSPerson : NSObject

- (void)sayNB;
- (void)sayGood:(NSString *)job;

@end

@implementation WSPerson

- (void)sayNB {
    NSLog(@"%s", __func__);
}

/ / call:
WSPerson *person = [WSPerson alloc];
[person sayNB];
[person sayGood: @"coder"];

// C++
WSPerson *person = ((WSPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("WSPerson"), sel_registerName("alloc"));
((void(*) (id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
((void(*) (id, SEL, NSString(*))void *)objc_msgSend)((id)person, sel_registerName("sayGood:"), (NSString *)&__NSConstantStringImpl__var_folders_7t_93mn_rbs0p1d7bkzy4z_f0s40000gn_T_main_e9af44_mi_1);

@end
Copy the code

From code analysis, we know that the essence of the method is objc_msgSend, and the objc_msgSend call requires the receiver of the message and sel+ parameters. Can we call the method directly by imitating objc_msgSend in C++? So let’s verify that

    1. objc_msgSendCall method directly

Note: 1. Import the header file #import

2. Disable objc_msgSend: target->Build Setting-> search MSG ->Enable strict Checking of obc_msgSend Calls changes to NO.

Then mock the call:

WSPerson *person = [WSPerson alloc];
[person sayNB];
objc_msgSend(person, sel_registerName("sayNB"));
Copy the code

The print result is as follows:



  • Conclusion:[person sayNB]andobjc_msgSend(person, sel_registerName("sayNB"))Is equivalent.

Call the parent class method

  • Let’s call the superclass method and try again:
@interface WSPerson : NSObject
- (void)sayNB;
- (void)sayGood;
@end

@implementation WSPerson
- (void)sayNB {
    NSLog(@"%s", __func__);
}
- (void)sayGood {
    NSLog(@" Parent method: %s", __func__);
}
@end

@interface WSTeacher : WSPerson
@end

@implementation WSTeacher
- (void)sayGood {
    [super sayGood];
}

@end
Copy the code

Objc_msgSendSuper: objc_msgSendSuper:

static void _I_WSTeacher_sayGood(WSTeacher * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("WSTeacher"))}, sel_registerName("sayGood"));
}
Copy the code
  • objc_msgSendSuperIt should be sending a message to the parent class. Let’s goObjc4-818.2 -View source code:
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... * / )
Copy the code

Objc_msgSendSuper takes two arguments, the second is SEL, and the first is an objc_super structure with the following structure:

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver; / / the recipient

    /// Specifies the particular superclass of the instance to message. 
#if! defined(__cplusplus) && ! __OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};
Copy the code

We are now using __OBJC2__, so objc_super has only two arguments: receiver and super_class. The super_class has a comment, which is the first class to look up. Let’s try it.

  • useobjc_msgSendSuperTo call the parent method:
struct objc_super ws_objc_super;
ws_objc_super.receiver = person;
ws_objc_super.super_class = teacher.class;
objc_msgSendSuper(&ws_objc_super, @selector(sayGood));

struct objc_super ws_objc_super1;
ws_objc_super1.receiver = person;
ws_objc_super1.super_class = person.class;
objc_msgSendSuper(&ws_objc_super1, @selector(sayGood));

/ / the result
// 2021-07-01 10:51:36.953720+0800 RuntimeDemo[1977:2219575] parent method: -[WSPerson sayGood]
// 2021-07-01 10:51:36.953777+0800 RuntimeDemo[1977:2219575] parent method: -[WSPerson sayGood]
Copy the code
  • instructionssuper_classWhether it isteacher.classorperson.class, does not affect the result.

conclusion

  • From calling the methods of the current and parent classes, we conclude:The essence of the method is message sending. thenobjc_msgSendWhat is the exact process of, let’s move onAssembly analysis.

Objc_msgSend assembly

  • inobjcSource searchobjc_msgSendAnd then findobjc-msg-arm64.sAssembly file



_objc_msgSend

  • First of all,objc_msgSendThere are two parametersMessage receiverandsel
  • cmpiscompareThe meaning of comparison,p0Is the receiver of the message,# 0is0This is takep0and0Do contrast.SUPPORT_TAGGED_POINTERSIs the small object pattern, which is defined as:
#if __arm64__.#define SUPPORT_TAGGED_POINTERS 1.// true arm64
#else
// arm64_32
#define SUPPORT_TAGGED_POINTERS 0.#endif
Copy the code

We are now a simulator, so SUPPORT_TAGGED_POINTERS is 0, so go down. Return LReturnZero (p0 = 0); return LReturnZero (p0 = 0);

LReturnZero:
	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	ret

	END_ENTRY _objc_msgSend
Copy the code

So it just returns nil.

GetClassFromIsa_p16

  • Keep going down herep13=isaAnd then look at the main methodGetClassFromIsa_p16Is passed in when calledp13, 1, x0:
// src=p13=isa, needs_auth=1, auth_address=x0
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if ! needs_auth */

#if SUPPORT_INDEXED_ISA //arm64
	// Indexed isa
	mov	p16, \src			// optimistically set dst = src
	tbz	p16, #ISA_INDEX_IS_NPI_BIT, 1f	// done if not non-pointer isa
	// isa in p16 is indexed
	adrp	x10, _objc_indexed_classes@PAGE
	add	x10, x10, _objc_indexed_classes@PAGEOFF
	ubfx	p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
	ldr	p16, [x10, p16, UXTP #PTRSHIFT]	// load class from array
1:

#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
	mov	p16, \src // assign SRC to p16
.else
	// 64-bit packed isa
	ExtractISA p16, \src, \auth_address / / get p16
.endif
#else
	// 32-bit raw isa
	mov	p16, \src

#endif

.endmacro
Copy the code

GetClassFromIsa_p16 is a macro definition that takes three arguments. Since SUPPORT_INDEXED_ISA is 1 in arm64, skip __LP64__, where needs_auth=1, so look at the ExtractISA method:

.macro ExtractISA
	and    $0, $1, #ISA_MASK
.endmacro
Copy the code

ExtractISA

ExtractISA is also a macro definition, and is an &, where $1&isa_mask is assigned to $0, and $0 is p16, so this is based on isa and ISA_MASK. And then I’m going to go back to GetClassFromIsa_p16, p13, 1, x0, and I’m going to get the class first, so the comment on this line says p16 = class.

The problem

    1. Why get it firstclass
    • First, let’s analyzeobjc_msgSendIt’s mostly about findingcache, butcacheIs in theclassWhich is why we have to get it firstclassOnly then can we find themclassIn thecache.