前言

Runtime是iOS里面非常重要的基础知识,初次与它见面时,甚是懵懂,但没有关系,万事万物都是要由陌生到熟悉。学就完了。

正文

经常有小伙伴私下在Q上问一些关于Runtime的东西,问我有没有Runtime的相关博客,之前还真没正儿八经的总结过。之前只是在解析第三方框架源码时,聊过一些用法,也就是这些第三方框架中用到的Runtime。比如属性关联,动态获取属性等等。本篇博客就针对Runtime这个主题来总结一些其常用的一些方法.

本篇博客所聊的Runtime的内容大概有:动态获取类名、动态获取类的成员变量、动态获取类的属性列表、动态获取类的方法列表、动态获取类所遵循的协议列表、动态添加新的方法、类的实例方法实现的交换、动态属性关联、消息发送与消息转发机制等。当然,本篇博客总结的是运行时常用的功能,并不是所有Runtime的内容。

一、构建Runtime测试用例

本篇博客的内容是依托于实例的,所以我们在本篇博客中先构建我们的测试类,Runtime将会对该类进行相关的操作。下方就是本篇博客所涉及Demo的目录,上面的RuntimeKit类是讲Runtime常用的功能进行了简单的封装,而下方的TestClass以及相关的类目就是我们Runtime要操作的对象了。下方会对TestClass以及类目中的内容进行详细介绍。

下方的代码就是我们的测试类TestClass的主要部分,因为TestClass是专门用来测试的类,所以其涉及的内容要尽量的全面。TestClass遵循了NSCoding, NSCopying这两个协议,并且为其添加了公有属性、私有属性、私有成员变量、 公有实例方法、私有实例方法、类方法等。这些添加的内容,都将是我们Runtime的操作对象。下方那几个TestClass的类目稍后在使用Runtime时再进行介绍。TestClass.h@interface TestClass : NSObject @property (nonatomic, strong) NSArray *publicProperty1;@property (nonatomic, strong) NSString *publicProperty2; + (void)classMethod:(NSString *)value;- (void)publicTestMethod1:(NSString *)value1 Second:(NSString *)value2;- (void)publicTestMethod2; - (void)method1; @end TestClass.m@interface TestClass() { NSInteger _var1; int _var2; BOOL _var3; double _var4; float _var5;} @property (nonatomic, strong) NSMutableArray *privateProperty1;@property (nonatomic, strong) NSNumber *privateProperty2;@property (nonatomic, strong) NSDictionary *privateProperty3; @end @implementation TestClass + (void)classMethod: (NSString *)value { NSLog(@"publicTestMethod1");} - (void)privateTestMethod1 { NSLog(@"privateTestMethod1");} - (void)privateTestMethod2 { NSLog(@"privateTestMethod2");} #pragma mark - 方法交换时使用- (void)method1 { NSLog(@"我是Method1的实现");} @end

二、RuntimeKit的封装

接下来我们就来看看RuntimeKit中的内容,其中对Runtime常用的方法进行了简单的封装。主要是动态的获取类的一些属性和方法的,以及动态方法添加和方法交换的。本部分的干货还是不少的。

1、获取类名

动态的获取类名是比较简单的,使用class_getName(Class)就可以在运行时来获取类的名称。class_getName()函数返回的是一个char类型的指针,也就是C语言的字符串类型,所以我们要将其转换成NSString类型,然后再返回出去。下方的+fetchClassName:方法就是我们封装的获取类名的方法,如下所示:/** 获取类名 @param class 响应类 @return NSString 类名 */+ (NSString *)fetchClassName:(Class)class { const char *className = class_getName(class); return [NSString stringWithUTF8String:className];}

2、获取成员变量

