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.mLLVM 中間コードは
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 では通常レジスタに置かれます。





