iOS面试题+基础知识

iOS动态调用类方法

2018-12-28  本文已影响270人  _人非草木_

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);
    }
}

优点

  1. 弱化连接,因此并不会把没有的Framework也link到程序中。
  2. 不需要使用import,因为类是动态加载的,只要存在就可以加载。因此如果你的toolchain中没有某个类的头文件定义,而你确信这个类是可以用的,那么就可以用这种方法。

问题

采用这种方式,在Xcode中会报以下警告信息:

"performSelector may cause a leak because its selector is unknown"(因为performSelector的选择器未知可能会引起泄漏)

原因

在ARC下调一个方法,runtime需要知道对于返回值该怎么办。返回值可能有各种类型:voidintcharNSString *id等等。ARC 一般是根据返回值的头文件来决定该怎么办的,一共有以下4种情况:

  1. 直接忽略(如果是基本类型比如 voidint这样的)。
  2. 把返回值先retain,等到用不到的时候再release(最常见的情况)。
  3. retain,等到用不到的时候直接release(用于 initcopy 这一类的方法,或者标注ns_returns_retained的方法)。
  4. 什么也不做,默认返回值在返回前后是始终有效的(一直到最近的release pool结束为止,用于标注ns_returns_autoreleased的方法)。

而调performSelector:的时候,系统会默认返回值并不是基本类型,但也不会retainrelease,也就是默认采取第 4 种做法。所以如果那个方法本来应该属于前3种情况,都有可能会造成内存泄漏。

对于返回void或者基本类型的方法,就目前而言你可以忽略这个warning,但这样做不一定安全。我看过Clang在处理返回值这块的几次迭代演进。一旦开着ARC,编译器会觉得从performSelector:返回的对象没理由不能retain,不能release。在编译器眼里,它就是个对象。所以,如果返回值是基本类型或者void,编译器还是存在会retainrelease它的可能,然后直接导致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<SomeProto>这样的)。这种函数指针叫做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

上一篇下一篇

猜你喜欢

热点阅读