下方这个+fetchIvarList:这个方法就是我们封装的获取类的成员变量的方法。当然我们在获取成员变量时,可以用ivar_getTypeEncoding()来获取相应成员变量的类型。使用ivar_getName()来获取相应成员变量的名称。下方就是对获取成员变量的功能的封装。返回的是一个数组,数组的元素是一个字典,而字典中存储的就是相应成员变量的名称和类型。/** 获取成员变量 @param class 响应类 @return NSArray 成员变量列表 */+ (NSArray *)fetchIvarList:(Class)class { unsigned int count = 0; Ivar *ivarList = class_copyIvarList(class, &count); NSMutableArray *mutableList = [[NSMutableArray alloc] initWithCapacity:count]; for (unsigned int i = 0; i < count; i++) { NSMutableDictionary *dic = [[NSMutableDictionary alloc] initWithCapacity:2]; const char *ivarName = ivar_getName(ivarList[i]); const char *ivarType = ivar_getTypeEncoding(ivarList[i]); dic[@"type"] = [NSString stringWithUTF8String:ivarType]; dic[@"ivarName"] = [NSString stringWithUTF8String:ivarName]; [mutableList addObject:dic]; } free(ivarList); return [NSArray arrayWithArray:mutableList];}

下方就是调用上述方法获取的TestClass类的成员变量。当然在运行时就没有什么私有和公有之分了,只要是成员变量就可以获取到。在OC中的给类添加成员属性其实就是添加了一个成员变量和getter以及setter方法。所以获取的成员列表中肯定带有成员属性,不过成员属性的名称前方添加了下划线来与成员属性进行区分。我们也可以获取成员变量的类型,下方的_var1是NSInteger类型,动态获取到的是q字母,其实是NSInteger的符号。而i就表示int类型,c表示Bool类型,d表示double类型,f则就表示float类型。当然这些基本类型都是由一个字母代替的,如果是引用类型的话,则直接就是一个字符串了,比如NSArray类型就是"@NSArray"。

3.获取成员属性

上面获取的是类的成员变量,那么下方这个+fetchPropertyList:获取的就是成员属性。当然此刻获取的只包括成员属性,也就是那些有setter或者getter方法的成员变量。下方主要是使用了class_copyPropertyList(Class,&count)来获取的属性列表,然后通过for循环通过property_getName()来获取每个属性的名字。当然使用property_getName()获取到的名字依然是C语言的char类型的指针,所以我们还需要将其转换成NSString类型,然后放到数组中一并返回。如下所示:/** 获取类的属性列表,包括私有和公有属性,以及定义在延展(extention)中的属性 @param class 响应类 @return NSArray 属性列表数组 */+ (NSArray *)fetchPropertyList:(Class)class { unsigned int count = 0; objc_property_t *propertyList = class_copyPropertyList(class, &count); NSMutableArray *mutableList = [[NSMutableArray alloc] initWithCapacity:count]; for (unsigned int i = 0; i < count; i++) { const char *propertyName = property_getName(propertyList[i]); [mutableList addObject:[NSString stringWithUTF8String:propertyName]]; } free(propertyList); return [NSArray arrayWithArray:mutableList];}

下方这个截图就是调用上述方法获取的TestClass的所有的属性,当然dynamicAddProperty是我们使用Runtime动态给TestClass添加的,所以也是可以获取到的。当然我们获取到的属性的名称为了与其对应的成员变量进行区分,成员属性的名字前边是没有下划线的。

4、获取类的实例方法

接下来我们就来封装一下获取类的实例方法列表的功能,下方这个+fetchMethodList:就是我们封装的获取类的实例方法列表的函数。在下方函数中,通过class_copyMethodList()方法获取类的实例方法列表,然后通过for循环使用method_getName()来获取每个方法的名称,然后将方法的名称转换成NSString类型,存储到数组中一并返回。具体代码如下所示:/** 获取类的实例方法列表:getter,setter,对象方法等,但不能获取类方法 @param class 响应类 @return NSArray 实例方法列表 */+ (NSArray *)fetchInstanceMethodList:(Class)class { unsigned int count = 0; Method *instanceMethodList = class_copyMethodList(class, &count); NSMutableArray *mutableList = [[NSMutableArray alloc] initWithCapacity:count]; for (unsigned int i = 0; i < count; i++) { Method method = instanceMethodList[i]; SEL methodName = method_getName(method); [mutableList addObject:NSStringFromSelector(methodName)]; } free(instanceMethodList); return [NSArray arrayWithArray:mutableList];}

