什么是Runtime? Runtime又叫运行时,是一套底层C语言的API,其为iOS内部的核心之一,我们平时编写的OC代码底层都是基于它来实现的。比如:[target doSomething];底层运行时会被编译器转化成objc_msgSend(target,@selector(doSomething)); 带参数的[target doSomething:arg1...]; 会被底层运行时会被编译器转化成objc_msgSend(target, @selector(doSomething), arg1, arg2, ...); 以上你可能看不出它的价值,但是我们需要了解的是 Objective-C 是一门动态语言,它会将一些工作放在代码运行时才处理而并非编译时。也就是说,有很多类和成员变量在我们编译的时是不知道的,而在运行时,我们所编写的代码会转换成完整的确定的代码运行。因此,编译器是不够的,我们还需要一个运行时系统(Runtime system)来处理编译后的代码。Runtime 基本是用 C 和汇编写的,由此可见苹果为了动态系统的高效而做出的努力。苹果和 GNU 各自维护一个开源的 Runtime 版本,这两个版本之间都在努力保持一致。 点击这里下载苹果维护的开源代码。 Runtime 的作用 Objc 在三种层面上与 Runtime 系统进行交互: 1 通过 Objective-C 源代码 2 通过 Foundation 框架的 NSObject 类定义的方法 3 通过对 Runtime 库函数的直接调用 Objective-C 源代码 多数情况我们只需要编写 OC 代码即可,Runtime 系统自动在幕后搞定一切,还记得简介中如果我们调用方法,编译器会将 OC 代码转换成运行时代码,在运行时确定数据结构和函数。 通过 Foundation 框架的 NSObject 类定义的方法 Cocoa 程序中绝大部分类都是 NSObject 类的子类,所以都继承了 NSObject 的行为。(NSProxy 类时个例外,它是个抽象超类) 一些情况下,NSObject 类仅仅定义了完成某件事情的模板,并没有提供所需要的代码。例如 -description 方法,该方法返回类内容的字符串表示,该方法主要用来调试程序。NSObject 类并不知道子类的内容,所以它只是返回类的名字和对象的地址,NSObject 的子类可以重新实现。 还有一些 NSObject 的方法可以从 Runtime 系统中获取信息,允许对象进行自我检查。例如: • -class方法返回对象的类; • -isKindOfClass: 和 -isMemberOfClass: 方法检查对象是否存在于指定的类的继承体系中(是否是其子类或者父类或者当前类的成员变量); • -respondsToSelector: 检查对象能否响应指定的消息; • -conformsToProtocol:检查对象是否实现了指定协议类的方法; • -methodForSelector: 返回指定方法实现的地址。 通过对 Runtime 库函数的直接调用 Runtime 系统是具有公共接口的动态共享库。头文件存放于/usr/include/objc目录下,这意味着我们使用时只需要引入objc/Runtime.h头文件即可。 许多函数可以让你使用纯 C 代码来实现 Objc 中同样的功能。除非是写一些 Objc 与其他语言的桥接或是底层的 debug 工作,你在写 Objc 代码时一般不会用到这些 C 语言函数。对于公共接口都有哪些,后面会讲到。我将会参考苹果官方的 API 文档。 OC中一切都被设计成了对象,我们都知道一个类被初始化成一个实例,这个实例是一个对象。实际上一个类本质上也是一个对象,在runtime中用结构体表示。 相关的定义: /// 描述类中的一个方法 typedef struct objc_method *Method;

/// 实例变量 typedef struct objc_ivar *Ivar;

/// 类别Category typedef struct objc_category *Category;

/// 类中声明的属性 typedef struct objc_property *objc_property_t; • 类在runtime中的表示 //类在runtime中的表示 struct objc_class { Class isa;//指针,顾名思义,表示是一个什么, //实例的isa指向类对象,类对象的isa指向元类

#if !OBJC2 Class super_class; //指向父类 const char *name; //类名 long version; long info; long instance_size struct objc_ivar_list *ivars //成员变量列表 struct objc_method_list **methodLists; //方法列表 struct objc_cache *cache;//缓存 //一种优化,调用过的方法存入缓存列表,下次调用先找缓存 struct objc_protocol_list protocols //协议列表 #endif } OBJC2_UNAVAILABLE; / Use Class instead of struct objc_class * */ 获取列表 有时候会有这样的需求,我们需要知道当前类中每个属性的名字(比如字典转模型,字典的Key和模型对象的属性名字不匹配)。 我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)。 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]);
}
复制代码

