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/