下方这个截图就是上述方法在TestClass上运行的结果,其中打印了TestClass类的所有实例方法,当然其中也必须得包含成员属性的getter和setter方法。当然TestClass类目中的方法也是必须能获取到的。结果如下所示:

5、获取协议列表

下方是获取我们类所遵循协议列表的方法,主要使用了class_copyProtocolList()来获取列表,然后通过for循序使用protocol_getName()来获取协议的名称,最后将其转换成NSString类型放入数组中返回即可。/** 获取协议列表 @param class 响应类 @return NSArray 协议列表 */ + (NSArray *)fetchProtocolList:(Class)class { unsigned int count = 0; __unsafe_unretained Protocol **protocolList = class_copyProtocolList(class, &count); NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:count]; for (unsigned int i = 0; i < count; i++) { Protocol *protocol = protocolList[i]; const char *protocolName = protocol_getName(protocol); [mutableArray addObject:[NSString stringWithUTF8String:protocolName]]; } free(protocolList); return [NSArray arrayWithArray:mutableArray];}

下方就是我们获取到的TestClass类所遵循的协议列表:

6、动态添加方法实现

下方就是动态的往相应类上添加方法以及实现。下方的+addMethod方法有三个参数,第一个参数是要添加方法的类,第二个参数是方法的SEL,第三个参数则是提供方法实现的SEL。稍后在消息发送和消息转发时会用到下方的方法。下方主要是使用class_getInstanceMethod()和method_getImplementation()这两个方法相结合获取相应SEL的方法实现。下方的IMP其实就是Implementation的方法缩写,获取到相应的方法实现后,然后再调用class_addMethod()方法将IMP与SEL进行绑定即可。具体做法如下所示。/** 往类上添加新的方法与实现 @param class 响应类 @param methodSel 方法名 @param methodSelImpl 对应方法实现的方法名 */+ (void)addMethod:(Class)class method:(SEL)methodSel method:(SEL)methodSelImpl { Method method = class_getInstanceMethod(class, methodSelImpl); IMP methodIMP = method_getImplementation(method); const char *types = method_getTypeEncoding(method); class_addMethod(class, methodSel, methodIMP, types);}

7、方法实现交换

下方就是讲类的两个方法的实现进行交换。如果将MethodA与MethodB的方法实现进行交换的话,调用MethodA时就会执行MethodB的内容,反之亦然。/** 方法交换 @param class 响应类 @param oldMethod 被交换方法 @param newMethod 交换方法 */+ (void)exchangeMethod:(Class)class method:(SEL)oldMethod method:(SEL)newMethod { Method method1 = class_getInstanceMethod(class, oldMethod); Method method2 = class_getInstanceMethod(class, newMethod); method_exchangeImplementations(method1, method2);}

下方这段代码就是对上述方法的测试。下方是TestClass的一个类目,在该类目中将类目中的方法与TestClass中的方法进行了替换。也就是将method1与method2进行了替换,替换后在method2中调用的method2其实就是调用的method1。在第三方库中,经常会使用该特性,已达到AOP编程的目的。#import "TestClass+ExchangeMethod.h"#import "RuntimeKit.h" @implementation TestClass (ExchangeMethod) - (void)exchangeMethod { [RuntimeKit exchangeMethod:[self class] method:@selector(method1) method:@selector(method2)];} - (void)method2 { [self method2]; NSLog(@"可以在Method1基础上添加任何东西了");} @end

三、属性关联

