blog

ランタイム(VI)。 superClassの本質

これをcpp.呼び出しに変換すると、最初の引数が構造体、構造体の最初のメンバがレシーバ、2番目のメンバが親クラスのオブジェクトになります。 つまり、[スーパークラス]はMJStudentを表示します。...

Oct 18, 2020 · 7 min. read
シェア

demo

#import "MJStudent.h" #import <objc/runtime.h> @implementation MJStudent /* [super message]の基本的な実装は 1.メッセージ・レシーバーはサブクラスのオブジェクトのままである。 2.親クラスからメソッドの実装を見つける */ struct objc_super { __unsafe_unretained _Nonnull id receiver; // メッセージ・レシーバー __unsafe_unretained _Nonnull Class super_class; // メッセージ・レシーバーの親クラス }; - (void)run { // super呼び出しの受け手はMJStudentオブジェクトのままである。 [super run]; // struct objc_super arg = {self, [MJPerson class]}; // // objc_msgSendSuper(arg, @selector(run)); // // // NSLog(@"MJStudet......."); } - (instancetype)init { if (self = [super init]) { NSLog(@"[self class] = %@", [self class]); // MJStudent NSLog(@"[self superclass] = %@", [self superclass]); // MJPerson NSLog(@"--------------------------------"); // objc_msgSendSuper({self, [MJPerson class]}, @selector(class)); NSLog(@"[super class] = %@", [super class]); // MJStudent NSLog(@"[super superclass] = %@", [super superclass]); // MJPerson } return self; } @end //@implementation NSObject // //- (Class)class //{ // return object_getClass(self); //} // //- (Class)superclass //{ // return class_getSuperclass(object_getClass(self)); //} // //@end

このMJStudent.mをcppに変換します。

スーパーラン]のソースコードは

