Objective-Cは実際にはメソッドと自己とは何ですか? +ランタイム

selfと_cmdはどのようにしてメソッドになりますか? ディスパッチテーブルとカテゴリはどのように機能しますか? メタクラスとは何ですか? クラスには、ARCおよびMRCに実際のメソッドがいくつありますか? スウィズルはどのように機能しますか?

面白い? 猫へようこそ!



注意!



この記事は初心者開発者向けではありません... Objective-C開発者が知っておくべき多くの点を考慮していないことをおaびします。











クラスメソッドがあり、クラスインスタンスのメソッドがあります。 クラスにメソッドがあることを一時的に忘れましょう。後でこれに間違いなく戻ります。この方法で記事を読むときの混乱が少なくなります。

Objective-Cでメソッドがどのように検索されるかに特に注意を払いません。これには適切な記事があり、 Wikipediaでさえ十分です。



それで、私たちは始めています。











メソッドは、ISAディスパッチテーブルを使用して検索され、ダウンします。 そのため、Objective-Cのすべてのメソッドは、プライベートを含む仮想です。



したがって、セレクターを知ってメソッドに目を向けることができます。



ディスパッチテーブルのキーはSEL(セレクタ、 詳細 )、およびIMP値(実装、最も一般的なC関数)です。



メソッドは関数ですか? それについては後で詳しく説明します。



図では、子クラスのテーブルには親クラスのテーブルは含まれていませんが、構成が使用されています。 実際に確認してみましょう。



ディスパッチテーブルクラスの取得
... typedef struct objc_method *Method; ... struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; }
      
      





Human.h
 #import <Foundation/Foundation.h> @interface Human : NSObject @property (copy, nonatomic) NSString *name; @end
      
      





Human.m
 #import "Human.h" @implementation Human @end
      
      





main.m
 #import <Foundation/Foundation.h> #import <objc/runtime.h> #import "Human.h" void printAllMethodClass(Class clazz) { unsigned int count; Method *methods = class_copyMethodList(clazz, &count); for (int i = 0; i < count; i++) { Method method = methods[i]; SEL sel = method_getName(method); NSLog(@"%@", NSStringFromSelector(sel)); } } int main(int argc, const char * argv[]) { @autoreleasepool { printAllMethodClass([Human class]); } return 0; }
      
      





おわりに
2015-11-14 22:02:03.744 TestingRuntime [71448:6200105] .cxx_destruct

2015-11-14 22:02:03.746 TestingRuntime [71448:6200105] setName:

2015-11-14 22:02:03.746 TestingRuntime [71448:6200105] name



発言
プロパティ名を宣言したため、setNameメソッドとnameメソッドが生成されます





さて、Humanクラスのメソッドのテーブルを取得し、コンポジションで親テーブルが使用されるようにしました。 確かに、メソッドの中に.cxx_destructが見つかりました(フィールドがある場合はARCが追加され、フィールドがリリースされます)。これはこの記事のトピックではありません。

ディスパッチテーブルでさらに理解します。 カテゴリはどのように機能しますか? クラステーブルを拡張します。 これはどのように起こりますか? include / importはいつ使用しますか? いいえ、そうではありません。



ディスパッチテーブルのカテゴリ効果
Human + FooMethod.h
 #import "Human.h" @interface Human (FooMethod) @end
      
      





Human + FooMethod.m
 #import "Human+FooMethod.h" @implementation Human (FooMethod) - (void)fooMethod { NSLog(@"i send msg fooMethod"); } @end
      
      





main.m
 #import <Foundation/Foundation.h> #import "Human.h" int main(int argc, const char * argv[]) { @autoreleasepool { Human *human = [[Human alloc] init]; [human performSelector:@selector(fooMethod)]; } return 0; }
      
      





おわりに
2015-11-14 22:24:20.862 TestingRuntime [71509:6208985]メッセージfooMethodを送信します







プログラムがクラッシュせず、メソッドが呼び出されたのはなぜですか? この時点で、「fooMethod」メソッドはすでにディスパッチテーブルに存在しているためです。 コードでは、ファイル「Human + FooMethod.h」のインクルードをどこでも使用していないことに注意してください。 したがって、このカテゴリは、include / importを使用してインクルードしたファイルだけでなく、プロジェクト全体で機能します。 また、テーブルで衝突が発生するとどうなりますか? 未定義の動作。コードでカテゴリをどのように使用するかは問題ではありません。



次に、テーブルを手で広げます。 はい、実行時にメソッドを追加し、通常の関数をメソッドに変換します。



関数をメソッドにしてみましょう
Human.h
 #import <Foundation/Foundation.h> @interface Human : NSObject @property (copy, nonatomic) NSString *name; @end
      
      