属性关联说白了就是在类目中动态的为我们的类添加相应的属性,如果看过之前发布的对Masonry框架源码解析的博客的话,对下方的属性关联并不陌生。在Masonry框架中就利用Runtime的属性关联在UIView的类目中给UIView添加了一个约束数组,用来记录添加在当前View上的所有约束。下方就是在TestClass的类目中通过objc_getAssociatedObject()和objc_setAssociatedObject()两个方法为TestClass类添加了一个dynamicAddProperty属性。上面我们获取到的属性列表中就含有该动态添加的成员属性。

下方就是属性关联的具体代码,如下所示。#import "TestClass+AssociatedObject.h"#import "RuntimeKit.h" @implementation TestClass (AssociatedObject) #pragma mark - 动态属性关联static char kDynamicAddProperty; /** getter方法 @return NSString 关联属性的值 */- (NSString *)dynamicAddProperty { return objc_getAssociatedObject(self, &kDynamicAddProperty);} /** setter方法 @param dynamicAddProperty 动态添加属性 */ - (void)setDynamicAddProperty:(NSString *)dynamicAddProperty { objc_setAssociatedObject(self, &kDynamicAddProperty, dynamicAddProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);} @end

四、消息处理与消息转发

在Runtime中不得不提的就是OC的消息处理和消息转发机制。当然网上也有不少相关资料,本篇博客为了完整性,还是要聊一下消息处理与消息转发的。当你调用一个类的方法时,先在本类中的方法缓存列表中进行查询,如果在缓存列表中找到了该方法的实现,就执行,如果找不到就在本类中的方列表中进行查找。在本类方列表中查找到相应的方法实现后就进行调用,如果没找到,就去父类中进行查找。如果在父类中的方法列表中找到了相应方法的实现,那么就执行,否则就执行下方的几步。

当调用一个方法在缓存列表,本类中的方法列表以及父类的方法列表找不到相应的实现时,到程序崩溃阶段中间还会有几步让你来挽救。接下来就来看看这几步该怎么走。

1.消息处理(Resolve Method)

当在相应的类以及父类中找不到类方法实现时会执行+resolveInstanceMethod:这个类方法。该方法如果在类中不被重写的话,默认返回NO。如果返回NO就表明不做任何处理,走下一步。如果返回YES的话,就说明在该方法中对这个找不到实现的方法进行了处理。在该方法中,我们可以为找不到实现的SEL动态的添加一个方法实现,添加完毕后,就会执行我们添加的方法实现。这样,当一个类调用不存在的方法时,就不会崩溃了。具体做法如下所示://运行时方法拦截- (void)dynamicAddMethod:(NSString *)value { NSLog(@"OC替换的方法:%@",value);} #pragma mark - 消息处理/** 没有找到SEL的IMP执行以下方法 @param sel 当前对象调用并且找不到IMP的SEL @return BOOL 找到其他的执行方法时,返回YES */+ (BOOL)resolveInstanceMethod:(SEL)sel {// return NO; //默认返回NO,当返回NO的时候,解开注释,会接着执行

