[关闭]
@whiteiOS 2017-04-10T11:08:46.000000Z 字数 8870 阅读 693

二十六、Runtime

Objective-c


一、Runtime简介

二、Runtime作用

(一)发送消息(objc_msgSend)

1. 验证:方法的调用是否真的转换为消息机制

  1. id objc = [[NSObject alloc] init];
  1. id objc = [NSObject alloc];
  2. objc = [objc init];

2. 消息机制简单实现

  1. //参数一:谁发送消息
  2. //参数二:发送什么消息
  3. //参数三:发送消息传递的参数,以此类推
  4. objc_msgSend(id self, SEL op, ...)

注意:
1.使用消息机制,必须导入#import <objc/message.h>
并且设置Build Setting -> 搜索msg -> 设置属性为No,否则没有参数提示
2.只有对象才能发送消息,就算是类名调用类方法,其本质也是将类名转换成类对象,因此以objc开头

①创建Person对象
OC语句

  1. Person *p = [[Person alloc] init];

runtime

  1. /***************简单写法***************/
  2. Person *p = objc_msgSend([Person class], @selector(alloc));
  3. p = objc_msgSend(p, @selector(init));
  4. /***************底层写法***************/
  5. //objc_getClass() : 根据传入的字符串获取某个类,注意传入的C语言字符串,所以前面不加@
  6. //sel_registerName() : 根据传入的方法名注册方法编号,根据方法编号才能找到对应的方法实现
  7. Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
  8. p = objc_msgSend(p, sel_registerName("init"));

②调用对象方法eat

OC语句

  1. [p eat];

runtime

  1. objc_msgSend(p, @selector(eat));

③调用类方法run

OC语句

  1. [Person run:20];

runtime

  1. objc_msgSend([Person class], @selector(run:), 20);

3. 消息机制的主要应用场景

4. 方法调用流程(消息机制的原理)

比如[p eat];
1. 对象通过isa指针找到自己所属的类
2. runtime会通过sel_registerName("eat")将eat方法注册成方法编号
3. 根据方法编号去查找对应方法(对象方法存储在类对象的方法列表中,类方法存储在元类的方法列表中)
4. 找到的只是最终函数的实现地址,最后根绝地址去方法区调用对应函数的实现

(二)交换方法(method_exchangeImplementation())

1. 交换方法的主要应用场景

2. 需求示例

  1. //自定义类CXImage,继承自UIImage,重写imageNamed
  2. + (UIImage *)imageNamed:(NSString *)name
  3. {
  4. //原有功能:加载图片
  5. UIImage *image = [super imageNamed:name];
  6. //扩展功能:判断图片是否加载成功
  7. if (image) {
  8. NSLog(@"加载成功");
  9. } else {
  10. NSLog(@"加载失败");
  11. }
  12. return image;
  13. }

① 步骤1:先创建分类UIImage+Image,自己定义一个能加载图片并判断是否加载成功的方法

  1. + (UIImage *)cx_imageNamed:(NSString *)imageName
  2. {
  3. //原有功能:加载图片
  4. UIImage *image = [UIImage imageNamed:imageNamed];
  5. //扩展功能:判断图片是否加载成功
  6. if (image) {
  7. NSLog(@"加载成功");
  8. } else {
  9. NSLog(@"加载失败");
  10. }
  11. return image;
  12. }

② 步骤2:当调用imageNamed方法时会自动调用cx_imageNamed方法,也就是交换两个方法的实现

  1. //交换方法只需要设置一次
  2. //加载这个分类时调用
  3. + (void)load
  4. {
  5. //1.先把两个方法取出来
  6. //方法都是从类里面寻找,所以以class开头
  7. //第一个参数:哪个类
  8. //第二个参数:哪个方法
  9. Method imageNamedMethod = class_getclassMethod(self, @selector(imageNamed:));
  10. Method cx_imageNamedMethod = class_getclassMethod(self, @selector(cx_imageNamed:));
  11. //2.方法交换
  12. method_exchangeImplementations(imageNamedMethod, cx_imageNamedMethod);
  13. }

③ 注意:因为此时imageNamedcx_imageNamed方法已经交换实现了,所以在cx_imageNamed方法中不能再使用imageNamed,那样会引起死循环,要改成cx_imageNamed

  1. + (UIImage *)cx_imageNamed:(NSString *)imageName
  2. {
  3. //原有功能:加载图片
  4. UIImage *image = [UIImage cx_imageNamed:imageNamed];
  5. //扩展功能:判断图片是否加载成功
  6. if (image) {
  7. NSLog(@"加载成功");
  8. } else {
  9. NSLog(@"加载失败");
  10. }
  11. return image;
  12. }

(三)动态添加方法(class_addMethod)

1. 动态添加方法的主要应用场景

2. 动态添加方法的简单使用:

① 创建一个Person类,里面没有任何方法,然后在外界直接调用某个方法,如果这时运行程序就会崩

  1. Person *p = [[Person alloc] init];
  2. //performSelector:动态添加方法
  3. [p performSelector:@selector(eat)];

② 在Person类中动态添加方法

  1. + (BOOL)resolveInstanceMethod:(SEL)sel
  2. {
  3. if (sel == NSSelectorFromString(@"eat")) {
  4. //动态添加方法
  5. }
  6. return [super resolveInstanceMethod:sel];
  7. }

  1. //函数的实现
  2. //默认一个方法都会包含两个隐式参数-self和_cmd
  3. void myMethodIMP(id self, SEL _cmd)
  4. {
  5. //implementation...
  6. }
  7. //动态添加方法
  8. class_addMethod([self class], @selector(resolveThisMethodDynamically), (IMP) myMethodIMP, "v@:");
  9. //最后一个参数-函数的类型可以通过官方文档中`Type Encodings`查到,v表示void,@表示对象,:表示SEL
  1. void eat(id self, SEL _cmd)
  2. {
  3. NSLog(@"实现了eat%@--%@", self, NSStringFromSelector(_cmd));
  4. }
  5. + (BOOL)resolveInstanceMethod:(SEL)sel
  6. {
  7. if (sel == NSSelectorFromString(@"eat")) {
  8. class_addMethod(self, sel, (IMP)eat, "v@:");
  9. return YES;
  10. }
  11. return [super resolveInstanceMethod:sel];
  12. }

