有时在代码中会有须要调用私有方法的场景,如不想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; };