OCのメッセージ転送

4889 ワード

OCは動的言語であり、従来の静的言語、例えばC言語は、コンパイル時に呼び出す方法が決定されていることはよく知られています.OCのような動的言語ではruntimeメカニズムがあるため、コンパイル段階で呼び出す具体的な方法が実現されず、実行時に呼び出されることがあり、あるtargetにactionのSELを指定したcaseもあるが、このSELに対応する方法は具体的に実現されず、コンパイル時には非常にNo Problemである.しかし、実行中は対応する実装が見つからないためcrashします.このような状況も私たちがよく出会うことです.
では、このcaseをどう処理すればいいのでしょうか.
runtimeのいくつかの原理とOCオブジェクトのメモリレイアウトを研究して、私達はこのようにいくつかの結論を出すことができます:1、OCオブジェクトの呼び出し方法はセレクタselector(つまりSEL)を使って、SELを通じて具体的な実現IMP 2、SELを探し当てて具体的な方法の1つのインデックスで、IMPは具体的な実現の関数のポインタで、両者は協力して迅速に具体的な実現の方法を探し当てて3、各OCのクラスは実際には構造体であり、objc/runtime.hのobjc_を表示します.class構造体の定義は以下の通りである
struct objc_class
{
    struct objc_class* isa;//isa      
    struct objc_class* super_class;//    ,         
    const char* name;//  
    long version;//      ,   0(       )
    long info;//   ,            
    long instance_size;//          
    struct objc_ivar_list* ivars;//      
    struct objc_method_list** methodLists;//    
    struct objc_cache* cache;//    
    struct objc_protocol_list* protocols;//      
};
4、オブジェクト検索方法の実現過程:
1   cache      ,    ,      
2  methodLists      ,    ,      
3       ,  super_class methodLists  ,    ,      ,           crash 。
しかし、crashの前には、crashを防ぐために3つのメッセージ転送の救済方法があります.もちろん「救済策」を取らなければ、直接crashします.この3つの救済方法は簡単に言えば、
1             
2           (      ,           ,            )
3   NSInvocation      
この3つの救済方法に関連するAPIは、次のとおりです.
//      
+(BOOL) resolveInstanceMethod:(SEL)selector
+(BOOL)resolveClassMethod:(SEL)sel

//     
-(id)forwardingTargetForSelector:(SEL)aSelector

//NSInvocation      
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
-(void)forwardInvocation:(NSInvocation *)anInvocation
そしてruntimeは、上記の順序で呼び出され、救済があるかどうかを確認します.
私たちの先人はcrashのシーンを作ります:Personクラスを創立して、Person.h
#import@interface Person : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,strong)NSNumber *age;
@property(nonatomic,weak)id delegate;
@end
Person.m #import "Person.h"@implementation Person @end
コントローラにPersonオブジェクトを作成し、
Person *pson = [[Person alloc] init];
pson.name = @"wc";
pson.age = @99;
 [pson performSelector:@selector(eat) withObject:nil];
明らかにここではeatメソッドを作成する時間がなく、プログラムはeatの具体的な実装を見つけずに直接クラッシュします.
救済措置1:
+(BOOL)resolveInstanceMethod:(SEL)selectorにeatメソッドを動的に追加します.
+(BOOL) resolveInstanceMethod:(SEL)selector
{
       NSString *selectorStr = NSStringFromSelector(selector);
       if ([selectorStr isEqualToString:@"eat"])
       {
               class_addMethod(self, selector, (IMP)eat, "v@:");
               return YES;
        }
        return [self resolveClassMethod:selector];
}
具体的な実現:
void eat()
{
   NSLog(@"person eat");
}
救済策2:代替受信者を使用する場合、コールバックはこの方法-(id)f o r wardingTargetForSelector:(SEL)aSelectorを使用します.ここでpesonにエージェント属性を追加し、personが実現していない方法をエージェントに渡します.
#import@interface Person : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,strong)NSNumber *age;
//  , person       ,      
@property(nonatomic,weak)id delegate;
@end

// pson       
Person *pson = [[Person alloc] init];
pson.name = @"wc";
pson.age = @99;
pson.delegate = self;
[pson performSelector:@selector(eat) withObject:nil];

//    eat  

-(void)eat
{
   NSLog(@"viewcontroller eat");
}
//ここでエージェントに戻り、エージェントを完了させる
-(id)forwardingTargetForSelector:(SEL)aSelector
{
   return  _delegate;
}
もちろん、ここではエージェントを使用する必要はありません.このコンテキストでアクセスできる適切なオブジェクトであればいいです.
ここまで救済措置が取られていない場合は、完全なメッセージ転送メカニズムを起動するしかありません.救済措置3:
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
-(void)forwardInvocation:(NSInvocation *)anInvocation
デフォルトの-(void)forwardInvocation:(NSInvocation*)anInvocationの実装は呼び出し-(void)doesNotRecognizeSelector:(SEL)aSelectorメソッドであり、このメソッドでは例外が投げ出され、プログラムがクラッシュする.
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
        NSMethodSignature *methodSignature = nil;
        NSString *selectorStr = NSStringFromSelector(aSelector);
      if ([selectorStr isEqualToString:@"eat"])
      {
                methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
              //    
               return methodSignature;
       }
      return [self methodSignatureForSelector:aSelector];
}

-(void)forwardInvocation:(NSInvocation *)anInvocation
{
     NSLog(@"forwordInvocation");
     SEL selector = anInvocation.selector;
    if ([_delegate respondsToSelector:selector])
    {
           [anInvocation invokeWithTarget:_delegate];
    }
   else
  {
           return[self forwardInvocation:anInvocation];
   }
}
ここのメッセージ転送は、完全に実現して私のエージェントに任せました.
参照リンク:http://blog.csdn.net/liangliang103377/article/details/39007683