对于从事 iOS 开发人员来说,当提到 ** runtime 时,我想都可以说出来 「
」和基本使用的方法。相信很多开发者跟我当初一样,也许当你使用这个重要的模块完成一些工作任务之后 (
- runtime 运行时
),还是不清楚 runtime** 知识体系和内在原理。
- 复制粘贴的^_^.
** runtime** 是 iOS 编程中比较难的模块,想要深入学习 OC,那 ** runtime** 是你必须要熟练掌握的东西,下面是我对
的整理,从零开始,由浅入深,并且带了几个
- runtime
实际开发的应用场景。
- runtime
本篇文章主要从【runtime 模块实用详解】学习总结。 在「时间和知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,以提高文章质量。
目录:
这篇文章较长一些,强烈建议先 ❤️ 收藏,在进行阅读 !
Objective-C 是基于 C 的,它为 C 添加了面向对象的特性。它将很多静态语言在编译和链接时期做的事放到了 runtime 运行时来处理,可以说 runtime 是我们 Objective-C 幕后工作者。
),是一套 纯 C(C 和汇编写的) 的 API。而 OC 就是 运行时机制,也就是在运行时候的一些机制,其中最主要的是 消息机制。
- 简称运行时
我们写 OC 代码,它在运行的时候也是转换成了
方式运行的。任何方法调用本质:就是发送一个消息(用
- runtime
发送消息,OC 底层实现通过
- runtime
实现)。
- runtime
消息机制原理:对象根据方法编号 SEL 去映射表查找对应的方法实现。
每一个 OC 的方法,底层必然有一个与之对应的
方法。
- runtime
简单示例: 验证:方法调用,是否真的是转换为消息机制?
- #import <objc/message.h>
-> 搜索
- build setting
->
- msg
(YES --> NO)】
- objc_msgSend
查看最终生成代码】,示例:
- clang -rewrite-objc main.m
- cd main.m --> 输入前面指令,就会生成 .opp文件(C++代码)
- <objc/runtime.h>
- 说明: eat(无参) 和 run(有参) 是 Person模型类中的私有方法「可以帮我调用私有方法」;
- // Person *p = [Person alloc];
- // 底层的实际写法
- Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
- // p = [p init];
- p = objc_msgSend(p, sel_registerName("init"));
- // 调用对象方法(本质:让对象发送消息)
- //[p eat];
- // 本质:让类对象发送消息
- objc_msgSend(p, @selector(eat));
- objc_msgSend([Person class], @selector(run:),20);
- //--------------------------- <#我是分割线#> ------------------------------//
- // 也许下面这种好理解一点
- // id objc = [NSObject alloc];
- id objc = objc_msgSend([NSObject class], @selector(alloc));
- // objc = [objc init];
- objc = objc_msgSend(objc, @selector(init));
面试:消息机制方法调用流程
方法,对象方法:(保存到类对象的方法列表) ,类方法:(保存到元类 (
- eat
) 中方法列表)。
- Meta Class
库会根据对象的
- runtime
指针找到该对象对应的类或其父类中查找方法。。
- isa
对象的
- objc
的指针指向什么?有什么作用?
- isa
应用场景:当第三方框架 或者 系统原生方法功能不能满足我们的时候,我们可以在保持系统原有方法功能的基础上,添加额外的功能。
需求:加载一张图片直接用
是无法知道到底有没有加载成功。给系统的
- [UIImage imageNamed:@"image"];
添加额外功能(是否加载图片成功)。
- imageNamed
实现步骤:
案例代码:方法 + 调用 + 打印输出
- - (void)viewDidLoad {
- [super viewDidLoad];
- // 方案一:先搞个分类,定义一个能加载图片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
- // 方案二:交换 imageNamed 和 ln_imageNamed 的实现,就能调用 imageNamed,间接调用 ln_imageNamed 的实现。
- UIImage *image = [UIImage imageNamed:@"123"];
- }
- #import <objc/message.h>
- @implementation UIImage (Image)
- /**
- load方法: 把类加载进内存的时候调用,只会调用一次
- 方法应先交换,再去调用
- */
- + (void)load {
- // 1.获取 imageNamed方法地址
- // class_getClassMethod(获取某个类的方法)
- Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
- // 2.获取 ln_imageNamed方法地址
- Method ln_imageNamedMethod = class_getClassMethod(self, @selector(ln_imageNamed:));
- // 3.交换方法地址,相当于交换实现方式;「method_exchangeImplementations 交换两个方法的实现」
- method_exchangeImplementations(imageNamedMethod, ln_imageNamedMethod);
- }
- /**
- 看清楚下面是不会有死循环的
- 调用 imageNamed => ln_imageNamed
- 调用 ln_imageNamed => imageNamed
- */
- // 加载图片 且 带判断是否加载成功
- + (UIImage *)ln_imageNamed:(NSString *)name {
- UIImage *image = [UIImage ln_imageNamed:name];
- if (image) {
- NSLog(@"runtime添加额外功能--加载成功");
- } else {
- NSLog(@"runtime添加额外功能--加载失败");
- }
- return image;
- }
- /**
- 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super
- 所以第二步,我们要 自己实现一个带有扩展功能的方法.
- + (UIImage *)imageNamed:(NSString *)name {
- }
- */
- @end
- // 打印输出
- 2017-02-17 17:52:14.693 runtime[12761:543574] runtime添加额外功能--加载成功
总结:我们所做的就是在方法调用流程第三步的时候,交换两个方法地址指向。而且我们改变指向要在系统的
方法调用前,所以将代码写在了分类的
- imageNamed:
方法里。最后当运行的时候系统的方法就会去找我们的方法的实现。
- load
原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。
应用场景:给系统的类添加属性的时候, 可以使用 runtime 动态添加属性方法。 注解:系统
添加一个分类,我们知道在分类中是不能够添加成员属性的,虽然我们用了
- NSObject
,但是仅仅会自动生成
- @property
和
- get
方法的声明,并没有带下划线的属性和方法实现生成。但是我们可以通过
- set
就可以做到给它方法的实现。
- runtime
需求:给系统 NSObject 类动态添加属性
字符串。
- name
案例代码:方法 + 调用 + 打印
- @interface NSObject (Property)
- // @property分类:只会生成get,set方法声明,不会生成实现,也不会生成下划线成员属性
- @property NSString *name;
- @property NSString *height;
- @end
- @implementation NSObject (Property)
- - (void)setName:(NSString *)name {
- // objc_setAssociatedObject(将某个值跟某个对象关联起来,将某个值存储到某个对象中)
- // object:给哪个对象添加属性
- // key:属性名称
- // value:属性值
- // policy:保存策略
- objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- - (NSString *)name {
- return objc_getAssociatedObject(self, @"name");
- }
- // 调用
- NSObject *objc = [[NSObject alloc] init];
- objc.name = @"123";
- NSLog(@"runtime动态添加属性name==%@",objc.name);
- // 打印输出
- 2017-02-17 19:37:10.530 runtime[12761:543574] runtime动态添加属性--name == 123
总结:其实,给属性赋值的本质,就是让属性与一个对象产生关联,所以要给
的分类的
- NSObject
属性赋值就是让
- name
和
- name
产生关联,而
- NSObject
可以做到这一点。
- runtime
字典转模型的方式:
一一对应。
- key
报
- [<Status 0x7fa74b545d60> setValue:forUndefinedKey:]
找不到的错。
- key
不一一对应,系统就会调用
- key
报错。
- setValue:forUndefinedKey:
,把系统的方法覆盖,就能继续使用 KVC,字典转模型了。
- setValue:forUndefinedKey:
,取出对应的值,给模型的属性赋值(从提醒:字典中取值,不一定要全部取出来)。
- key
和模型的属性匹配不上。
- key
和模型的属性不对应的情况。不对应有两种,一种是字典的键值大于模型属性数量,这时候我们不需要任何处理,因为
- key
是先遍历模型所有属性,再去字典中根据属性名找对应值进行赋值,多余的键值对也当然不会去看了;另外一种是模型属性数量大于字典的键值对,这时候由于属性没有对应值会被赋值为
- runtime
,就会导致
- nil
,我们只需加一个判断即可。考虑三种情况下面一一注解;
- crash
分类,专门字典转模型,以后所有模型都可以通过这个分类实现字典转模型。
- NSObject
的封装,才可以把一个模型中所有属性遍历出来。(你之所以看不懂,是 MJ 封装了很多层而已 ^_^.)。
- runtime
这里针对字典转模型 KVC 实现,就不做详解了,如果你 对 KVC 详解使用或是实现原理 不是很清楚的,可以参考 实用「KVC 编码 & KVO 监听
字典转模型 Runtime 方式实现: 说明:下面这个示例,是考虑三种情况包含在内的转换示例,具体可以看图上的注解
1、runtime 字典转模型 --> 字典的 key** 和模型的属性不匹配「模型属性数量大于字典键值对数」**,这种情况处理如下:
- // Runtime:根据模型中属性,去字典中取出对应的value给模型属性赋值
- // 思路:遍历模型中所有属性->使用运行时
- + (instancetype)modelWithDict:(NSDictionary *)dict
- {
- // 1.创建对应的对象
- id objc = [[self alloc] init];
- // 2.利用runtime给对象中的属性赋值
- /**
- class_copyIvarList: 获取类中的所有成员变量
- Ivar:成员变量
- 第一个参数:表示获取哪个类中的成员变量
- 第二个参数:表示这个类有多少成员变量,传入一个Int变量地址,会自动给这个变量赋值
- 返回值Ivar *:指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到。
- count: 成员变量个数
- */
- unsigned int count = 0;
- // 获取类中的所有成员变量
- Ivar *ivarList = class_copyIvarList(self, &count);
- // 遍历所有成员变量
- for (int i = 0; i < count; i++) {
- // 根据角标,从数组取出对应的成员变量
- Ivar ivar = ivarList[i];
- // 获取成员变量名字
- NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
- // 处理成员变量名->字典中的key(去掉 _ ,从第一个角标开始截取)
- NSString *key = [ivarName substringFromIndex:1];
- // 根据成员属性名去字典中查找对应的value
- id value = dict[key];
- // 【如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil】
- // 而报错 (could not set nil as the value for the key age.)
- if (value) {
- // 给模型中属性赋值
- [objc setValue:value forKey:key];
- }
- }
- return objc;
- }
注:
这里在获取模型类中的所有属性名,是采取
先获取成员变量(
- class_copyIvarList
) ,然后再处理成员变量名 -> 字典中的 key(
- 以下划线开头
) 得到属性名。
- 去掉 _ ,从第一个角标开始截取
原因:
,
- Ivar:成员变量,以下划线开头
获取类里面属性
- Property 属性
获取类中的所有成员变量
- class_copyPropertyList
- class_copyIvarList
- {
- int _a; // 成员变量
- }
- @property (nonatomic, assign) NSInteger attitudes_count; // 属性
- 这里有成员变量,就不会漏掉属性;如果有属性,可能会漏掉成员变量;
使用
字典转模型获取模型属性名的时候,最好获取成员属性名
- runtime
因为可能会有个属性是没有
- Ivar
和 ``getter 方法的。
- setter
2、runtime 字典转模型 --> 模型中嵌套模型「模型属性是另外一个模型对象」,这种情况处理如下:
- + (instancetype)modelWithDict2:(NSDictionary *)dict
- {
- // 1.创建对应的对象
- id objc = [[self alloc] init];
- // 2.利用runtime给对象中的属性赋值
- unsigned int count = 0;
- // 获取类中的所有成员变量
- Ivar *ivarList = class_copyIvarList(self, &count);
- // 遍历所有成员变量
- for (int i = 0; i < count; i++) {
- // 根据角标,从数组取出对应的成员变量
- Ivar ivar = ivarList[i];
- // 获取成员变量名字
- NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
- // 获取成员变量类型
- NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
- // 替换: @\"User\" -> User
- ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
- ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
- // 处理成员属性名->字典中的key(去掉 _ ,从第一个角标开始截取)
- NSString *key = [ivarName substringFromIndex:1];
- // 根据成员属性名去字典中查找对应的value
- id value = dict[key];
- //--------------------------- <#我是分割线#> ------------------------------//
- //
- // 二级转换:如果字典中还有字典,也需要把对应的字典转换成模型
- // 判断下value是否是字典,并且是自定义对象才需要转换
- if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
- // 字典转换成模型 userDict => User模型, 转换成哪个模型
- // 根据字符串类名生成类对象
- Class modelClass = NSClassFromString(ivarType);
- if (modelClass) { // 有对应的模型才需要转
- // 把字典转模型
- value = [modelClass modelWithDict2:value];
- }
- }
- // 给模型中属性赋值
- if (value) {
- [objc setValue:value forKey:key];
- }
- }
- return objc;
- }
3、runtime 字典转模型 --> 数组中装着模型「模型的属性是一个数组,数组中是字典模型对象」,这种情况处理如下:
- // Runtime:根据模型中属性,去字典中取出对应的value给模型属性赋值
- // 思路:遍历模型中所有属性->使用运行时
- + (instancetype)modelWithDict3:(NSDictionary *)dict
- {
- // 1.创建对应的对象
- id objc = [[self alloc] init];
- // 2.利用runtime给对象中的属性赋值
- unsigned int count = 0;
- // 获取类中的所有成员变量
- Ivar *ivarList = class_copyIvarList(self, &count);
- // 遍历所有成员变量
- for (int i = 0; i < count; i++) {
- // 根据角标,从数组取出对应的成员变量
- Ivar ivar = ivarList[i];
- // 获取成员变量名字
- NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
- // 处理成员属性名->字典中的key(去掉 _ ,从第一个角标开始截取)
- NSString *key = [ivarName substringFromIndex:1];
- // 根据成员属性名去字典中查找对应的value
- id value = dict[key];
- //--------------------------- <#我是分割线#> ------------------------------//
- //
- // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
- // 判断值是否是数组
- if ([value isKindOfClass:[NSArray class]]) {
- // 判断对应类有没有实现字典数组转模型数组的协议
- // arrayContainModelClass 提供一个协议,只要遵守这个协议的类,都能把数组中的字典转模型
- if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
- // 转换成id类型,就能调用任何对象的方法
- id idSelf = self;
- // 获取数组中字典对应的模型
- NSString *type = [idSelf arrayContainModelClass][key];
- // 生成模型
- Class classModel = NSClassFromString(type);
- NSMutableArray *arrM = [NSMutableArray array];
- // 遍历字典数组,生成模型数组
- for (NSDictionary *dict in value) {
- // 字典转模型
- id model = [classModel modelWithDict3:dict];
- [arrM addObject:model];
- }
- // 把模型数组赋值给value
- value = arrM;
- }
- }
- // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil,而报错
- if (value) {
- // 给模型中属性赋值
- [objc setValue:value forKey:key];
- }
- }
- return objc;
- }
总结:我们既然能获取到属性类型,那就可以拦截到模型的那个数组属性,进而对数组中每个模型遍历并字典转模型,但是我们不知道数组中的模型都是什么类型,我们可以声明一个方法,该方法目的不是让其调用,而是让其实现并返回模型的类型。
这里提到的你如果不是很清楚,建议参考我的 Demo,重要的部分代码中都有相应的注解和文字打印,运行程序可以很直观的表现。
应用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
注解:OC 中我们很习惯的会用懒加载,当用到的时候才去加载它,但是实际上只要一个类实现了某个方法,就会被加载进内存。当我们不想加载这么多方法的时候,就会使用到
动态的添加方法。
- runtime
需求:runtime 动态添加方法处理调用一个未实现的方法 和 去除报错。
案例代码:方法 + 调用 + 打印输出
- - (void)viewDidLoad {
- [super viewDidLoad];
- Person *p = [[Person alloc] init];
- // 默认person,没有实现run:方法,可以通过performSelector调用,但是会报错。
- // 动态添加方法就不会报错
- [p performSelector:@selector(run:) withObject:@10];
- }
- @implementation Person
- // 没有返回值,1个参数
- // void,(id,SEL)
- void aaa(id self, SEL _cmd, NSNumber *meter) {
- NSLog(@"跑了%@米", meter);
- }
- // 任何方法默认都有两个隐式参数,self,_cmd(当前方法的方法编号)
- // 什么时候调用:只要一个对象调用了一个未实现的方法就会调用这个方法,进行处理
- // 作用:动态添加方法,处理未实现
- + (BOOL)resolveInstanceMethod:(SEL)sel
- {
- // [NSStringFromSelector(sel) isEqualToString:@"run"];
- if (sel == NSSelectorFromString(@"run:")) {
- // 动态添加run方法
- // class: 给哪个类添加方法
- // SEL: 添加哪个方法,即添加方法的方法编号
- // IMP: 方法实现 => 函数 => 函数入口 => 函数名(添加方法的函数实现(函数地址))
- // type: 方法类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
- class_addMethod(self, sel, (IMP)aaa, "v@:@");
- return YES;
- }
- return [super resolveInstanceMethod:sel];
- }
- @end
- // 打印输出
- 2017-02-17 19:05:03.917 runtime[12761:543574] runtime动态添加方法--跑了10米
如果你实现过自定义模型数据持久化的过程,那么你也肯定明白,如果一个模型有许多个属性,那么我们需要对每个属性都实现一遍
和
- encodeObject
方法,如果这样的模型又有很多个,这还真的是一个十分麻烦的事情。下面来看看简单的实现方式。
- decodeObjectForKey
假设现在有一个
类,有 3 个属性。先看下 .h 文件
- Movie
- // Movie.h文件
- //1. 如果想要当前类可以实现归档与反归档,需要遵守一个协议NSCoding
- @interface Movie : NSObject<NSCoding>
- @property (nonatomic, copy) NSString *movieId;
- @property (nonatomic, copy) NSString *movieName;
- @property (nonatomic, copy) NSString *pic_url;
- @end
如果是正常写法, .m 文件应该是这样的:
- // Movie.m文件
- @implementation Movie
- - (void)encodeWithCoder:(NSCoder *)aCoder
- {
- [aCoder encodeObject:_movieId forKey:@"id"];
- [aCoder encodeObject:_movieName forKey:@"name"];
- [aCoder encodeObject:_pic_url forKey:@"url"];
- }
- - (id)initWithCoder:(NSCoder *)aDecoder
- {
- if (self = [super init]) {
- self.movieId = [aDecoder decodeObjectForKey:@"id"];
- self.movieName = [aDecoder decodeObjectForKey:@"name"];
- self.pic_url = [aDecoder decodeObjectForKey:@"url"];
- }
- return self;
- }
- @end
如果这里有 100 个属性,那么我们也只能把 100 个属性都给写一遍吗。
不过你会使用
后,这里就有更简便的方法,如下。
- runtime
- #import "Movie.h"
- #import <objc/runtime.h>
- @implementation Movie
- - (void)encodeWithCoder:(NSCoder *)encoder
- {
- unsigned int count = 0;
- Ivar *ivars = class_copyIvarList([Movie class], &count);
- for (int i = 0; i<count; i++) {
- // 取出i位置对应的成员变量
- Ivar ivar = ivars[i];
- // 查看成员变量
- const char *name = ivar_getName(ivar);
- // 归档
- NSString *key = [NSString stringWithUTF8String:name];
- id value = [self valueForKey:key];
- [encoder encodeObject:value forKey:key];
- }
- free(ivars);
- }
- - (id)initWithCoder:(NSCoder *)decoder
- {
- if (self = [super init]) {
- unsigned int count = 0;
- Ivar *ivars = class_copyIvarList([Movie class], &count);
- for (int i = 0; i<count; i++) {
- // 取出i位置对应的成员变量
- Ivar ivar = ivars[i];
- // 查看成员变量
- const char *name = ivar_getName(ivar);
- // 归档
- NSString *key = [NSString stringWithUTF8String:name];
- id value = [decoder decodeObjectForKey:key];
- // 设置到成员变量身上
- [self setValue:value forKey:key];
- }
- free(ivars);
- }
- return self;
- }
- @end
这样的方式实现,不管有多少个属性,写这几行代码就搞定了。怎么,代码有点多, 好说下面看看更加简便的方法:两句代码搞定。
- #import "Movie.h"
- #import <objc/runtime.h>
- #define encodeRuntime(A) \
- \
- unsigned int count = 0;\
- Ivar *ivars = class_copyIvarList([A class], &count);\
- for (int i = 0; i<count; i++) {\
- Ivar ivar = ivars[i];\
- const char *name = ivar_getName(ivar);\
- NSString *key = [NSString stringWithUTF8String:name];\
- id value = [self valueForKey:key];\
- [encoder encodeObject:value forKey:key];\
- }\
- free(ivars);\
- \
- #define initCoderRuntime(A) \
- \
- if (self = [super init]) {\
- unsigned int count = 0;\
- Ivar *ivars = class_copyIvarList([A class], &count);\
- for (int i = 0; i<count; i++) {\
- Ivar ivar = ivars[i];\
- const char *name = ivar_getName(ivar);\
- NSString *key = [NSString stringWithUTF8String:name];\
- id value = [decoder decodeObjectForKey:key];\
- [self setValue:value forKey:key];\
- }\
- free(ivars);\
- }\
- return self;\
- \
- @implementation Movie
- - (void)encodeWithCoder:(NSCoder *)encoder
- {
- encodeRuntime(Movie)
- }
- - (id)initWithCoder:(NSCoder *)decoder
- {
- initCoderRuntime(Movie)
- }
- @end
优化:上面是
和
- encodeWithCoder
这两个方法抽成宏。我们可以把这两个宏单独放到一个文件里面,这里以后需要进行数据持久化的模型都可以直接使用这两个宏。
- initWithCoder
下面是 runtime 下 Class 的常见方法 及 带有使用示例代码。各项操作,【转载原著】http://www.jianshu.com/p/46dd81402f63
unsigned int count;
- objc_property_t *propertyList = class_copyPropertyList([self class], &count);
- for (unsigned int i=0; i<count; i++) {
- const char *propertyName = property_getName(propertyList[i]);
- NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
- }
- Method *methodList = class_copyMethodList([self class], &count);
- for (unsigned int i; i<count; i++) {
- Method method = methodList[i];
- NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
- }
- Ivar *ivarList = class_copyIvarList([self class], &count);
- for (unsigned int i; i<count; i++) {
- Ivar myIvar = ivarList[i];
- const char *ivarName = ivar_getName(myIvar);
- NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
- }
- __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
- for (unsigned int i; i<count; i++) {
- Protocol *myProtocal = protocolList[i];
- const char *protocolName = protocol_getName(myProtocal);
- NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
- }
现在有一个 Person 类,和 person 创建的 xiaoming 对象, 有 test1 和 test2 两个方法
- Class PersonClass = object_getClass([Person class]);
- SEL oriSEL = @selector(test1);
- Method oriMethod = _class_getMethod(xiaomingClass, oriSEL);
- Class PersonClass = object_getClass([xiaoming class]);
- SEL oriSEL = @selector(test2);
- Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
- BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
- class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
- method_exchangeImplementations(oriMethod, cusMethod);
以上的几种方法应该算是
在实际场景中所应用的大部分的情况了,平常的编码中差不多足够用了。 这里在对
- runtime
几个参数概念,做一简单说明
- runtime
1、objc_msgSend 这是个最基本的用于发送消息的函数。 其实编译器会根据情况在
,
- objc_msgSend
,,
- objc_msgSend_stret
, 或
- objc_msgSendSuper
四个方法中选择一个来调用。如果消息是传递给超类,那么会调用名字带有
- objc_msgSendSuper_stret
的函数;如果消息返回值是数据结构而不是简单值时,那么会调用名字带有
- Super
的函数。
- stret
2、SEL
函数第二个参数类型为
- objc_msgSend
,它是
- SEL
在 Objc 中的表示类型(Swift 中是 Selector 类)。
- selector
是方法选择器,可以理解为区分方法的
- selector
,而这个
- ID
的数据结构是
- ID
:
- SEL
其实它就是个映射到方法的 C 字符串,你可以用 Objc 编译器命令
- typedef struct objc_selector *SEL;
系统的
- @selector()``或者 Runtime
函数来获得一个
- sel_registerName
类型的方法选择器。
- SEL
3、id
第一个参数类型为
- objc_msgSend
,大家对它都不陌生,它是一个指向类实例的指针:
- id
那
- typedef struct objc_object *id;
又是啥呢:
- objc_object
- struct objc_object { Class isa; };
结构体包含一个
- objc_object
指针,根据
- isa
指针就可以顺藤摸瓜找到对象所属的类。
- isa
4、runtime.h 里 Class 的定义
- struct objc_class {
- Class isa OBJC_ISA_AVAILABILITY;//每个Class都有一个isa指针
- #if !__OBJC2__
- Class super_class OBJC2_UNAVAILABLE;//父类
- const char *name OBJC2_UNAVAILABLE;//类名
- long version OBJC2_UNAVAILABLE;//类版本
- long info OBJC2_UNAVAILABLE;//!*!供运行期使用的一些位标识。如:CLS_CLASS (0x1L)表示该类为普通class; CLS_META(0x2L)表示该类为metaclass等(runtime.h中有详细列出)
- long instance_size OBJC2_UNAVAILABLE;//实例大小
- struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;//存储每个实例变量的内存地址
- struct objc_method_list **methodLists OBJC2_UNAVAILABLE;//!*!根据info的信息确定是类还是实例,运行什么函数方法等
- struct objc_cache *cache OBJC2_UNAVAILABLE;//缓存
- struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;//协议
- #endif
- } OBJC2_UNAVAILABLE;
可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议。 在
结构体中:`
- objc_class
objc_ivar_list
- ivars是
methodLists
- 指针;
objc_method_list
- 是指向
*methodLists
- 指针的指针。也就是说可以动态修改
Category` 实现的原理。
- 的值来添加成员方法,这也是
上面讲到的所有东西都在 Demo 里,如果你感觉这样难以理解,那强烈建议你下载 Demo ,运行代码加上文字注解,效果会更好,如果你觉得不错,还请为我的 Demo star 一个。
中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是
- Objective-C
的名字。利用
- selector
的动态特性,可以实现在运行时偷换
- Objective-C
对应的方法实现,达到给方法挂钩的目的
- selector
的本质其实就是方法名,
- selector
有点类似函数指针,指向具体的
- IMP
实现,通过
- Method
就可以找到对应的
- selector
。
- IMP
交换两个方法的实现
- method_exchangeImplementations
替换方法的实现
- class_replaceMethod
来直接设置某个方法的
- method_setImplementation
。
- IMP
这里可以参考简友这篇:【Runtime Method Swizzling 开发实例汇总】http://www.jianshu.com/p/f6dad8e1b848
这里可以参考权威这篇:OC 运行时黑魔法 Method Swizzling
下面的代码输出什么
- @implementation Son : NSObject
- - (id)init
- {
- self = [super init];
- if (self) {
- NSLog(@"%@", NSStringFromClass([self class]));
- NSLog(@"%@", NSStringFromClass([super class]));
- }
- return self;
- }
- @end
先思考一下,会打印出来什么❓ 关注我的更多干货分享 ^_^.
答案:都输出 Son
获取当前方法的调用者的类,
- class
获取当前方法的调用者的父类,
- superClass
仅仅是一个编译指示器,就是给编译器看的,不是一个指针。
- super
这个标志,就会让当前对象去调用父类方法,本质还是当前对象在调用
- super
这个题目主要是考察关于
中对
- objc
和
- self
的理解:
- super
是类的隐藏参数,指向当前调用方法的这个类的实例。而
- self
本质是一个编译器标示符,和
- super
是指向的同一个消息接受者
- self
调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;
- self
时,则从父类的方法列表中开始找。然后调用父类的这个方法
- super
时,会转化成
- [self class]
函数
- objc_msgSend
- id objc_msgSend(id self, SEL op, ...)
- - 调用 `[super class]`时,会转化成 `objc_msgSendSuper` 函数.
- id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
- 第一个参数是 objc_super 这样一个结构体,其定义如下
- struct objc_super {
- __unsafe_unretained id receiver;
- __unsafe_unretained Class super_class;
- };
- 第一个成员是 receiver, 类似于上面的 objc_msgSend函数第一个参数self
- 第二个成员是记录当前类的父类是什么,告诉程序从父类中开始找方法,找到方法后,最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))去调用, 此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son
- objc Runtime 开源代码对- (Class)class方法的实现
- -(Class)class { return object_getClass(self);
- }
温馨提示: 更多有关本文系统文件的属性和方法及常用功能代码案例,请移步这里【UIKit 框架分类简明介绍】← ️→【GitHub 工程地址】
来源: http://www.cnblogs.com/Plainboiledwater/p/jianshu-baikaishuiln.html