ios nslog 例子_iOS Runtime常用示例总结相关推荐

  1. ios nslog 例子_IOS 打印 NSlog 使用

    NSLog的定义 void NSLog(NSString *format, -); 基本上,NSLog很像printf,同样会在console中输出显示结果.不同的是,传递进去的格式化字符是NSStr ...

  2. ios nslog 例子_iOS Block实例

    iOS之Block详解:Block详解 ViewController.h(ARC) #import @interface ViewController : UIViewController // 属性 ...

  3. ios nslog 例子_iOS 典型内存泄露案例 - zhenshan2013的个人空间 - 51Testing软件测试网 51Testing软件测试网-软件测试人的精神家园...

    最近进行iOS 安全黑匣子的测试,在Demo中通过不断的点击调加密接口,同时通过苹果自带instrument的leak工具监控,发现典型的内存泄漏,监控图如下: 上图中红色的部分表示该操作触发的代码有 ...

  4. iOS开发之Runtime常用示例总结

    深度好文,转载自:https://github.com/lizelu/ObjCRuntimeDemo 经常有小伙伴私下在Q上问一些关于Runtime的东西,问我有没有Runtime的相关博客,之前还真 ...

  5. iOS 开发:『Runtime』详解(二)Method Swizzling

    本文用来介绍 iOS 开发中『Runtime』中的黑魔法Method Swizzling. 通过本文,您将了解到: Method Swizzling(动态方法交换)简介 Method Swizzlin ...

  6. iOS模式详解runtime面试工作

    简书:http://www.jianshu.com/p/19f280afcb24 对于从事 iOS 开发人员来说,所有的人都会答出「runtime 是运行时」,什么情况下用runtime?,大部分人能 ...

  7. IOS小技巧–用runtime 解决UIButton 重复点击问题

    IOS小技巧–用runtime 解决UIButton 重复点击问题 什么是这个问题 我们的按钮是点击一次响应一次, 即使频繁的点击也不会出问题, 可是某些场景下还偏偏就是会出问题. 通常是如何解决 我 ...

  8. IOS基础之Foundation框架常用类NSFileManager,DSDate,CGPoint,CGSize,copy,单例

    IOS基础之Foundation框架常用类NSFileManager,DSDate,CGPoint,CGSize,copy,单例 1.01_FileManager学习 // // main.m // ...

  9. SpringBoot之ElasticsearchRestTemplate常用示例

    目录 1.引入pom依赖 2.application 配置 3.JavaBean配置以及ES相关注解 3.1 Student实体类 3.2 Teacher实体类 3.3 Headmaster 实体类 ...

最新文章

  1. 使用python的while语句,编写简单门票与年龄计算器
  2. fiddler自动响应AutoResponder之正则匹配Rule Editor
  3. java调用jndi出错,无法使用Java JNDI上下文查找来访问对象
  4. 剑指offer--旋转数组的最小数字
  5. Windows Phone 8 获取与监听网络连接状态
  6. python保存后不运行_Python后台执行不启用缓存
  7. bio、nio、aio及select、poll、epoll
  8. windows串口控制linux,是这个linux的串口配置与windows相同
  9. python字典合并最高效_Python合并两个字典的常用方法与效率比较
  10. [系统安全] 二十.PE数字签名之(上)什么是数字签名及Signtool签名工具详解
  11. 转速传感器隔离放大器输出信号隔离变送器正弦波转方波信号隔离器
  12. word封面下划线对齐
  13. CES2020即将完结!盘点这些脑洞产品,保证你看一眼就被种草
  14. POJ 1436 Horizontally Visible Segments(线段树建图+枚举)
  15. 使用stream报错:stream has already been operated upon or closed
  16. 京东如何建设基于云原生架构的监控 - 日志系统?
  17. 《BackTrack 5 Cookbook中文版——渗透测试实用技巧荟萃》目录—导读
  18. Java中的Swing概述
  19. JS内置对象操作方法整理
  20. Numpy中flatten与ravel的区别

热门文章

  1. 流畅的python 对象引用 可变性和垃圾回收
  2. Java8 Lambda表达式(三)Lambda表达式与Stream API
  3. mybatis-动态sql-if,choose,when,otherwis,trim,where,set,foreach,test,bind
  4. 冻豆腐怎么烧才出味儿?——红烧冻豆腐
  5. it行业热门岗位推荐,高薪就业不发愁
  6. 快速入门Docker
  7. 电源拓扑结构优缺点比较-常见开关电源优缺点对比
  8. JavaScript设计模式系统讲解与应用-笔记
  9. 11. python入门复习教程之命名空间与作用域,再谈异常,标准库,持久化与序列化
  10. 西门子PLC产生随机数