深入探究SEL,Method,IMP

SEL

  • SEL方法選擇器,表示一個selector的指針
  • 無論什麼類裏,只要方法名相同,SEL就相同。項目裏的所有SEL都保存在一個NSSet集合裏(NSSet集合裏的元素不能重複),所以查找對應方法,只要找到對應的SEL就可以了。
  • SEL實際是根據方法名hash化了的字符串
SEL sel_registerName(const char *str)//向runtime system註冊一個方法名。如果方法名已經註冊,則放回已經註冊的SEL
SEL sel_getUid(const char *str)//同上
@selector(<#selector#>)//oc編譯器提供的
SEL NSSelectorFromString(NSString *aSelectorName)//OC字符串轉化
SEL method_getName ( Method m );//根據Method結構體獲取
等等

SEL的操作函數

// 比較兩個選擇器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
//判斷方法名是否映射到某個函數實現上
BOOL sel_isMapped(SEL sel);
出現個BUG:

既然SEL是方法的唯一標識,那不同的類調用名字相同的方法怎麼辦呢?

那就讓下一個重要任務出場吧。

IMP

定義:函數指針,指向方法實現的首地址。
代碼定義如下:

typedef id (*IMP)(id, SEL, ...); 

其參數包含id,SEL,後面試實際的參數列表。
那麼,XX調用了XXX方法,其參數爲XX都確定下來了。

IMP的高級作用

既然上述元素都確定下來了,那麼就可以直接繞過Runtime的消息傳遞機制,直接執行IMP指向的函數了。省去了一些列的查找,直接向對象發送消息,效率會高一些。

IMP imp_implementationWithBlock(id block)//根據代碼塊獲取IMP,其實就是代碼塊與IMP關聯
IMP method_getImplementation(Method m) //根據Method獲取IMP
[[objc Class] instanceMethodForSelector:SEL]//根據OC方式獲取IMP

當我們獲取一個方法的IMP時候可以直接調用IMP

IMP imp = method_getImplementation(Method m);
id objc = imp(id,SEL,argument);//objc用來保存方法的返回值,id表示調用這個方法的對象,SEL是Method的選擇器,argument是方法的參數。

Method

Method定義如下:它主要是用語描述類裏面的方法

typedef struct objc_method *Method;

objc_method結構體定義如下

struct objc_method {
    SEL method_name                              OBJC2_UNAVAILABLE;//方法名
    char *method_types                           OBJC2_UNAVAILABLE;//參數返回值字符串描述
    IMP method_imp                               OBJC2_UNAVAILABLE;//方法的實現
}    

從上述代碼可以看出,Method是一個結構體,包含了SEL和IMP成員變量。
實際上,相當於在SEL和IMP之間做了一個映射,有了Method,SEL就可以找到對應的IMP,從而調用方法。

Method操作函數如下:
方法操作主要有以下函數:
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );

// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );

// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );

// 獲取所有方法的數組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );

// 替代方法的實現
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );

// 返回方法的具體實現
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

// 類實例是否響應指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );

此處具體實現比較簡單,可以通過手動操作IMP來完成方法的調用。

實例代碼:

#import "OC_IMP.h"


typedef void (^CustomBlock)(NSString *name);

@interface OC_IMP ()

@property(nonatomic, weak) CustomBlock block;

@end

@implementation OC_IMP

- (void)testIMP {
    [self addMethodByIMP];
}

//定義一個block
//手動添加方法
- (void)addMethodByIMP {
    CustomBlock block = ^(NSString *name){
        NSLog(@"執行block");
    };
    
    IMP impBlock = imp_implementationWithBlock(block);
    Method m = class_getInstanceMethod(self.class, @selector(testIMP));
    method_setImplementation(m, impBlock);
    const char * types = method_getTypeEncoding(m); //因爲方法類型相同(都是無參數無返回值類型,所以方法類型相同,如果知道的話,可以直接制定type爲v16@0:8)
    sel_registerName("newSel");             //註冊newSel
    BOOL isAdded = class_addMethod([self class], @selector(newSel), impBlock, types);
    if (isAdded == YES) {
        NSLog(@"添加成功");
        [self performSelector:@selector(newSel)];
    }
}

@end
    OC_IMP *oc_imp = [[OC_IMP alloc] init];
    [oc_imp testIMP];

調用執行結果

2017-07-14 20:14:26.359 OCDeepLearning[5654:530015] 添加成功
2017-07-14 20:14:26.359 OCDeepLearning[5654:530015] 執行block

補充:

  1. 在swift裏可以使用#selector(XXX)來獲取對應的SEL,但這並非指swift的方法調用是通過selector來實現的,能調用僅僅是因爲swift和OC的混編;
  2. 每個方法名有對應的唯一seletor,其SEL相同,但對應的IMP函數指針不同。

Github源碼地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章