·您现在的位置: 云翼网络 >> 文章中心 >> 网站建设 >> app软件开发 >> IOS开发 >> iOSRunTime的简单使用

iOSRunTime的简单使用

作者:佚名      IOS开发编辑:admin      更新时间:2022-07-23

1.根据指定规则根据runtime进行页面选择跳转

        背景:要根据后台返回的数据 进行选择要跳转到哪一个ViewController  

    

 // 这个规则肯定事先跟服务端沟通好,跳转对应的界面需要对应的参数

    NSDictionary *userInfo = @{

                               @"class": @"HSFeedsViewController",

                               @"PRoperty": @{//类中所需要的参数

                                       @"ID": @"123",

                                       @"type": @"12"

                                       }

                               };

        思路:使用runtime去解决这个问题的思路就是 通过运行时 根据对应的类名去创建类以及类的属性,这就需要和服务端去协商返回的数据形式 要类似: 

        实现:  这里使用了三个objc/runtime.h中函数分别是: 

 1   - (void)push:(NSDictionary *)params
 2 
 3 {
 4 
 5     // 类名
 6 
 7     NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]];
 8 
 9     const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
10 
11     
12 
13     // 从一个字串返回一个类
14 
15     Class newClass = objc_getClass(className);
16 
17     if (!newClass)
18 
19     {
20 
21         // 创建一个类
22 
23         Class superClass = [NSObject class];
24 
25         newClass = objc_allocateClassPair(superClass, className, 0); 
26 
27         // 注册你创建的这个类
28 
29         objc_registerClassPair(newClass);
30 
31     }
32 
33     // 创建对象
34 
35     id instance = [[newClass alloc] init];
36 
37     
38 
39     NSDictionary *propertys = params[@"property"];
40 
41     [propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
42 
43         // 检测这个对象是否存在该属性
44 
45         if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
46 
47             // 利用kvc赋值
48 
49             [instance setValue:obj forKey:key];
50 
51         }
52 
53     }]
54 
55     // 获取导航控制器
56 
57     UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
58 
59     UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
60 
61  
62 
63   // 跳转到对应的控制器
64 
65     [pushClassStance pushViewController:instance animated:YES];
66 
67 }

 

(1)objc_getClass(const char *name)  即返回对应的类;

(2)objc_allocateClassPair(Class superClass,const char*name ,size_t extraBytes) 为对应的类设置父类,并根据父类和extraBytes(变量的字节数) 给这个类分配空间

(3)objc_registerClassPair(class) 注册这个类

         通过这三个函数 创建类一个类

 

还有一个运行时函数  class_copyPropertyList(Class class,unsigned int *outCount) 

 1 /**
 2 
 3  *  检测对象是否存在该属性
 4 
 5  */
 6 
 7 - (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
 8 
 9 {
10 
11     unsigned int outCount, i;
12 
13         // 获取对象里的属性列表
14 
15     objc_property_t * properties = class_copyPropertyList([instance
16 
17                                                            class], &outCount);
18 
19     
20 
21     for (i = 0; i < outCount; i++) {
22 
23         objc_property_t property =properties[i];
24 
25         //  属性名转成字符串
26 
27         NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
28 
29         // 判断该属性是否存在
30 
31         if ([propertyName isEqualToString:verifyPropertyName]) {
32 
33             free(properties);
34 
35             return YES;
36 
37         }
38 
39     }
40 
41     free(properties);
42 
43     
44 
45     return NO;
46 
47 }

 

objc_property_t  class_copyPropertyList(Class class,unsigned int *outCount)    返回值为一个数组 数组中为对应的类的属性列表 以此来对比类中是否含有当前属性,从而通过KVC为类的属性赋值.

 

2.修改系统类的方法实现

      背景:如果需求中要统计每个viewController 展示给用户的次数,我们想到的就是在ViewController中添加对应的统计的代码,但是会很麻烦,此时可以创建一个UI ViewController的分类更改底层中viewWillAppear的实现 这就用到runtime中method Swizzling (方法混合)

     实现:通过methodSwizzling 修改了UIViewController的@selector(viewWillAppear:)对应的函数指针,使他的实现指向了自定的方法

 

#import "UIViewController+Tracking.h"

#import <objc/runtime.h>

 

@implementation UIViewController (Tracking)

 + (void)load

{

    

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        Class  class  = [self class];

        SEL originalSelector = @selector(viewWillAppear:);

        SEL swizzlingSelector = @selector(swizzling__viewWillAppear:);

        Method originMethod = class_getInstanceMethod(class, originalSelector);

        Method swizzlingMethod = class_getInstanceMethod(class, swizzlingSelector);

        

        //将 swizzlingMethod 中的实现过程添加到 originalSelector 这个方法中

        BOOL didAddMethod = class_addMethod(class,originalSelector, method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));

        if (didAddMethod) {

            //用 originMethod中的实现 替换 swizzlingSelector这个方法中的实现 这样原始的和自定义的方法都执行的时同一个实现过程

            class_replaceMethod(class, swizzlingSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));

        }else{//可能出现添加失败的情况(比如 被添加的方法中已经存在了对应方法的实现 就会返回NO)

            //将 originMethod 和 swizzlingMethod中的实现过程进行交换

            method_exchangeImplementations(originMethod, swizzlingMethod);

        }

        

    });

}

/*

 经过上面的  class_method 和 class_replaceMethod 或  method_exchangeImplementations 就实现了

 //http://blog.jobbole.com/79580/

 */

 

- (void)swizzling__viewWillAppear:(BOOL)animation

{

    [self swizzling__viewWillAppear:animation];

    NSLog(@"swizzling_viewWillAppear");

}

 

(1)通过两个方法  class_addMethod class_replaceMethod这个方法实现方法Method这个结构体中 IMP对应的函数指针对应的函数实现进行添加 或者 替换SEL 代表了Method的名称name,IMP代表函数指针 即为函数在内存中的开始位置

另外还有一些注意事项 为什么一定要写在load中 还要写在dispatch_once中,因为swilzzing method会影响代码的整体状态 甚至有可能改变程序的运行流程,要保证避免并发的竞争,而+load方法是在类初始化就进行了加载(这里不太明白具体原理)详细参看下面链接

http://nshipster.com/method-swizzling/