main.m
 #import <Foundation/Foundation.h> #import <objc/runtime.h> #import "Human.h" void methodInRuntime(Human *self, SEL _cmd) { NSLog(@"name self %@", self.name); } int main(int argc, const char * argv[]) { @autoreleasepool { class_addMethod([Human class], @selector(methodInRuntime), (IMP)methodInRuntime, "@@:"); Human *human = [[Human alloc] init]; human.name = @"ajjnix"; [human performSelector:@selector(methodInRuntime)]; } return 0; }
      
      





種類







最小限の制限、オブジェクト、セレクターをメソッドに渡す必要があります。このメソッドはselfと_cmdを象徴します( これは何ですか?



したがって、メソッドはオブジェクトとセレクターが渡される関数です。 これには実際的な意味がありますか?



これで、selfが変数であり、ブロックがselfを通常の外部変数としてキャプチャすることがわかりました(別の記事のトピック)。 また、同じトークンにより、selfと呼ばれるブロック内に変数を作成できます(マクロを使用するときに、selfを内部で使用する場合に行う必要がある場合があります)。



論理的な質問が発生します:「呼び出されたときにselfと_cmdを偽造できますか?」はい、できます。 上記のコードでわかるように、IMPは単純な関数であり、必要な任意の型に変換して、必要なものをすべて渡すことができます。



ランタイムで他に何ができますか? プライベートivarの使用、クラス、プロパティ、メソッドの追加、削除、すべてのクラスメソッドの取得など。 しかし、この記事ではランタイムの使用方法についてではなく、メソッドについて説明しています。



私たちはスウィズリングの概念に到達しますが、これは代替です。



スウィズリング
Human.h
 #import <Foundation/Foundation.h> @interface Human : NSObject - (NSString *)fname; - (NSString *)lname; - (void)swizzling; @end
      
      





Human.m
 #import "Human.h" #import <objc/runtime.h> @implementation Human - (NSString *)fname { return @"first name"; } - (NSString *)lname { return @"last name"; } - (void)swizzling { Method mfname = class_getInstanceMethod([self class], @selector(fname)); Method mlname = class_getInstanceMethod([self class], @selector(lname)); method_exchangeImplementations(mfname, mlname); } @end
      
      





main.m
 #import <Foundation/Foundation.h> #import "Human.h" int main(int argc, const char * argv[]) { @autoreleasepool { Human *human = [[Human alloc] init]; NSLog(@"my fname:%@", [human fname]); NSLog(@"my lname:%@", [human lname]); [human swizzling]; NSLog(@"my fname:%@", [human fname]); NSLog(@"my lname:%@", [human lname]); } return 0; }
      
      





おわりに
2015-11-15 19:53:28.307 TestingRuntime [72180:6349571]私のfname:名

2015-11-15 19:53:28.309 TestingRuntime [72180:6349571] lname:姓

2015-11-15 19:53:28.309 TestingRuntime [72180:6349571] my fname:last name

2015-11-15 19:53:28.309 TestingRuntime [72180:6349571] lname:名







また、テーブルにメソッドを追加して、必要なアクションを実行することもできます。 メソッドが何であるかを理解するとき、今私たちにとって魔法はありません。



しかし、ISAはどうですか? 結局のところ、すべてがここに行きます、オブジェクトのクラスを変更できますか? できます。



オブジェクトのアドレスを変更せずに実行時にクラスを変更する
Human.h
 #import <Foundation/Foundation.h> @interface Human : NSObject - (void)humanMethod; @end @interface Human1 : Human - (void)humanMethod1; @end @interface NoHuman : NSObject @property (copy, nonatomic) NSString *foo; - (void)noHumanMethod; @end
      
      





Human.m
 #import "Human.h" @implementation Human - (void)humanMethod { NSLog(@"humanMethod"); } @end @implementation Human1 - (void)humanMethod1 { NSLog(@"humanMethod1"); } @end @implementation NoHuman - (void)noHumanMethod { NSLog(@"noHumanMethod with property foo:%@", self.foo); } @end
      
      





main.m
 #import <Foundation/Foundation.h> #import "Human.h" #import <objc/runtime.h> int main(int argc, const char * argv[]) { @autoreleasepool { Human *human = [[Human alloc] init]; NSLog(@"ptr %p, isa = %@", human, NSStringFromClass([human class])); [human performSelector:@selector(humanMethod)]; object_setClass(human, [Human1 class]); NSLog(@"ptr %p, isa = %@", human, NSStringFromClass([human class])); [human performSelector:@selector(humanMethod)]; [human performSelector:@selector(humanMethod1)]; object_setClass(human, [NoHuman class]); NSLog(@"ptr %p, isa = %@", human, NSStringFromClass([human class])); NoHuman *noHuman = (NoHuman *)human; noHuman.foo = @"fo o"; [noHuman noHumanMethod]; } return 0; }
      
      





おわりに
2015-11-15 22:40:45.960 TestingRuntime [72469:7427905] ptr 0x10020b8b0、isa = Human

2015-11-15 22:40:45.961 TestingRuntime [72469:7427905] humanMethod

2015-11-15 22:40:45.962 TestingRuntime [72469:7427905] ptr 0x10020b8b0、isa = Human1

2015-11-15 22:40:45.962 TestingRuntime [72469:7427905] humanMethod

2015-11-15 22:40:45.962 TestingRuntime [72469:7427905] humanMethod1

2015-11-15 22:40:45.962 TestingRuntime [72469:7427905] ptr 0x10020b8b0、isa = NoHuman

2015-11-15 22:40:45.962 TestingRuntime [72469:7427905]プロパティfooのnoHumanMethod:foo







記事の冒頭で、クラスにはメソッドがあり、Objective-Cではオブジェクトであることを忘れるように依頼しました。 だから、キャンセルします。



実際、クラスはメタクラスのオブジェクトです。 独自のメソッド、独自のディスパッチテーブル、ISAがあります。 また、彼にはエントリポイント(+初期化子)があります。



以前と同じ方法で、クラスにメソッドを追加できます。 1つのポイントを除いて、メタクラスを取得する必要があります。



メタクラスを使用したさまざまなディスパッチテーブルのデモンストレーション
Human.h
 #import <Foundation/Foundation.h> @interface Human : NSObject + (void)humanClassMethod; - (void)humanMethod; @end
      
      





Human.m
 #import "Human.h" @implementation Human - (void)humanMethod { NSLog(@"humanMethod"); } + (void)humanClassMethod { NSLog(@"humanClassMethod"); } @end
      
      





main.m
 #import <Foundation/Foundation.h> #import "Human.h" #import <objc/runtime.h> void printAllMethodClass(Class clazz) { unsigned int count; Method *methods = class_copyMethodList(clazz, &count); for (int i = 0; i < count; i++) { Method method = methods[i]; SEL sel = method_getName(method); NSLog(@"%@", NSStringFromSelector(sel)); } } int main(int argc, const char * argv[]) { @autoreleasepool { printAllMethodClass([Human class]); NSLog(@"\n\n\n"); Class metaClass = object_getClass([Human class]); printAllMethodClass(metaClass); } return 0; }
      
      





おわりに
2015-11-15 20:20:33.128 TestingRuntime [72303:6360119] humanMethod

2015-11-15 20:20:33.129 TestingRuntime [72303:6360119]



2015-11-15 20:20:33.129 TestingRuntime [72303:6360119] humanClassMethod







マテリアルを統合し、クラスインスタンスメソッドのアドレスを取得し、目的の型にキャストする通常の関数として呼び出します。



関数としてのメソッド呼び出し
Human.h
 #import <Foundation/Foundation.h> @interface Human : NSObject @property (copy, nonatomic) NSString *name; - (NSString *)fooMethodWithArg1:(NSString *)arg1 arg2:(NSString *)arg2; @end
      
      





Human.m
 #import "Human.h" @implementation Human - (NSString *)fooMethodWithArg1:(NSString *)arg1 arg2:(NSString *)arg2 { return [NSString stringWithFormat:@"\nname:%@ \n_cmd:%@ \narg1:%@ \narg2:%@", self.name, NSStringFromSelector(_cmd), arg1, arg2]; } @end
      
      





main.m
 #import <Foundation/Foundation.h> #import "Human.h" #import <objc/runtime.h> int main(int argc, const char * argv[]) { @autoreleasepool { SEL sel = @selector(fooMethodWithArg1:arg2:); Method method = class_getInstanceMethod([Human class], sel); IMP imp = method_getImplementation(method); #define funcWithArg1AndArg2(imp) ((NSString * (*)())imp) Human *human = [[Human alloc] init]; human.name = @"ajjnix"; NSString *result = funcWithArg1AndArg2(imp)(human, sel, @"Hello ", @"world"); NSLog(@"%@", result); NSLog(@"\n\n\n"); NSString *result1 = funcWithArg1AndArg2(imp)(human, @selector(fake_selector), @"Hello ", @"world"); NSLog(@"%@", result1); #undef funcWithArg1AndArg2 } return 0; }
      
      





おわりに
2015-11-17 12:28:29.821 TestingRuntime [73269:8918838]

名前:ajjnix

_cmd:fooMethodWithArg1:arg2:

arg1:こんにちは

arg2:ワールド

2015-11-17 12:28:29.823 TestingRuntime [73269:8918838]



2015-11-17 12:28:29.823 TestingRuntime [73269:8918838]

名前:ajjnix

_cmd:fake_selector

arg1:こんにちは

arg2:ワールド







この記事は小さくはありませんでしたが、Objective-Cで実際にメソッドが何であるかを説明できたと思います。

PSと結論- ドキュメントへのリンク



All Articles