iOS之runtime詳解api(二)

上一篇咱們講解了runtime裏面關於類和分類的函數,那麼,咱們這一篇就講解下關於Method的那些函數。git

3.objc_method or Method

objc_method或者Method(這兩個實際上是同一個)這個結構體在runtime.h文件裏並無詳細的告訴咱們其中的成員變量,這個在這個階段也不是很重要,後面我將會在分析runtime源碼的時候,再去分析這個結構體,如今咱們只需知道這個是關於函數的結構體。 除了objc_method這個結構體還有一個objc_method_description,結構以下:github

struct objc_method_description {
    SEL _Nullable name;               /**< The name of the method */
    char * _Nullable types;           /**< The types of the method arguments */
};
複製代碼

name在這裏指的是函數名稱,types指的是函數的參數返回值的類型。 咱們就要經過這個method_getDescription這個方法,能夠得到objc_method_description的結構體:api

struct objc_method_description * _Nonnull
method_getDescription(Method _Nonnull m)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼

經過傳入Method返回objc_method_description: 除了這個objc_method_description結構體,咱們還要了解2個概念,SELIMP: SEL:函數的選擇器,通常用函數名進行綁定。 IMP:函數的地址,用過這個能夠執行函數。 關於SEL咱們應該很熟悉,在OC中,有個方法是SELNSString互相轉換,方法是SEL NSSelectorFromString(NSString *aSelectorName)NSString *NSStringFromSelector(SEL aSelector),在runtime裏面也有:bash

const char * _Nonnull
sel_getName(SEL _Nonnull sel)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

SEL _Nonnull
sel_registerName(const char * _Nonnull str)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
複製代碼

這兩個方法就能夠是上面兩個方法的替代方法。如今準備寫個方法如何打印關於Method的信息:函數

-(void)logMethodDescription:(Method)method {
    if (method) {
        struct objc_method_description * description = method_getDescription(method);
        SEL selector = description->name;
        char* types = description->types;
        NSLog(@"selector=%s,type=%s", sel_getName(selector),types);
    } else {
        NSLog(@"Method 爲 null");
    }
}
複製代碼

後面咱們去打印Method相關的信息就能夠調用logMethodDescription方法。 另外,runtime爲了能更好的表示函數的參數和返回值的結構,特別有一張表:測試

#define _C_ID '@'
#define _C_CLASS '#'
#define _C_SEL ':'
#define _C_CHR 'c'
#define _C_UCHR 'C'
#define _C_SHT 's'
#define _C_USHT 'S'
#define _C_INT 'i'
#define _C_UINT 'I'
#define _C_LNG 'l'
#define _C_ULNG 'L'
#define _C_LNG_LNG 'q'
#define _C_ULNG_LNG 'Q'
#define _C_FLT 'f'
#define _C_DBL 'd'
#define _C_BFLD 'b'
#define _C_BOOL 'B'
#define _C_VOID 'v'
#define _C_UNDEF '?'
#define _C_PTR '^'
#define _C_CHARPTR '*'
#define _C_ATOM '%'
#define _C_ARY_B '['
#define _C_ARY_E ']'
#define _C_UNION_B '('
#define _C_UNION_E ')'
#define _C_STRUCT_B '{'
#define _C_STRUCT_E '}'
#define _C_VECTOR '!'
#define _C_CONST 'r'
複製代碼

這些表明什麼等咱們後面用到再說。 咱們知道方法分爲實例方法和類方法,那麼怎麼得到他們呢,就用這兩個函數:ui

Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

Method _Nullable
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
複製代碼

這兩個函數分別表明,獲取某個類的某個方法。spa

在項目裏面,新增一個類Car,定義兩個實例方法-(void)function1-(void)function2,兩個類方法+(void)function1_class+(void)function2_class。可是隻實現-(void)function1+(void)function1_class。咱們看下是否均可以找到。指針

@interface Car : NSObject
-(void)function1;
-(void)function2;
+(void)function1_class;
+(void)function2_class;
@end

@implementation Car

-(void)function1 {
    NSLog(@"----function1----");
}
+(void)function1_class {
    NSLog(@"----function1_class----");
}
@end
複製代碼

而後在ViewController裏面去獲取這四個方法。code

