有時在代碼中會有須要調用私有方法的場景,如不想import太多頭文件;想組件設計一些解耦的模塊;查看別人模塊中未暴露的代碼進行分析等。html
在 ios 中調用私有方法有不少種方式,主要是經過Runtime去實現。下面本身也測試一下。ios
新建一個Person類,Person.h中不寫代碼,Person.m中以下:app
#import "Person.h" @implementation Person - (void)eat { NSLog(@"xxx eat===="); } - (void)eat:(NSString *)str str2:(NSString *)str2 str3:(NSString *)str3 { NSLog(@"xxx eat====%@==%@==%@", str, str2, str3); } @end
【找到該類methodLists裏的方法】ide
要想調用私有方法,首先要知道類有什麼哪些方法。能夠經過以下代碼獲得方法的一些信息:(無論私有仍是公有,只要在該類的methodLists裏)函數
// 獲取實例方法 - (void)getMethods { int outCount = 0; Person *p = [Person new]; Method *methods = class_copyMethodList([p class], &outCount); for (int i = 0; i < outCount; i ++) { NSLog(@"=============%d", i); // 獲取方法名 Method method = methods[i]; SEL methodName = method_getName(method); NSLog(@"方法名= %@", NSStringFromSelector(methodName)); // 獲取參數 char argInfo[512] = {}; unsigned int argCount = method_getNumberOfArguments(method); for (int j = 0; j < argCount; j ++) { // 參數類型 method_getArgumentType(method, j, argInfo, 512); NSLog(@"參數類型= %s", argInfo); memset(argInfo, '\0', strlen(argInfo)); } // 獲取方法返回值類型 char retType[512] = {}; method_getReturnType(method, retType, 512); NSLog(@"返回類型值類型= %s", retType); } free(methods); }
上面代碼使用runtime獲取一些方法的信息:方法名,參數對應的類型,返回值類型。上面這個方法打印結果以下:學習
=============0 方法名= eat 參數類型= @ 參數類型= : 返回類型值類型= v =============1 方法名= eat:str2:str3: 參數類型= @ 參數類型= : 參數類型= @ 參數類型= @ 參數類型= @ 返回類型值類型= v
打印的類型是一些符號,不知道這是什麼鬼,但其實這是蘋果的類型編碼。它對照的含義以下:測試
v A void —— (爲空) @ An object (whether statically typed or typed id) —— (id類型) : A method selector (SEL) —— (方法名)
上面打印的方法信息中 eat 方法也有兩個參數,實際每一個方法都有兩個隱藏參數。(_cmd是當前方法編號)ui
【調用私有方法】編碼
調用私有方法有多種方式,但其實最終都大同小異。以下:spa
1. 使用 performSelector 下面2和3行結果同樣。這樣比使用對象直接調用好處是編譯器不會報錯,也不用方法暴露頭文件。
1 Person *p = [Person new]; 2 [p performSelector:@selector(eat)]; // log: xxx eat====
3 [p performSelector:NSSelectorFromString(@"eat")]; // log: xxx eat====
2. 使用 objc_msgSend ,對比上面 objc_msgSend 好處是傳遞多個參數時更爲方便。objc_msgSend深刻學習
Person *p = [Person new]; // 須要引用 Person.h 頭文件 objc_msgSend(p, @selector(eat:str2:str3:), @"1", @"2", @"3"); // log: eat====1==2==3
3. 利用函數實現IMP,IMP類型結構與objc_msgSend底層是同一類型,與2中實現無差異
Person *p = [Person new]; IMP imp = [p methodForSelector:@selector(eat)]; void (* tempFunc)(id target, SEL) = (void *)imp; tempFunc(p, @selector(eat)); // log: xxx eat====
4. 使用類對象發送消息,上面3種方式都須要引用 Person.h 頭文件,這裏類對象進行調用能夠解決這個問題。
Class pClassObj = NSClassFromString(@"Person"); objc_msgSend([pClassObj new], @selector(eat));
【類對象】
進入 objc.h 中查看 Class 與 Object 結構定義。每一個 objc_object 下都有一個 isa 指針指向這個對象所屬的 objc_class。
/// An opaque type that represents an Objective-C class. typedef struct objc_class *Class; /// Represents an instance of a class. struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; };
再看一下 objc_class 定義的結構。它裏面只有一個 isa 指針,下面那些屬性在 objc2 中已經不可用了。
struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE; const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;
它裏面的 isa 指向的是 MetaClass (元類)。Class 和 MetaClass :
獲取類對象
[NSObject class];
獲取元類對象
Class class = [NSObject class]; Class metaClass = objc_getClass(class);
【實例對象成員變量】
上面能夠知道,實例對象的方法存放在它對應類對象 (class) 的 methodsList 中,類方法存放它對應的元類 (metaClass) 的 methodsList 中。
一個 new 出來的對象除了它的方法,它還有成員變量。
假若有一個 Person 對象,這個實例對象結構體中存放信息應該是這樣,即成員變量存放在結構體,方法信息經過 isa 去找相應的類對象。
struct Student_IMPL { Class isa; int _age; int _height; };