Runtimeメッセージ配信関数の使用(4)

5062 ワード

前節のメッセージ配信では、NSMethodSignatureとNSInvocationが使用されていました.この節では、この2つが何に使われているのか、どのように使われているのかを見てみましょう.
NSMethodSignature
名前からして、これが一つの方法のサインだと知っています.では、何が方法署名なのか、署名という2つの字はどのように理解されているのでしょうか.署名とは,実際にはメソッドのパラメータと戻り値のタイプを符号化することである.では、タイプのコードはどうですか.
//@encode()         。
char* intType = @encode(int);  //intType    i。
char* idType = @encode(id);    //idType   @。
char* doubleType = @encode(double);//doubleType     d

その他のタイプは、Type Encodeを参照してください.
では、メソッドの署名はどのように生成されるのでしょうか.ここでは実際の例を挙げます.
- (void)testSignature:(NSString *)strPara withTwoPara:(NSArray *)arrStr
{
    NSMethodSignature *signature = [self methodSignatureForSelector:_cmd]; //_cmd       , @selector(testSignature:withTwoPara:)
    NSMethodSignature *manualSignature = [NSMethodSignature signatureWithObjCTypes:"v@:@@"];
    if ([signature isEqual:manualSignature]){
        NSLog(@"  Signature    。");   //    
    }
}

文字列「v@:@@」は、メソッドの戻り値とパラメータを表します.戻り値voidの符号化vは先頭に置かれ、ゼロ番目のパラメータであると仮定する.1番目と2番目のパラメータはselfと_です.cmdの符号化.OC法の場合,この2つのパラメータは固定されている.3つからが本当のパラメータです.パラメータの順序は左から右に並べられています.次にNSMethodSignatureの定義を見てみましょう.
@interface NSMethodSignature : NSObject {
@private
    void *_private;
    void *_reserved[5];
    unsigned long _flags;
}

+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;

@property (readonly) NSUInteger numberOfArguments;//    
- (const char *)getArgumentTypeAtIndex:(NSUInteger)idx NS_RETURNS_INNER_POINTER;//         。

@property (readonly) NSUInteger frameLength;

- (BOOL)isOneway;

@property (readonly) const char *methodReturnType NS_RETURNS_INNER_POINTER;//     
@property (readonly) NSUInteger methodReturnLength;//     

@end

実際に使用されているのはsignatureWithObjCTypeを使用して署名を生成するだけです.または、署名によってパラメータのタイプまたは戻り値のタイプを取得します.したがって、NSMethodSignatureは、メソッドに関連するパラメータ情報へのアクセスにのみ使用されるモデルと見なすことができる.
NSInvocation
このクラスは主にメソッド呼び出し関連を担当します.ocのメソッド呼び出しは3つの形式に分けられる.
//       
- (NSString *)methodInvoke:(NSString *)paraOne withName:(NSString *)paraTwo
{
    NSLog(@"paraOne:%@ paraTwo:%@ ",paraOne,paraTwo);
    return [NSString stringWithFormat:@"%@,%@",paraOne,paraTwo];
}


- (void)testInvocation
{
    //1.    
    [self methodInvoke:@"   " withName:@"   " ];
    //2.  performSelector:      。
    [self performSelector:@selector(methodInvoke:withName:) withObject:@"   " withObject:@"   " ];//    
    //3.  NSInvocation  
    NSMethodSignature *signature = [self methodSignatureForSelector:@selector(methodInvoke:withName:)];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:self];
    [invocation setSelector:@selector(methodInvoke:withName:)];
    NSString *paraOne = @"   ";
    NSString *paraTwo = @"   ";
    
    [invocation setArgument:&paraOne atIndex:2];//  ,  2  。        
    [invocation setArgument:&paraTwo atIndex:3];
    [invocation invoke];
    void *result;
    [invocation getReturnValue:&result];//    result     
    NSString *retStr = (__bridge NSString *)result;  //   alloc      ,          。
    NSLog(@"    :%@",retStr);
}

3つの呼び出し方法は同じです.しかし、効率にはいくつかの違いがあり、方法は最も速く、方法は2番目、方法は3番目に遅い.方式3 NSInvocationは使うのが面倒で、効率が最も低い以上、何で使うのですか.実際にNSInvocationは他の言語とのインタラクションで使用できます.たとえばJSPochではJavaScriptでOCを呼び出す場合にNSInvocationが使用されます.NSInvocationは、実行時に特定のメソッドを呼び出すことを決定する能力を提供します.performSelectorも同様の能力を提供していますが、パラメータの数を制限しています.最大2つのパラメータしか伝達できないので,汎用的なメソッド呼び出しを行うためにNSInvocationのみが利用できる.
NSInvocationの定義を見てみましょう.
NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available")
@interface NSInvocation : NSObject {
@private
    void *_frame;
    void *_retdata;
    id _signature;
    id _container;
    uint8_t _retainedArgs;
    uint8_t _reserved[15];
}

+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;

@property (readonly, retain) NSMethodSignature *methodSignature;

- (void)retainArguments;
@property (readonly) BOOL argumentsRetained;

@property (nullable, assign) id target;
@property SEL selector;

- (void)getReturnValue:(void *)retLoc;
- (void)setReturnValue:(void *)retLoc;

- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;

- (void)invoke;
- (void)invokeWithTarget:(id)target;

@end

定義からも分かるように,その提供方法の大部分も前の例で用いられている.その主な能力は,任意のOCを動的に呼び出す方法である.特に(void)setReturnValue:(void*)retLocというメソッドは、メソッド呼び出しの戻り値の位置にretLocを設定できることに注意してください.すなわち、[invocation invoke]を実行しなくても、戻り値も取得できます.したがって,任意の従来の方法を新しい方法に置き換えることができる.この点と前のメッセージ配信プロセスを利用すれば,熱修復が可能になる.
//                     。        4 :

//1.hook     。
//2.         。
//3.         (   JS              )  ,     。
//4.                 。
- (void)setReturnValue:(void *)retLoc;

ステップ4では,結果を元のメソッドの戻り値アドレスに記入し,NSInvocationを通過しなければ,自分でアセンブリを書くしか実現できない.
参照先:
  • Objective-c Runtime Programming Guide