- (void)getMethod {
    //獲取function1
    SEL selector = sel_registerName("function1");
    Method method = class_getInstanceMethod(objc_getClass("Car"), selector);
    [self logMethodDescription:method];
    
    //獲取function1
    SEL selector1 = sel_registerName("function2");
    Method method1 = class_getInstanceMethod(objc_getClass("Car"), selector1);
    [self logMethodDescription:method1];
    
    //獲取function1_class
    SEL selector2 = sel_registerName("function1_class");
    Method method2 = class_getInstanceMethod(objc_getMetaClass("Car"), selector2);
    [self logMethodDescription:method2];
    
    //獲取function2_class
    SEL selector3 = sel_registerName("function2_class");
    Method method3 = class_getInstanceMethod(objc_getMetaClass("Car"), selector3);
    [self logMethodDescription:method3];
}
複製代碼

打印結果(若是隻聲明方法,可是不實現Method就會爲null):

2019-02-25 11:40:07.336465+0800 Runtime-Demo[41836:4488074] selector=function1,type=v16@0:8
2019-02-25 11:40:07.336513+0800 Runtime-Demo[41836:4488074] Method 爲 null
2019-02-25 11:40:07.336523+0800 Runtime-Demo[41836:4488074] selector=function1_class,type=v16@0:8
2019-02-25 11:40:07.336539+0800 Runtime-Demo[41836:4488074] Method 爲 null
複製代碼

首先,咱們看到了沒有實現的方法是找不到的,只有實現的方法才能找到,另外,咱們發現找類方法時候傳入的cls是經過objc_getMetaClass這個方法得到的,爲何?咱們在上一篇中說過了,類方法是存在元類對象裏面,而實例方法是存在類對象裏面。而後咱們再看看打印出來的type是一段奇怪的符號v16@0:8,是否是看起來很奇怪,在這裏我就直接告訴你答案:

  • 這個方法返回值是void(能夠從上面的表格裏面去找)
  • 這個方法第一個參數是id類型(全部方法都默認有2個參數,第一個是target,第二個selector
  • 第二個參數是SEL類型
  • 這個方法全部參數的長度是16個字節
  • 第0個字節開始是第一個參數
  • 第8個字節開始是第二個參數 若是不信?你能夠去嘗試更多的方法,給方法加參數等等。

那咱們繼續,既然能夠得到方法的結構體,那麼也能夠得到方法的地址,這可讓咱們對方法進行調用:

IMP _Nullable
class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) 
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

IMP _Nonnull
method_getImplementation(Method _Nonnull m) 
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

複製代碼

這兩個方法均可以得到IMP,只是傳參不一樣,第一種更直接一點。 咱們依舊以-(void)function1+(void)function1_class爲測試對象,看看可否進行調用

-(void)getMethodImplementation {
    IMP imp1 = class_getMethodImplementation(objc_getClass("Car"), sel_registerName("function1"));
    imp1();
    
    IMP imp2 = class_getMethodImplementation(objc_getMetaClass("Car"), sel_registerName("function1_class"));
    imp2();
    
    Method method1 = class_getInstanceMethod(objc_getClass("Car"), sel_registerName("function1"));
    IMP imp3 = method_getImplementation(method1);
    imp3();
    
    Method method2 = class_getInstanceMethod(objc_getMetaClass("Car"), sel_registerName("function1_class"));
    IMP imp4 = method_getImplementation(method2);
    imp4();
    
}
複製代碼

打印結果:

2019-02-25 13:42:16.757607+0800 Runtime-Demo[43515:4532002] ----function1----
2019-02-25 13:42:16.757647+0800 Runtime-Demo[43515:4532002] ----function1_class----
2019-02-25 13:42:16.757659+0800 Runtime-Demo[43515:4532002] ----function1----
2019-02-25 13:42:16.757667+0800 Runtime-Demo[43515:4532002] ----function1_class----
複製代碼

咱們能夠看到打印的內容確實是實現的內容。咱們不只能夠找到已經存在的IMP,並且還能夠爲一個方法設置新的IMP

IMP _Nonnull
method_setImplementation(Method _Nonnull m, IMP _Nonnull imp)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼

傳入的是你要設置的Method和新的IMP,返回的是舊的IMP; 咱們設置一個功能,讓+(void)function1_class實現-(void)function1方法裏的實現:

-(void)setImplementation {
    Method method = class_getClassMethod(objc_getMetaClass("Car"), sel_registerName("function1_class"));
    IMP imp = class_getMethodImplementation(objc_getClass("Car"), sel_registerName("function1"));
    
    IMP oldIMP = method_setImplementation(method, imp);
    
    oldIMP();
    
    IMP newIMP = method_getImplementation(method);
    
    newIMP();
}
複製代碼

運行結果:

2019-02-25 13:55:52.261447+0800 Runtime-Demo[43745:4536866] ----function1_class----
2019-02-25 13:55:52.261486+0800 Runtime-Demo[43745:4536866] ----function1----
複製代碼

咱們能夠看到舊的IMP是原來的IMP,打印的也是原來的實現,newIMP是設置的新的IMP,打印的是-(void)function1,因此沒問題。

Method _Nonnull * _Nullable
class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount)
複製代碼

這個函數是獲取某個類對象的實例對象列表或者元類對象的類方法列表,outCount是一個列表數量的指針,咱們能夠經過outCount知道列表的數量。 咱們在Car類裏面再新增兩個方法-(void)function3+(void)function3_class,而且實現,實現內容就是打印他們的方法名。 而後,在ViewController去使用class_copyMethodList方法

-(void)copyMethodList {
    unsigned int count1 ;
    Method* instanceMethods = class_copyMethodList(objc_getClass("Car"), &count1);
    
    unsigned int count2 ;
    Method* classMethods = class_copyMethodList(objc_getMetaClass("Car"), &count2);
    NSLog(@"--------------------------");
    for (unsigned int i = 0; i < count1; i++) {
        Method method = instanceMethods[i];
        [self logMethodDescription:method];
    }
    NSLog(@"--------------------------");

    for (unsigned int i = 0; i < count1; i++) {
        Method method = classMethods[i];
        [self logMethodDescription:method];
    }

    free(instanceMethods);
    free(classMethods);
}
複製代碼

打印結果:

2019-02-25 13:31:14.263003+0800 Runtime-Demo[43333:4527940] --------------------------
2019-02-25 13:31:14.263045+0800 Runtime-Demo[43333:4527940] selector=function1,type=v16@0:8
2019-02-25 13:31:14.263057+0800 Runtime-Demo[43333:4527940] selector=function3,type=v16@0:8
2019-02-25 13:31:14.263065+0800 Runtime-Demo[43333:4527940] --------------------------
2019-02-25 13:31:14.263073+0800 Runtime-Demo[43333:4527940] selector=function1_class,type=v16@0:8
2019-02-25 13:31:14.263082+0800 Runtime-Demo[43333:4527940] selector=function3_class,type=v16@0:8
複製代碼

咱們看到了,仍然是隻有實現的方法才能找出來 SEL除了經過Classname得到之外,還能夠經過Method來獲取:

SEL _Nonnull
method_getName(Method _Nonnull m) 
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼

例子以下:

-(void)getSEL {
    Method method = class_getClassMethod(objc_getMetaClass("Car"), sel_registerName("function1_class"));
    SEL sel = method_getName(method);
    NSLog(@"sel = %s",sel_getName(sel));
}
複製代碼

運行結果:

sel = function1_class
複製代碼

能夠經過method來得到SEL。關於SEL還有兩個方法:

BOOL
sel_isEqual(SEL _Nonnull lhs, SEL _Nonnull rhs)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

BOOL
class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼

sel_isEqual是用來兩個SEL是否相等,class_respondsToSelector表示某個類是否響應特定的選擇器。 下面咱們來看一組api,這些都是關於參數或者返回值的函數:

//得到方法的格式
const char * _Nullable
method_getTypeEncoding(Method _Nonnull m) 
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

//得到方法參數的個數
unsigned int
method_getNumberOfArguments(Method _Nonnull m)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

//得到返回類型
char * _Nonnull
method_copyReturnType(Method _Nonnull m)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

//得到index處的參數類型
char * _Nullable
method_copyArgumentType(Method _Nonnull m, unsigned int index)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

//得到返回類型
void
method_getReturnType(Method _Nonnull m, char * _Nonnull dst, size_t dst_len)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

//得到index處的參數類型
void
method_getArgumentType(Method _Nonnull m, unsigned int index,
                       char * _Nullable dst, size_t dst_len)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼

咱們寫個方法測試下這些函數:

-(void)getType {
    Method method = class_getInstanceMethod(objc_getClass("Car"), sel_registerName("function1"));
    const char* type = method_getTypeEncoding(method);
    NSLog(@"type = %s",type);
    
    int count  = method_getNumberOfArguments(method);
    NSLog(@"count = %d",count);

    char* returnChar = method_copyReturnType(method);
    NSLog(@"returnChar = %s",returnChar);
    
    for (int i = 0; i < count; i++) {
        char* argumentType = method_copyArgumentType(method,i);
        NSLog(@"第%d個參數類型爲%s",i,argumentType);
    }
    
    char dst[2] = {};
    method_getReturnType(method,dst,2);
    NSLog(@"returnType = %s",dst);
    
    
    for (int i = 0; i < count; i++) {
        char dst2[2] = {};
        method_getArgumentType(method,i,dst2,2);
        NSLog(@"第%d個參數類型爲%s",i,dst2);
    }
    
}
複製代碼

運行結果:

2019-02-25 14:37:53.641383+0800 Runtime-Demo[44457:4553505] type = v16@0:8
2019-02-25 14:37:53.641424+0800 Runtime-Demo[44457:4553505] count = 2
2019-02-25 14:37:53.641439+0800 Runtime-Demo[44457:4553505] returnChar = v
2019-02-25 14:37:53.641459+0800 Runtime-Demo[44457:4553505] 第0個參數類型爲@
2019-02-25 14:37:53.641471+0800 Runtime-Demo[44457:4553505] 第1個參數類型爲:
2019-02-25 14:37:53.641495+0800 Runtime-Demo[44457:4553505] returnType = v
2019-02-25 14:37:53.641508+0800 Runtime-Demo[44457:4553505] 第0個參數類型爲@
2019-02-25 14:37:53.641519+0800 Runtime-Demo[44457:4553505] 第1個參數類型爲:
複製代碼

返回的這些值也能夠驗證咱們以前說的type的含義。

BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼

這個方法是給一個類增長方法。 咱們爲Car類增長一個-(void)test方法,傳入的imp則是-(void)function1imptypes就是無參無返回值。

-(void)addMethod {
    IMP imp = class_getMethodImplementation(objc_getClass("Car"), sel_registerName("function1"));
    BOOL addSuccess = class_addMethod(objc_getClass("Car"), sel_registerName("test"), imp, "v@:");
    NSLog(@"增長不存在的方法 = %d",addSuccess);
    
    BOOL addSuccess2 = class_addMethod(objc_getClass("Car"), sel_registerName("function1"), imp, "v@:");
    NSLog(@"增長已存在的方法 = %d",addSuccess2);
}
複製代碼

運行結果:

2019-02-25 14:55:29.265128+0800 Runtime-Demo[44747:4559426] 增長不存在的方法 = 1
2019-02-25 14:55:29.265327+0800 Runtime-Demo[44747:4559426] 增長已存在的方法 = 0
複製代碼

這個方法是給一個類增長方法,增長成功,返回YES,增長失敗(例如,已經存在),返回NO

IMP _Nullable
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types) 
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼

這個方法有2種狀況: (一)若是SEL存在,則至關於調用method_setImplementation方法 (二)若是SEL不存在,則至關於調用class_addMethod方法

void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼

這個函數堪稱黑魔法,通常在實際用途中,是和系統方法進行交換。 咱們將function1function3交換一波:

-(void)exchangeImplementations {
    Method method1 = class_getInstanceMethod(objc_getClass("Car"), sel_registerName("function1"));
    Method method2 = class_getInstanceMethod(objc_getClass("Car"), sel_registerName("function3"));
    //交換方法
    method_exchangeImplementations(method1, method2);
    //此時的function1的實現應該是`function3`的實現
    IMP imp1 = method_getImplementation(method1);
    //此時的function3的實現應該是`function1`的實現
    IMP imp2 = method_getImplementation(method2);

    imp1();
    imp2();
}
複製代碼

運行結果:

2019-02-25 15:16:19.507945+0800 Runtime-Demo[45112:4568994] ----function3----
2019-02-25 15:16:19.507982+0800 Runtime-Demo[45112:4568994] ----function1----
複製代碼

確實交換過來了。第二篇就這麼結束了,第三篇我將講解關於屬性和變量的函數。 對了,這個是demo,喜歡的能夠點個星。

相關文章
相關標籤/搜索