系统Foundation框架为我们提供了一些方法反射的API,我们可以通过这些API执行将字符串转为SEL等操作。由于OC语言的动态性,这些操作都是发生在运行时的。
// SEL和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);
// Class和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class __nullable NSClassFromString(NSString *aClassName);
// Protocol和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) NS_AVAILABLE(10_5, 2_0);
FOUNDATION_EXPORT Protocol * __nullable NSProtocolFromString(NSString *namestr) NS_AVAILABLE(10_5, 2_0);
通过这些方法,我们可以在运行时选择创建哪个实例,并动态选择调用哪个方法。这些操作甚至可以由服务器传回来的参数来控制,我们可以将服务器传回来的类名和方法名,实例为我们的对象。
// 假设从服务器获取JSON串,通过这个JSON串获取需要创建的类为ViewController,并且调用这个类的getDataList方法。
例子如下:创建了一个测试用的字典然后利用反射机制调用了testViewController的方法。打印了数据
#import "ViewController.h"
#import "TestViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSDictionary *testDic = @{
// 类名
@"className" : @"TestViewController",
// 数据参数
@"propertys" : @{ @"name": @"liuxiaozhuang",
@"age": @3 },
// 调用方法名
@"method" : @"getDataList"
};
[self remoteNotificationDictionary:testDic];
}
- (void)remoteNotificationDictionary:(NSDictionary *)dict {
// 根据字典字段反射出我们想要的类,并初始化控制器
Class class = NSClassFromString(dict[@"className"]);
UIViewController *vc = [[class alloc] init];
// 获取参数列表,使用枚举的方式,对控制器属性进行KVC赋值
NSDictionary *parameter = dict[@"propertys"];
[parameter enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 在属性赋值时,做容错处理,防止因为后台数据导致的异常
if ([vc respondsToSelector:NSSelectorFromString(key)]) {
[vc setValue:obj forKey:key];
}
}];
[self.navigationController pushViewController:vc animated:YES];
// 从字典中获取方法名,并调用对应的方法
SEL selector = NSSelectorFromString(dict[@"method"]);
[vc performSelector:selector];
@end
- testViewController.h & .m
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface TestViewController : UIViewController
@property (nonatomic,strong) NSString *name;/*!< 用户名 */
@property (nonatomic,strong) NSNumber *age;/*!< 用户年龄 */
- (void)getDataList;
- (void)getTestList:(NSString *)name withAge:(NSNumber *)age;
@end
NS_ASSUME_NONNULL_END
#import "TestViewController.h"
@interface TestViewController ()
@end
@implementation TestViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)getDataList{
NSLog(@"name:%@,age:%@",self.name,self.age);
}
- (void)getTestList:(NSString *)name withAge:(NSNumber *)age{
NSLog(@"name:%@,age:%@",name,age);
}
@end
因为上面的例子的传值是利用属性赋值的,假如想调用的方法带传参怎么办呢,同时[vc performSelector:selector];这里代码会报警告。
- 解决方法1:用这段代码替换
[vc performSelector:selector];
IMP imp = [vc methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(vc, selector);
- 方法2:创建宏定义
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks" \
[someController performSelector: NSSelectorFromString(@"someMethod")]
#pragma clang diagnostic pop
//多个警告的忽略
#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop") \
} while (0)
- 宏定义的使用
SuppressPerformSelectorLeakWarning(
[vc performSelector:selector];
);
//如果需要performSelector返回值的话,
id result;
SuppressPerformSelectorLeakWarning(
result = [_target performSelector:_action withObject:self]
);
现在来完成传参函数的调用,其实就是用的方法1.当然这个方法也是可以调用带返回值的函数的,只不过我没写。但是注释了代码提供了思路了。
Class class = NSClassFromString(@"TestViewController");
UIViewController *vc = [[class alloc] init];
SEL selector = NSSelectorFromString(@"getTestList:withAge:");
IMP imp = [vc methodForSelector:selector];
// CGRect (*func)(id, SEL, NSString *, NSNumber *) = (void *)imp;
void (*func)(id, SEL, NSString*, NSNumber*) = (void *)imp;
// CGRect result = func(_controller, selector, @"小黄", @(11));
func(vc, selector, @"小黄", @(11));
这个反射机制 其实就是 CTMediator(组件化之间的交流)的核心内容 CTMediator的简单使用