外界调用

  1. [p performSelector:@selector(run:) withObject:@10];

动态添加

  1. void run(id self, SEL _cmd, NSNumber *meter)
  2. {
  3. NSLog(@"跑了%@米", meter);
  4. }
  5. + (BOOL)resolveInstanceMethod:(SEL)sel
  6. {
  7. if (sel == NSSelectorFromString(@"run:")) {
  8. class_addMethod(self, sel, (IMP)run, "v@:@");
  9. return YES;
  10. }
  11. return [super resolveInstanceMethod:sel];
  12. }

(四)分类添加属性

NSObject+Extension.h

  1. @interface NSObject (Extension)
  2. @property NSString *name;
  3. @end

NSObject+Extension.m

  1. @implementation NSObject (Extension)
  2. - (void)setName:(NSString *)name
  3. {
  4. //参数一:给谁添加属性
  5. //参数二:属性名称
  6. //参数三:属性值
  7. //参数四:保存策略
  8. objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  9. }
  10. - (NSString *)name
  11. {
  12. return objc_getAssociatedObject(self, @"name");
  13. }
  14. @end

ViewController.m

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. NSObject *objc = [[NSObject alloc] init];
  4. objc.name = @"123";
  5. NSLog(@"%@", objc.name);
  6. }

(五)字典转模型

1. 利用KVC进行字典转模型

  1. + (instancetype)itemWithDictionary:(NSDictionary *)dict
  2. {
  3. StatusItem *item = [[StatusItem alloc] init];
  4. //KVC:把字典中所有值给模型的属性赋值
  5. [item setValuesForKeysWithDictionary:dict];
  6. return item;
  7. }
  1. + (instancetype)itemWithDictionary:(NSDictionary *)dict
  2. {
  3. StatusItem *item = [[StatusItem alloc] init];
  4. //KVC:把字典中所有值给模型的属性赋值
  5. // [item setValuesForKeysWithDictionary:dict];
  6. [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) {
  7. [item setValue:value forKey:key];
  8. }];
  9. return item;
  10. }
  1. - (void)setValue:(id)value forUndefinedKey:(NSString *)key
  2. {
  3. }

2. 利用runtime进行字典转模型(一级转换)

  1. //字典转模型
  2. + (instancetype)modelWithDict:(NSDictionary *)dict
  3. {
  4. id objc = [[self alloc] init];
  5. //1.获取模型中的所有成员变量
  6. //参数一:获取哪个类的成员变量
  7. //参数二:成员变量个数
  8. //返回类型:Ivar *,是一个数组
  9. unsigned int count = 0;
  10. Ivar *ivarList = class_copyIvarList(self, &count);
  11. //遍历所有成员变量
  12. for (int i = 0, i < count, i++) {
  13. //取出成员变量
  14. Ivar ivar = ivarList[i];
  15. //获取成员变量的名字
  16. NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
  17. //成员变量名字是带下划线的,要把下划线去掉才是正确的key
  18. NSString *key = [ivarName substringFromIndex:1];
  19. //2.根据key去字典中取出对应的value
  20. id value = dict[key];
  21. //3.用value给模型属性赋值
  22. if (value) {
  23. [objc setValue:value forKey:key];
  24. }
  25. }
  26. return objc;
  27. }

为什么第一步是获取模型中的所有成员变量而不是属性?
利用runtime既可以获取类中的所有成员变量,也可以获取所有属性(class_copyPropertyList)。
如果类中存在成员变量:
{
int _age;
}
@property (nonatomic, strong) NSString *source;
这时候使用获取属性的方法class_copyPropertyList是拿不到_age的,只能拿到source属性。
如果使用获取成员变量方法的话,既能拿到_age,又能拿到_source,所以直接获取所有成员变量的方法更为稳妥。

2. 利用runtime进行字典转模型(二级转换)

  1. //字典转模型
  2. + (instancetype)modelWithDict:(NSDictionary *)dict
  3. {
  4. id objc = [[self alloc] init];
  5. //1.获取模型中的所有成员变量
  6. //参数一:获取哪个类的成员变量
  7. //参数二:成员变量个数
  8. //返回类型:Ivar *,是一个数组
  9. unsigned int count = 0;
  10. Ivar *ivarList = class_copyIvarList(self, &count);
  11. //遍历所有成员变量
  12. for (int i = 0, i < count, i++) {
  13. //取出成员变量
  14. Ivar ivar = ivarList[i];
  15. //获取成员变量的名字
  16. NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
  17. //获取成员变量的类型
  18. NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
  19. // @\"User\" -> User
  20. ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
  21. ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
  22. //成员变量名字是带下划线的,要把下划线去掉才是正确的key
  23. NSString *key = [ivarName substringFromIndex:1];
  24. //2.根据key去字典中取出对应的value
  25. id value = dict[key];
  26. //二级转换:判断value是否是字典
  27. //并且是自定义对象才需要转换
  28. if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
  29. // 获取类
  30. Class modelClass = NSClassFromString(ivarType);
  31. value = [modelClass modelWithDict:value];
  32. }
  33. //3.用value给模型属性赋值
  34. if (value) {
  35. [objc setValue:value forKey:key];
  36. }
  37. }
  38. return objc;
  39. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注