ios 放射机制的了解

160 阅读3分钟

系统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的简单使用