((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MJStudent"))}, sel_registerName("run"));

簡略化

struct objc_super { __unsafe_unretained _Nonnull id receiver; // メッセージ・レシーバー __unsafe_unretained _Nonnull Class super_class; // メッセージ・レシーバーの親クラス };
 struct objc_super arg = {self, [MJPerson class]};
 objc_msgSendSuper(arg, @selector(run));
  • objc_msgSendSuper をコールします。第1引数は構造体で、構造体の第1メンバは受信機、第2メンバは親クラス・オブジェクトです。

  • 意味

    1. レシーバーはサブクラスのオブジェクトのままです。2. メソッドの実装は、親クラスから検索されます。

クラスとスーパークラスの実装を推測します:


- (Class)class
{
 return object_getClass(self);
}
- (Class)superclass
{
 return class_getSuperclass(object_getClass(self));
}

LLVM

変換されたC++コードから、superがobjc_msgSendSuperメソッドを呼び出していることがわかります。しかし、変換されたC++は、最終的なソースコードとほとんど同じであるにもかかわらず、まだ異なっています。

基本的には objc_msgSendSuper2 メソッドの呼び出しで、親クラス・オブジェクトではなく現在のクラス・オブジェクトを渡します。しかし、本質的には同じであり、親クラスから開始し、上へ移動します。

方法1:コンパイルを見るためにポイントを中断。

方法II:コンパイルの他の見方

コード行は25行目にあるので、":25 "を検索します。

方法3:LLVM中間コード

コードはLLVMフロントエンドを通過し、LLVM中間コードに変換されます。LLVM中間コードはクロスプラットフォームに使用され、どのプラットフォームでも同じで、マシンやCPU、プラットフォームに関連するアセンブリコードやマシンコードに変換されます。

可視コールは objc_msgSendSuper2 です。

  • clang -emit-llvm -S main.m LLVM 中間コードは

isKindOfClass と isMemberOfClass

NSObjectのいくつかのメソッドはオープンソースです。

objc-723 で検索:

NSObject.mm
+ (Class)superclass { return self->superclass; } - (Class)superclass { return [self class]->superclass; } + (BOOL)isMemberOfClass:(Class)cls { return object_getClass((id)self) == cls; } - (BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; } + (BOOL)isKindOfClass:(Class)cls { for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } - (BOOL)isKindOfClass:(Class)cls { for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; }

デモ

NSLog(@"%d", [[NSObject class] isKindOfClass:[NSObject class]]); // 1 NSLog(@"%d", [[NSObject class] isMemberOfClass:[NSObject class]]); // 0 NSLog(@"%d", [[MJPerson class] isKindOfClass:[MJPerson class]]); // 0 NSLog(@"%d", [[MJPerson class] isMemberOfClass:[MJPerson class]]); // 0 // このコードのメソッド呼び出し側は、クラスに関係なくYESを返す。 NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1 NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0 NSLog(@"%d", [MJPerson isKindOfClass:[MJPerson class]]); // 0 NSLog(@"%d", [MJPerson isMemberOfClass:[MJPerson class]]); // 0 // id person = [[MJPerson alloc] init]; // // NSLog(@"%d", [person isMemberOfClass:[MJPerson class]]); // 1 // NSLog(@"%d", [person isMemberOfClass:[NSObject class]]); // 0 // // NSLog(@"%d", [person isKindOfClass:[MJPerson class]]); // 1 // NSLog(@"%d", [person isKindOfClass:[NSObject class]]); // 1 // // // NSLog(@"%d", [MJPerson isMemberOfClass:object_getClass([MJPerson class])]); //1 // NSLog(@"%d", [MJPerson isKindOfClass:object_getClass([NSObject class])]); // 1 // // NSLog(@"%d", [MJPerson isKindOfClass:[NSObject class]]); // 1
  • -isMemberOfClass 現在のオブジェクトのクラス・オブジェクト。
  • isMemberOfClassの現在のメタクラス・オブジェクトは、入力されるクラス・オブジェクトですか?
  • -isKindOfClass現在のオブジェクトのクラス・オブジェクトが、渡されたクラス・オブジェクトであるかどうか、あるいはクラス・オブジェクトのサブクラスであるかどうか。
  • +isKindOfClass 現在のクラス・オブジェクトが、渡されたメタクラス・オブジェクトであるか、そのサブクラス・オブジェクトのメタクラス・オブジェクトであるか。
  • そのため、+isMemberOfClass, +isKindOfClassの引数はメタクラスのオブジェクトでなければなりません。
注意:`NSLog(@"%d", [MJPerson isKindOfClass:[NSObject class]]); `表示結果は1である。.NSObjectのメタクラスのsuperClassポインターはNSObjectのクラス・オブジェクトを指しているからだ。

/*
 1.printなぜ呼び出しが成功するのか?
 
 2.なぜselfなのか?.nameViewControllerのような別のものになる。
 */
- (void)viewDidLoad {
 [super viewDidLoad];
 
// struct abc = {
// self,
// [ViewController class]
// };
// objc_msgSendSuper2(abc, sel_registerName("viewDidLoad"));
 
// NSObject *obj2 = [[NSObject alloc] init];
//
// NSString *test = @"123";
// int age = 10;  
 
 id cls = [MJPerson class];
 void *obj = &cls;
 [(__bridge id)obj print];
 
 long long *p = (long long *)obj;
 
 NSLog(@"%p",p);
 
 NSLog(@"%p",*p);
 
 NSLog(@"%p %p", *(p+2), [ViewController class]);
 
// struct MJPerson_IMPL
// {
// Class isa;
// NSString *_name;
// };
}

分析

  • clsはpersonへのisaポインタに相当し、objはpersonオブジェクトに相当するので、objにprintメソッドを送ることができます。

印刷結果

my name is <ViewController: 0x7fda>
  • スタック空間は上位から下位に割り当てられ、clsとobjはともにスタック空間にあり、clsは上位アドレスに、objは下位アドレスにあります。
  • print メソッドは現在のオブジェクトのプロパティを表示します。つまり、オブジェクトへのポインタの中身はa+1です。
  • objはpersonオブジェクトに相当し、clsはisaポインタに相当します。つまり、出力されるのはcls+1ポインタの内容です。
  • clsが前のNSString *test = @"123 "で初期化された場合、printは "123 "になります。
  • ここでも、[super viewDidLoad];が存在するためです。以上から、superの本質はobjc_msgSendSuper2の呼び出しであり、第一引数として渡されるstructローカル変数を生成していることは明らかです。つまり、clsは上記の構造体です。
  • また、構造体メンバはロー・アドレスからハイ・アドレスに割り当てられることも知られています。
  • つまり、ローカルのプリントアウトは構造体であり、構造体のアドレスはその最初のメンバのアドレスと同じなので、この質問は現在のコントローラオブジェクトをプリントします。 selfはスタック上にあり、ViewControllerオブジェクトを指しています。NSString *test = @"123";

long long *p = (long long *)obj; NSLog(@"%p",p); NSLog(@"%p",*p); NSLog(@"%p %p", *(p+2), [ViewController class]);
  • objからpへの強力な変換.
  • NSLog(@"%p",p); objに格納されているもの、つまりclsのアドレスを表示します。これは p/x obj で確認できます。
  • NSLog(@"%p",*p);表示されるのは、p(obj)がclsを保持しているので、*pはclsが指すメモリアドレスです。つまり、clsの中身です。
  • NSLog(@"%p %p", *(p+2), [ViewController class]);. *: p の中身は cls のアドレスで、cls のアドレスを 2 単位分シフトしたものが ViewController のクラスオブジェクトです。
  • x/4g 0x00007ffeed8754a8,0x00007ffeed8754a8はclsアドレスです。このコマンドは、0x00007ffeed8754a8から始まる4つの8バイトのメモリに何が格納されているかを表示します。int age = 10;最初の8バイト0x000000010238a070は[MJPerson class]、2番目の8バイトは現在のコントローラself、3番目の8バイトは.上記を確認しました。

注意:もしclsが上記のコードをint age = 10;で初期化するとクラッシュします。

  • viewDidLoad viewDidLoadの本質は
void viewDidLoad(id self,SEL _cmd){ }

暗黙の引数である self と _cmd は通常スタック空間に置かれますが、arm64 では通常レジスタに置かれます。

Read next

nwjsはパッケージ化されたvueプロジェクトを開始する。

nwjs how to start a packageaged vue project download nwjs official website download usually the official website download will be slower this time directly with the Taobao mirror direct download ps://..../m

Oct 18, 2020 · 2 min read