在Xcode上跑一下看看输出吧,需要给你当前的类写几个属性,成员变量,方法和协议,不然获取的列表是没有东西的。 注意,调用这些获取列表的方法别忘记导入头文件#import <objc/runtime.h>。 方法调用 让我们看一下方法调用在运行时的过程(参照前文类在runtime中的表示) 如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。 如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。

  1. 首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行。
  2. 如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行
  3. 如果没找到,去父类指针所指向的对象中执行1,2.
  4. 以此类推,如果一直到根类还没找到,转向拦截调用。
  5. 如果没有重写拦截调用的方法,程序报错。 以上的过程给我带来的启发: • 重写父类的方法,并没有覆盖掉父类的方法,只是在当前类对象中找到了这个方法后就不会再去父类中找了。 • 如果想调用已经重写过的方法的父类的实现,只需使用super这个编译器标识,它会在运行时跳过在当前的类对象中寻找方法的过程。 拦截调用 在方法调用中说到了,如果没有找到方法就会转向拦截调用。 那么什么是拦截调用呢。 拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。
  • (BOOL)resolveClassMethod:(SEL)sel;
  • (BOOL)resolveInstanceMethod:(SEL)sel; //后两个方法需要转发到其他的类处理
  • (id)forwardingTargetForSelector:(SEL)aSelector;
  • (void)forwardInvocation:(NSInvocation *)anInvocation; • 第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。 • 第二个方法和第一个方法相似,只不过处理的是实例方法。 • 第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。 • 第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。 动态添加方法 重写了拦截调用的方法并且返回了YES,我们要怎么处理呢? 有一个办法是根据传进来的SEL类型的selector动态添加一个方法。 首先从外部隐式调用一个不存在的方法: //隐式调用方法 [target performSelector:@selector(resolveAdd:) withObject:@"test"]; 然后,在target对象内部重写拦截调用的方法,动态添加方法。 void runAddMethod(id self, SEL _cmd, NSString *string){ NSLog(@"add C IMP ", string); }
  • (BOOL)resolveInstanceMethod:(SEL)sel{

    //给本类动态添加一个方法 if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) { class_addMethod(self, sel, (IMP)runAddMethod, "v@:*"); } return YES; } 其中class_addMethod的四个参数分别是:

  1. Class cls 给哪个类添加方法,本例中是self
  2. SEL name 添加的方法,本例中是重写的拦截调用传进来的selector。
  3. IMP imp 方法的实现,C方法的方法实现可以直接获得。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。
  4. "v@:*"方法的签名,代表有一个参数的方法。 关联对象 现在你准备用一个系统的类,但是系统的类并不能满足你的需求,你需要额外添加一个属性。 这种情况的一般解决办法就是继承。 但是,只增加一个属性,就去继承一个类,总是觉得太麻烦类。 这个时候,runtime的关联属性就发挥它的作用了。 //首先定义一个全局变量,用它的地址作为关联对象的key static char associatedObjectKey; //设置关联对象 objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串属性", OBJC_ASSOCIATION_RETAIN_NONATOMIC); //获取关联对象 NSString *string = objc_getAssociatedObject(target, &associatedObjectKey); NSLog(@"AssociatedObject = %@", string); objc_setAssociatedObject的四个参数:
  5. id object给谁设置关联对象。
  6. const void *key关联对象唯一的key,获取时会用到。
  7. id value关联对象。
  8. objc_AssociationPolicy关联策略,有以下几种策略: enum { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403 }; 如果你熟悉OC,看名字应该知道这几种策略的意思了吧。 objc_getAssociatedObject的两个参数。
  9. id object获取谁的关联对象。
  10. const void *key根据这个唯一的key获取关联对象。 其实,你还可以把添加和获取关联对象的方法写在你需要用到这个功能的类的类别中,方便使用。 //添加关联对象
  • (void)addAssociatedObject:(id)object{ objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } //获取关联对象
  • (id)getAssociatedObject{ return objc_getAssociatedObject(self, _cmd); } 注意:这里面我们把getAssociatedObject方法的地址作为唯一的key,_cmd代表当前调用方法的地址。 方法交换 方法交换,顾名思义,就是将两个方法的实现交换。例如,将A方法和B方法交换,调用A方法的时候,就会执行B方法中的代码,反之亦然。 话不多说,这是参考Mattt大神在NSHipster上的文章自己写的代码。 #import "UIViewController+swizzling.h" #import <objc/runtime.h>

@implementation UIViewController (swizzling)

//load方法会在类第一次加载的时候被调用 //调用的时间比较靠前,适合在这个方法里做方法交换

  • (void)load{ //方法交换应该被保证,在程序中只会执行一次 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{

      //获得viewController的生命周期方法的selectorSEL systemSel = @selector(viewWillAppear:);//自己实现的将要被交换的方法的selectorSEL swizzSel = @selector(swiz_viewWillAppear:);//两个方法的MethodMethod systemMethod = class_getInstanceMethod([self class], systemSel);Method swizzMethod = class_getInstanceMethod([self class], swizzSel);//首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));if (isAdd) {//如果成功,说明类中不存在这个方法的实现//将被交换方法的实现替换到这个并不存在的实现class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));}else{//否则,交换两个方法的实现method_exchangeImplementations(systemMethod, swizzMethod);}
    复制代码

    }); }

  • (void)swiz_viewWillAppear:(BOOL)animated{ //这时候调用自己,看起来像是死循环 //但是其实自己的实现已经被替换了 [self swiz_viewWillAppear:animated]; NSLog(@"swizzle"); }

@end 在一个自己定义的viewController中重写viewWillAppear

  • (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; NSLog(@"viewWillAppear"); } Run起来看看输出吧! 我的理解: • 方法交换对于我来说更像是实现一种思想的最佳技术:AOP面向切面编程。 • 既然是切面,就一定不要忘记,交换完再调回自己。 • 一定要保证只交换一次,否则就会很乱。 • 最后,据说这个技术很危险,谨慎使用

转载于:https://juejin.im/post/5a308a075188252ae93aed12

对Runtime的理解相关推荐

  1. ios runtime重要性_iOS:学习runtime的理解和心得

    作者:兴宇是谁 授权本站转载. Runtime是想要做好iOS开发,或者说是真正的深刻的掌握OC这门语言所必需理解的东西.最近在学习Runtime,有自己的一些心得,整理如下, 一为 查阅方便 二为 ...

  2. Golang中的runtime.Caller理解

    func Caller(skip int) (pc uintptr, file string, line int, ok bool) 参数:skip是要提升的堆栈帧数,0-当前函数,1-上一层函数,. ...

  3. YYModel底层解析- Runtime

    这段时间一直在忙新的需求,没有时间来整理代码,发表自己技术博客,今天我们来看一下YYModel的底层解析以及如何使用,希望对大家有所帮助! 一 概述 概括 YYModel是一个轻量级的JSON模型转换 ...

  4. 快过年了,为过完年跳槽的人准备一份面试题

    设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的事情. 1). MVC模式:Model View Control,把模型 视图 控制器 ...

  5. iOS 问题整理04----Runtime

    本篇文章主要解决以下问题 说说你对 runtime 的理解. 你了解 isa 指针吗? 类的结构是怎样的? class_rw_t 与 class_ro_t 的区别? runtime 中,SEL 和 I ...

  6. iOS面试知识点梳理

    1.iOS开发者账号类型 "个人"开发者可以申请升级"公司",可以通过拨打苹果公司客服电话(400 6701 855)来咨询和办理. 公司账号允许多个开发者进行 ...

  7. Eevnt Loop (事件循环)

    javascript是一门单线程的非阻塞的脚本语言.单线程意味着javascript在执行代码的任何时候,都只有一个主线程来处理所有的任务.非阻塞:是当代码需要进行一项异步任务(无法立刻返回结果,需要 ...

  8. 今日头条面试——iOS开发岗

    目录 一面 二面 三面 一面 1.自我介绍 2.项目相关 3.怎么自定义导航跳转 4.谈谈runtime的理解 5.KVC的用途 6.使用method swizzling要注意什么?(进行版本迭代的时 ...

  9. 今日头条面试——iOS开发面试题

    目录 一面 二面 三面 一面 * 1.自我介绍 * 2.项目相关 * 3.怎么自定义导航跳转 * 4.谈谈runtime的理解 * 5.KVC的用途 * 6.使用method swizzling要注意 ...

  10. 一个iOS程序员的BAT面试经验

    转载于:http://www.techug.com/ios-bat-interview 随着各大公司春招的开始,很多小伙伴都行动起来了,我有幸能够加入百度并和大家分享自己的经验心得.由于我面试的都是比 ...

最新文章

  1. 专访阿里达摩院聂再清:不能让每个人无差别享受AI,是程序员的耻辱
  2. Linux eval命令
  3. win定时关机_怎么让电脑定时关机,有多种办法
  4. netflix 数据科学家_数据科学和机器学习在Netflix中的应用
  5. 数据结构中的逻辑结构简介
  6. 字扩展、位扩展、字位同时扩展
  7. Rust 逆袭!位列 Stack Overflow 2018 最受欢迎编程语言榜首
  8. opengl 矩阵投影代码 shade_LookAt、Viewport、Perspective矩阵
  9. 魔域私服怎么老服务器中断,魔域私服技术文章-服务器端比较正确的数据库解释文件...
  10. 文献管理三剑客之noteexpress endnote 资源和论文的搬迁备份
  11. 21天学通C语言-学习笔记(12)
  12. WinAPI WinMain函数
  13. 「 微信黑科技 」神奇符号
  14. phalapi做登录检测_PhalApi:[1.22] 签名验证:自定义签名规则
  15. allegro 04_B class和subclass介绍
  16. 编译原理复习 第一章 概述
  17. 如何把图片上的文字转换成可编辑的文档文字?
  18. element之el-scrollbar
  19. 关于Wrap Lighting与皮肤SSS
  20. 6月程序员平均工资出炉,这个水平我慕了!

热门文章

  1. html5的file api,HTML5 File API
  2. 452.用最少数量的箭引爆气球
  3. 2020 比特大陆 面经
  4. java实体类属性比较_实体类之间属性对比的方法
  5. 转置卷积 反卷积 PyTorch torch.nn.ConvTranspose2d() output_padding
  6. DoubleArrayTrie详解
  7. 编写爬虫遇到的问题总结
  8. sublime text3 错误解决
  9. Q-learning和Sarsa
  10. matlab课堂笔记,厦门大学matlab第四次课程笔记 PTB的简单讲解