当前位置:   article > 正文

iOS runtime的应用实例

iOS runtime的应用实例

  一直想弄明白runtime是怎么回事,因为面试的时候这是一道必备问题,但是平时用的机会真的少之又少,我一度以为runtime只是用来装13的利器,没什么卵用。但是随着学习的增多,发现runtime真的很有用,但也没那么神秘。我相信看了我这篇博客,您对runtime肯定会有自己的理解。
  先说说OC与C的对比:
1.OC是对OC的面向对象的封装,OC中的对象只是C中指向结构体的指针。
2.OC的方法,本质上就是C语言中的函数,OC中的任意一个方法,在runtime中都会有一个与之对应的函数。eg:[objc sendMessage:@"I am back"]; -> objc_msg(self,@selector(sendMessage),"I am back");所以说在OC中对象调用方法,到运行的时候,都会变成向对象发送消息,这就是runtime中最著名的消息机制
3.既然本质都是函数,那是不是和C语言的函数没有区别呢?绝对不是。
(1)C语言只能调用实现过的函数,只声明了是不行的,编译是不能通过的。
(2)OC无所谓,只要声明了就能调用,即时你没声明都能调用,编译阶段都不会报错,只会报警告。

- (id)performSelector:(SEL)aSelector;

这样据说是保证了编程的灵活性,反正大家都这么说,但是我觉得这就是不够严谨,因为真要是需要这个方法执行了,程序就得崩溃,在编译的时候就能解决的问题,为什么要等到程序崩溃再修改代码呢,有点浪费时间啊。
  下面列举了几个runtime的应用实例,先用起来,用的多了,自然就理解了。

