NSClassFromString 用法简介

简单了解如何使用 NSClassFromString() 函数动态加载类。

NSClassFromString 及相关函数

OC Java 备注
通过名字获取类 NSClassFromString() Class.forName()
通过名字获取方法 NSSelectorFromString() Class.getDeclaredMethod()
判断方法能否调用 [NSObject respondsToSelector]
动态调用方法 [NSInvocation invoke] Method.invoke()

NSClassFromString 介绍

NSClassFromString() 函数的作用是通过名字来获取类 (原文:Obtains a class by name.)。

这个函数接受类的名字(一个字符串)作为参数,返回值是对应的类对象(如果这个名字对应的类未加载,则返回 nil)。如果参数为 nil,则直接返回 nil

OC 的 NSClassFromString() 跟 Java 的 Class.forName() 类似。

OC 代码如下:

1
NSObject *target = [[NSClassFromString(className) alloc] init];

对应的 Java 代码如下:

1
2
3
4
5
6
// 简单起见这里忽略异常处理
Class<?> clazz = Class.forName(className);
Object target = clazz.newInstance();

// Java 9
// Object target = clazz.getDeclaredConstructor().newInstance()

NSSelectorFromString 介绍

NSSelectorFromString() 函数的作用是通过名字来获取 selector (原文:Returns the selector with a given name)。

这个函数接受 selector 的名字(一个字符串)作为参数,返回值是对应的 selector。如果参数为 nil 或者无法转换成 UTF-8 字符串,则直接返回 (SEL)0

OC 的 NSSelectorFromString() 跟 Java 的 Class.getDeclaredMethod() 方法类似。

OC 代码如下:

1
SEL action = NSSelectorFromString(methodName);

对应的 Java 代码如下:

1
Method catMethod = SomeClass.class.getDeclaredMethod(methodName);

注意:

SEL 的定义是 typedef struct objc_selector *SEL;。它的用于定义代表 method selector 的类型 (原文:Defines an opaque type that represents a method selector)。

判断方法是否能调用

[NSObject] - (BOOL)respondsToSelector:(SEL)aSelector; - Returns a Boolean value that indicates whether the receiver implements or inherits a method that can respond to a specified message.

1
2
3
if (![target respondsToSelector:action]){
// ...
};

Java中没有对应的用法。

动态调用方法

[NSInvocation invoke] - Sends the receiver’s message (with arguments) to its target and sets the return value.

OC代码如下:

1
2
3
NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation invoke];

对应的Java代码如下:

1
2
Method action = ...
action.invoke(target);

NSStringClass 使用案例

可以使用 NSStringClass() 动态加载类。如果返回 nil,表示不能加载此类。

如下面代码所示,有不同的方式创建对象:

1
2
3
4
5
// 方式一
id myObj = [[NSClassFromString(@"MyClass") alloc] init];

// 方式二
id myObj2 = [[MyClass alloc] init];

通常来说这两种方式没有太多区别。但是,如果并不存在 MyClass 这个类,那么方式二编译报错。所以如果不确定是否存在 MyClass 这个类的情况下,应当使用方式一来创建对象。

方式一有这样两个好处:

  • “弱”链接
  • 不需要使用 import。即使没有头文件,只要类存在,就可以创建其对象

再来看一个实例。某项目需要兼容 v4.3 和 v6.0 版本的百度地图SDK(有点怪,是吧)

  • 百度地图SDK v4.3 - 没有 BMKCustomMapStyleOption,不支持自定义地图功能
  • 百度地图SDK v6.0 - 有 BMKCustomMapStyleOption,支持自定义地图功能

原先的代码是这样写的:

1
BMKCustomMapStyleOption *options = [[BMKCustomMapStyleOption alloc] init];

当使用百度地图SDK v6.0时,这行代码工作正常;当使用百度地图SDK v4.3时,这行代码无法编译,提示 BMKCustomMapStyleOption 引起 “Undefined symbols”。

我们可以使用 NSClassFromString 避免这里的编译错误:

1
id options = [[NSClassFromString(@"BMKCustomMapStyleOption") alloc] init];

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// MyBMapSettingsAdapter.m

- (void)setCustomMapStyleOptions:(MyCustomStyleOptions *)styleOptions {
if (styleOptions) {
// BMKCustomMapStyleOption *options = [[BMKCustomMapStyleOption alloc] init];
// options.customMapStyleID = styleOptions.styleId;
// options.customMapStyleFilePath = styleOptions.stylePath;
// self.bMapView setCustomMapStyleWithOption...

id options = [[NSClassFromString(@"BMKCustomMapStyleOption") alloc] init];

if (options) {
SEL idMethod = NSSelectorFromString(@"setCustomMapStyleID:");
[MyBMapSettingsAdapter invokeMethod:options selector:idMethod arg1:styleOptions.styleId];

SEL pathMethod = NSSelectorFromString(@"setCustomMapStyleFilePath:");
[MyBMapSettingsAdapter invokeMethod:options selector:pathMethod arg1:styleOptions.stylePath];

...
}

...
} else {
...
}
}

// 用于解决 "performSelector may cause a leak because its selector is unknown".
+ (void)invokeMethod:(id)obj selector:(SEL)selector arg1:(id)arg1 {
if (!obj) {
return;
}
if ([obj respondsToSelector:selector]) {
IMP imp = [obj methodForSelector:selector];
void (*func)(id, SEL, id) = (void*)imp;
func(obj, selector, arg1);
}
}

参考