iOS动态调用类方法(不带参数)
Class class = NSClassFromString(@"MyClass");
if (class) {
id obj = [[class alloc] init];
SEL sel = NSSelectorFromString(@"myMethod");
//检查是否有"myMethod"这个名称的方法
if ([obj respondsToSelector:sel]) {
[obj performSelector:sel];
}
}
iOS动态调用类方法(带参数)
Class class = NSClassFromString(@"MyClass");
if (class) {
id obj = [[class alloc] init];
SEL sel = NSSelectorFromString(@"myMethod:"); //方法带有参数需要加冒号:
//检查是否有"myMethod"这个名称的方法
if ([obj respondsToSelector:sel]) {
[obj performSelector:sel withObject:param]; //方法有多个参数时使用多个withObject传递参数
}
}
iOS动态调用类方法(有返回值)
Class class = NSClassFromString(@"MyClass");
if (class) {
id obj = [[class alloc] init];
SEL sel = NSSelectorFromString(@"myMethod");
//检查是否有"myMethod"这个名称的方法
if ([obj respondsToSelector:sel]) {
id = [obj performSelector:sel];
//然后将id转换为类方法实际返回的数据类型。
//假设为NSString *类型
NSString *str = (NSString *)id;
NSLog(@"str: %@", str);
}
}
优点
- 弱化连接,因此并不会把没有的Framework也link到程序中。
- 不需要使用import,因为类是动态加载的,只要存在就可以加载。因此如果你的toolchain中没有某个类的头文件定义,而你确信这个类是可以用的,那么就可以用这种方法。
问题
采用这种方式,在Xcode中会报以下警告信息:
"performSelector may cause a leak because its selector is unknown"(因为performSelector的选择器未知可能会引起泄漏)
原因
在ARC下调一个方法,runtime需要知道对于返回值该怎么办。返回值可能有各种类型:void
,int
,char
,NSString *
,id
等等。ARC 一般是根据返回值的头文件来决定该怎么办的,一共有以下4种情况:
- 直接忽略(如果是基本类型比如
void
,int
这样的)。 - 把返回值先
retain
,等到用不到的时候再release
(最常见的情况)。 - 不
retain
,等到用不到的时候直接release
(用于init
、copy
这一类的方法,或者标注ns_returns_retained
的方法)。 - 什么也不做,默认返回值在返回前后是始终有效的(一直到最近的
release pool
结束为止,用于标注ns_returns_autoreleased
的方法)。
而调performSelector:
的时候,系统会默认返回值并不是基本类型,但也不会retain
、release
,也就是默认采取第 4 种做法。所以如果那个方法本来应该属于前3种情况,都有可能会造成内存泄漏。
对于返回void
或者基本类型的方法,就目前而言你可以忽略这个warning,但这样做不一定安全。我看过Clang在处理返回值这块的几次迭代演进。一旦开着ARC,编译器会觉得从performSelector:
返回的对象没理由不能retain
,不能release
。在编译器眼里,它就是个对象。所以,如果返回值是基本类型或者void
,编译器还是存在会retain
、release
它的可能,然后直接导致crash。
解决办法
1. 使用宏忽略警告(不推荐)
#define SuppressPerformSelectorLeakWarning(Stuff) \
do {\
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop")
\} while (0)
在产生警告也就是performSelector的地方用使用该宏,如:
SuppressPerformSelectorLeakWarning([obj performSelector:sel]);
1. 使用函数指针方式(推荐)
Class class = NSClassFromString(@"MyClass");
if (class) {
id obj = [[class alloc] init];
SEL sel = NSSelectorFromString(@"myMethod");
IMP imp = [obj methodForSelector:sel];
void (*function)(id, SEL) = (void *)imp;
function(obj, sel);
}
这一堆代码在做的事情其实是向obj请求那个方法对应的C函数指针。所有的NSObject
都能响应methodForSelector:
这个方法,不过也可以用Objective-C runtime里的class_getMethodImplementation
(只在protocol的情况下有用,id
这样的)。这种函数指针叫做IMP
,就是typedef
过的函数指针(id (*IMP)(id, SEL, ...)
)。它跟方法签名(signature)比较像,虽然可能不是完全一样。
得到IMP
之后,还需要进行转换,转换后的函数指针包含ARC所需的那些细节(比如每个OC方法调用都有的两个隐藏参数self
和_cmd
)。这就是代码第6行干的事(右边的那个(void *)
只是告诉编译器,不用报类型强转的warning)。
最后一步,调用函数指针。
如果selector接收参数,或者有返回值,代码就需要改改:
Class class = NSClassFromString(@"MyClass");
if (class) {
id obj = [[class alloc] init];
SEL sel = NSSelectorFromString(@"myMethod:");
IMP imp = [obj methodForSelector:sel];
NSString *(*function)(id, SEL, NSString *) = (void *)imp;
NSString *result = function(obj, sel);
}
部分引用内容出处如下:
链接:https://www.jianshu.com/p/a9569a9c9a63
链接:https://www.jianshu.com/p/6517ab655be7