1.runtime的常用方法

  runtime可以动态获取一个对象的成员变量、属性、方法、遵守的协议。

  1. #import <Foundation/Foundation.h>
  2. #import "ProtocolTest1.h"
  3. #import "ProtocolTest2.h"
  4. @interface Person : NSObject <ProtocolTest1,ProtocolTest2>
  5. @property (nonatomic, strong) NSString *name;
  6. @property (nonatomic, strong) NSString *age;
  7. - (NSString *)getName;
  8. - (NSString *)getAge;
  9. + (void)classMethodTest;
  10. @end
  1. #import "Person.h"
  2. @implementation Person
  3. - (NSString *)getName {
  4. return @"I am Tomcat";
  5. }
  6. - (NSString *)getAge {
  7. return @"I will be 18 years old forever";
  8. }
  9. + (void)classMethodTest {
  10. NSLog(@"This is a class method");
  11. }
  12. - (NSString *)description {
  13. return [NSString stringWithFormat:@"name=%@,age=%@",_name,_age];
  14. }
  15. @end

  先看看我们的小白鼠Person类,我们就拿他做实验,动态获取他的成员变量、属性、方法、遵守的协议。

  1. #import "FirstViewController.h"
  2. #import <objc/runtime.h>
  3. #import "Person.h"
  4. @interface FirstViewController ()
  5. @end
  6. @implementation FirstViewController
  7. - (void)viewDidLoad {
  8. [super viewDidLoad];
  9. Person *tomcat = [Person new];
  10. unsigned int count;
  11. NSLog(@"\n1.获得属性列表");
  12. // 1.get describes of all properties (获得属性列表)
  13. objc_property_t *propertyList = class_copyPropertyList([tomcat class], &count);
  14. for (unsigned int i = 0; i < count; i++) {
  15. const char *propertyName = property_getName(propertyList[i]);
  16. printf("property = %s\n",propertyName);
  17. }
  18. // 2.get describes of all methods (获得方法列表)
  19. NSLog(@"\n\n2.获得方法列表");
  20. Method *methodList = class_copyMethodList([tomcat class], &count);
  21. for (unsigned int i = 0; i < count; i++) {
  22. SEL methodName = method_getName(methodList[i]);
  23. NSLog(@"methodName = %@",NSStringFromSelector(methodName));
  24. }
  25. // 3.get describes of all variables (获得成员变量列表)
  26. NSLog(@"\n\n3.获得成员变量列表");
  27. Ivar *ivarList = class_copyIvarList([tomcat class], &count);
  28. for (unsigned int i = 0; i < count; i++) {
  29. const char *ivarNmae = ivar_getName(ivarList[i]);
  30. printf("ivarNmae = %s\n",ivarNmae);
  31. // 动态变量控制
  32. object_setIvar(tomcat, ivarList[i], @"哈哈,你被我改了");
  33. }
  34. NSLog(@"动态变量控制: name = %@",tomcat.name);
  35. //4.get describes of all protocols adopted by a class (获得当前对象遵守的协议列表)
  36. NSLog(@"\n\n4.获得协议列表");
  37. __unsafe_unretained Protocol **protocolList = class_copyProtocolList([tomcat class], &count);
  38. for (unsigned int i = 0; i < count; i++) {
  39. const char *protocolNmae = protocol_getName(protocolList[i]);
  40. printf("protocolNmae = %s\n",protocolNmae);
  41. }

  注意:我们要使用runtime库,首先要 #import <objc/runtime.h>

2.拦截并替换方法

  想要拦截和替换方法,首先要找到方法,根据什么找呢?方法名。

  1. //5. 通过方法名获得类方法
  2. Class personClass = object_getClass([Person class]);
  3. SEL classSel = @selector(classMethodTest);
  4. Method classMethod = class_getInstanceMethod(personClass, classSel);
  5. //6. 通过方法名获得实例方法
  6. SEL objSel1 = @selector(getName);
  7. Method objMethod1 = class_getInstanceMethod([tomcat class], objSel1);
  8. SEL objSe2 = @selector(getAge);
  9. Method objMethod2 = class_getInstanceMethod([tomcat class], objSe2);

  我们还可以交换这两个方法的实现

  1. //7. 交换两个方法的实现
  2. NSLog(@"\n\n交换两个方法的实现");
  3. NSLog(@"交换之前 --- getName = %@,getAge = %@", [tomcat getName],[tomcat getAge]);
  4. method_exchangeImplementations(objMethod1, objMethod2);
  5. NSLog(@"交换之后 --- getName = %@,getAge = %@", [tomcat getName],[tomcat getAge]);

872567-20170227095226610-1258896506.png

  拦截并替换方法,多用于给系统方法添加新的功能和修改第三方库。我们现在实现一个功能,就是给计算按钮的点击计数。

  1. #import "UIButton+Count.h"
  2. #import <objc/runtime.h>
  3. #import "Tool.h"
  4. @implementation UIButton (Count)
  5. + (void)load {
  6. static dispatch_once_t onceToken;
  7. dispatch_once(&onceToken, ^{
  8. Class selfClass = [self class];
  9. // 1.原来的方法
  10. SEL oriSEL = @selector(sendAction:to:forEvent:);
  11. Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
  12. // 2.现在的方法
  13. SEL cusSel = @selector(mySendAction:to:forEvent:);
  14. Method cusMethod = class_getInstanceMethod(selfClass, cusSel);
  15. // 3.给原来的方法添加实现,防止原来的方法没有实现,只有声明崩溃
  16. BOOL addSuc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
  17. if (addSuc) {
  18. // 添加成功,用现在的方法的实现替换原来方法的实现
  19. class_replaceMethod(selfClass, cusSel, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
  20. }else {
  21. // 没添加成功,证明原来的方法有实现,直接交换两个方法
  22. method_exchangeImplementations(oriMethod, cusMethod);
  23. }
  24. });
  25. }
  26. // 现在的方法
  27. - (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
  28. [[Tool shareInstance] countClicks];
  29. [super sendAction:action to:target forEvent:event];
  30. }
  31. @end

Tool.h

  1. #import <Foundation/Foundation.h>
  2. @interface Tool : NSObject
  3. @property (nonatomic, assign) NSInteger count;
  4. + (instancetype)shareInstance;
  5. - (void)countClicks;
  6. @end

Tool.m

  1. #import "Tool.m"
  2. @implementation Tool
  3. static id _instance;
  4. + (instancetype)shareInstance {
  5. static dispatch_once_t onceToken;
  6. dispatch_once(&onceToken, ^{
  7. _instance = [[Tool alloc] init];
  8. });
  9. return _instance;
  10. }
  11. - (void)countClicks {
  12. _count += 1;
  13. NSLog(@"您点击了%ld次",_count);
  14. }
  15. @end

3.实现NSCoding自动归档解档

  NSCoding用于存储模型对象,必须实现代理NSCoding。

  1. #import "Student.h"
  2. #import <objc/runtime.h>
  3. @implementation Student
  4. - (void)encodeWithCoder:(NSCoder *)aCoder {
  5. // 如果不用runtime
  6. // [aCoder encodeObject:self.name forKey: @"name"];
  7. // [aCoder encodeObject:self.stuID forKey: @"stuID"];
  8. // [aCoder encodeObject:self.score forKey: @"score"];
  9. // [aCoder encodeObject:self.name forKey: @"myFriend"];
  10. unsigned int count = 0;
  11. // 获取变量列表
  12. Ivar *ivars = class_copyIvarList([self class], &count);
  13. for (int i = 0; i < count; i++) {
  14. // 获取变量名
  15. const char *name = ivar_getName(ivars[i]);
  16. NSString *key = [NSString stringWithUTF8String:name];
  17. id value = [self valueForKey:key];
  18. // 用变量名归档变量
  19. [aCoder encodeObject:value forKey:key];
  20. }
  21. free(ivars);
  22. }
  23. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  24. if (self = [super init]) {
  25. // 如果不用runtime
  26. // self.name = [aDecoder decodeObjectForKey:@"name"];
  27. // self.stuID = [aDecoder decodeObjectForKey:@"stuID"];
  28. // self.score = [aDecoder decodeObjectForKey:@"score"];
  29. // self.myFriend = [aDecoder decodeObjectForKey:@"myFriend"];
  30. unsigned int count = 0;
  31. // 获取变量列表
  32. Ivar *ivars = class_copyIvarList([self class], &count);
  33. for (int i = 0; i < count; i++) {
  34. // 获取变量名
  35. const char *name = ivar_getName(ivars[i]);
  36. NSString *key = [NSString stringWithUTF8String:name];
  37. // 用变量名解档变量
  38. id value = [aDecoder decodeObjectForKey:key];
  39. [self setValue:value forKey:key];
  40. }
  41. free(ivars);
  42. }
  43. return self;
  44. }
  45. - (NSString *)description {
  46. return [NSString stringWithFormat:@"name=%@\nstuID=%@\nscore=%@\nmyFriend:%@",_name,_stuID,_score,_myFriend];
  47. }
  48. @end

  如果模型属性少无所谓,如果多的话,最好用runtime。有100个属性,你不可能写100次解档归档啊。

4.实现字典转模型的自动转换

  其实这个用系统方法就很好了,这次我用runtime实现一下,估计系统方法也是这个逻辑。

  1. #import "NSObject+Model.h"
  2. #import <objc/runtime.h>
  3. @implementation NSObject (Model)
  4. + (instancetype)allocWithDic: (NSDictionary *)dic {
  5. id objc = [[self alloc] init];
  6. unsigned int count = 0;
  7. Ivar *ivarList = class_copyIvarList(self, &count);
  8. for (int i = 0; i < count; i++) {
  9. Ivar ivar = ivarList[i];
  10. NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
  11. NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
  12. NSString *key = [ivarName substringFromIndex:1];
  13. id value = dic[key];
  14. NSLog(@"type = %@",ivarType);
  15. // 把模型转化的字典再转换成模型
  16. // 先判断value类型,value类型是自字典,但是ivarType又不是NSDictionary,说明value实际上是一个模型转化的字典
  17. if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) {
  18. // @"Person" -> "Person"
  19. ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
  20. // "Person" -> Person
  21. ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
  22. // 生成模型
  23. Class modelClass = NSClassFromString(ivarType);
  24. if (modelClass) {
  25. // 给模型里的属性赋值
  26. value = [modelClass allocWithDic:value];
  27. }
  28. }
  29. if (value) {
  30. [objc setValue:value forKey:key];
  31. }else {
  32. NSLog(@"没找到value");
  33. }
  34. }
  35. return objc;
  36. }
  37. @end

  rumtime库是一个非常强大的,我列举的这几个用法是比较常用和基础的。例外runtime是开源的,不过看起来应该很难,基本上是用c语言和汇编写的,现在懂汇编的应该很少。本文完整案例已经上传到Github,欢迎下载。

转载于:https://www.cnblogs.com/doujiangyoutiao/p/6472246.html

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号