在上一篇我們講解了runtime裡面關於類和分類的函式,那麼,我們這一篇就講解下關於Method的那些函式。
3.objc_method or Method
objc_method
或者Method
(這兩個其實是同一個)這個結構體在runtime.h
檔案裡並沒有詳細的告訴我們其中的成員變數,這個在這個階段也不是很重要,後面我將會在分析runtime
原始碼的時候,再去分析這個結構體,現在我們只需知道這個是關於函式的結構體。
除了objc_method
這個結構體還有一個objc_method_description
,結構如下:
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
的結構體:
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個概念,SEL
和IMP
:
SEL
:函式的選擇器,一般用函式名進行繫結。
IMP
:函式的地址,用過這個可以執行函式。
關於SEL
我們應該很熟悉,在OC中,有個方法是SEL
和NSString
互相轉換,方法是SEL NSSelectorFromString(NSString *aSelectorName)
和NSString *NSStringFromSelector(SEL aSelector)
,在runtime
裡面也有:
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'
複製程式碼
這些代表什麼等我們後面用到再說。 我們知道方法分為例項方法和類方法,那麼怎麼獲得他們呢,就用這兩個函式:
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);
複製程式碼
這兩個函式分別代表,獲取某個類的某個方法。
在專案裡面,新增一個類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
裡面去獲取這四個方法。
- (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
除了通過Class
和name
獲得以外,還可以通過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)function1
的imp
,types
就是無參無返回值。
-(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);
複製程式碼
這個函式堪稱黑魔法,一般在實際用途中,是和系統方法進行交換。
我們將function1
和function3
交換一波:
-(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,喜歡的可以點個星。