概论:Runtime是Objective-C中底层的一套C语言API,是一个将C语言转化为面向对象语言的拓展。OC是一种面向对象的动态语言,动态语言就是在运行时执行静态语言的编译连接的工作。OC编写的程序不能直接编译为及其读懂的机器语言,在程序运行时,须通过Runtime来转换。
而在某些在特定的场景下,运用runtime的知识,往往可以大大减少我们的工作量。并能使代码更加统一、简洁。
本文将以一下几个方面逐一讲解runtime:
目录:
一、系统方法中的应用
1、消息机制
2、KVO的底层实现
二、runtime常见作用
1、动态获取成员属性
2、动态获得成员变量
3、动态获得实例方法
4、动态添加方法实现
5、方法交换
三、实际开发中的应用
1、给控件添加block事件回调方法
2、自定义类型自动归档、解归档
3、数组越界crash处理
首先定义一个Dog类,本篇文章的讲解将围绕这个类展开。
@interface Dog : NSObject
{
float _weight;
}
@property(nonatomic,copy) NSString *name;
- (void)run:(NSInteger)runLong street:(NSString *)streetName;
@end
@interface Dog ()
{
NSInteger _age;
}
@property(nonatomic,copy) NSString *address;
@end
@implementation Dog
- (void)run:(NSInteger)runLong street:(NSString *)streetName{
NSLog(@"在%@跑了%li米",streetName,runLong);
}
- (void)_sleep{
NSLog(@"%s",__func__);
}
@end
一、系统方法运用runtime
1、发送消息
在OC中,调用一个对象的方法,实际上是给对象发了一条消息,在编译Objective-C函数调用的语法时,会被翻译成一个C的函数调用:objc_msgSend()
当我们dog,run的方法时:
[dog run:100 street:@"华尔街"];
实际上会转换为:
objc_msgSend(dog, @selector(run:street:),100,@"华尔街");
注:由于xcode5.0开始苹果不建议我们使用底层的代码,所以我们需要在target->build setting->搜索msg->将YES改为NO
之后import <objc/message.h>
才能使用objc_msgSend
方法。
2、KVO的底层实现
概述:KVO是通过"isa-swizzling"技术来实现的,当一个对象注册观察者时,这个对象的isa指针被修改指向一个中间类。
当观察A类型的对象时,在运行时会创建了一个继成自A类的NSKVONotifying_A类,且为NSKVONotifying_A重写观察属性的setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察者属性值的更改情况。
因为本文主要讲解runtime的作用以及项目中的应用,所以这里就不做过多的陈述,关于KVO和isa指针不了解的同学可以移步我的另一篇Objective-C isa指针及KVO实现原理进行学习。
二、runtime常见作用
1、动态获取成员属性
通过class_copyPropertyList获取属性列表,此方法会获得所有通过@property展开的成员属性,包括私有属性。
unsigned int count = 0;
NSMutableArray *nameArray = [NSMutableArray array];
/**
@param class 你想要检测的类 Class类型
@param count 数组的个数
@return 返回c语言数组 数组里元素是 objc_property_t 类型
*/
objc_property_t *properList = class_copyPropertyList([Dog class],&count);
for (int i = 0; i<count; i++) {
const char *name = property_getName(properList[i]);
NSString *ocName = [NSString stringWithUTF8String:name];
[nameArray addObject:ocName];
}
NSLog(@"属性名:%@",nameArray);
2、动态获得成员变量
通过class_copyIvarList
获取成员变量列表,此方法会获得所有通过@property展开的成员属性以及声明的成员变量,包括私有成员变量。
unsigned int count = 0;
NSMutableArray *nameArray = [NSMutableArray array];
NSMutableArray *typeArray = [NSMutableArray array];
/**
@param class 你想要检测的类 Class类型
@param count 数组的个数
@return 返回c语言数组 数组里元素是 Ivar 类型
*/
Ivar *vars = class_copyIvarList([Dog class], &count);
for (int i = 0; i<count; i++) {
const char *name = ivar_getName(vars[i]);
const char *type = ivar_getTypeEncoding(vars[i]);
NSString *ocName = [NSString stringWithUTF8String:name];
NSString *ocType = [NSString stringWithUTF8String:type];
[nameArray addObject:ocName];
[typeArray addObject:ocType];
/*
类型说明:基本类型 q:NSInteger d:double f:float i:int c:BOOL
引用类型:以字符串显示 如:NSString:"NSString" NSArray:"NSArray"
不同类型编码请参考:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
*/
}
NSLog(@"变量名:%@-----变量类型:%@",nameArray,typeArray);
3、动态获得实例方法
通过class_copyMethodList
获得实例方法列表,通过method_copyReturnType
获得返回类型,通过method_copyArgumentType
获得各个参数类型,代码如下
unsigned int count = 0;
NSMutableArray *methodArray = [NSMutableArray array];
Method *methodList = class_copyMethodList([Dog class],&count);
for (int i = 0 ; i<count; i++) {
NSMutableDictionary *methodDic = [NSMutableDictionary dictionary];
// 方法名
SEL method = method_getName(methodList[i]);
// 返回类型
const char *returnTypeName = method_copyReturnType(methodList[i]);
// 参数个数 默认会多两个 第一个接受消息的对象 第二个selector 原因:objc_msgSend(id, SEL)前两个参数
int argumentCount = method_getNumberOfArguments(methodList[i]);
methodDic[@"methodName"] = NSStringFromSelector(method);
methodDic[@"returnType"] = [NSString stringWithUTF8String:returnTypeName];
for (int j = 0; j<argumentCount; j++) {
NSString *key = [NSString stringWithFormat:@"param%i",j+1];
methodDic[key] = [NSString stringWithUTF8String:method_copyArgumentType(methodList[i], j)];
}
[methodArray addObject:methodDic];
}
// @对象类型 v为void :为selector
NSLog(@"%@",methodArray);
4、动态添加方法实现
通过class_addMethod
动态添加方法:通过[dog performSelector:@selector(haha)];
执行haha的方法,因为Dog类中并没有haha方法,所以执行时会crash,我们可以通过如下代码,动态添加方法实现:
// class: 给哪个类添加方法
// SEL: 方法选择器,即调用时候的名称(只是一个编号)
// IMP: 方法的实现(函数指针)
// type: 方法类型,(返回值+隐式参数) v:void @对象->self :表示SEL->_cmd
class_addMethod([Dog class], @selector(haha),(IMP)abc, "v@:");
// 任何方法默认都有两个隐式参数,self,_cmd
void abc(id self, SEL _cmd) {
NSLog(@"哈哈大笑");
}
5、方法交换
通过method_exchangeImplementations
实现方法的交换:在Dog类的load方法里添加如下方法,实现方法的交换。(写在load方法中的原因:当类被加载到内存中是会调用,执行比较早,并且不会多次调用)
+(void)load{
Method orginMethod = class_getInstanceMethod([Dog class], @selector(run:street:));
Method myMethod = class_getInstanceMethod([Dog class], @selector(myRun:street:));
method_exchangeImplementations(orginMethod, myMethod);
}
- (NSString *)myRun:(NSInteger)runLong street:(NSString *)streetName{
NSLog(@"交换方法了");
return nil;
}
三、实际开发中的应用
说明:runtime在开发中的应用有很多,但知识点都是大致相同,为了控制篇幅这里只给出了某个知识点的一种应用,其他的应用可自己尝试实现,如果想看其他实现的代码可以给我留言。
1、给控件添加block回调方法
知识点: 动态关联两个对象
需求:给UIButton添加点击点击事件回调的block方法
思路:给UIButton添加一个扩展方法,传入一个block,再在button的点击事件里执行block;
还可以做:给类的catagory添加属性
实现代码:
@interface UIButton (Blocks)
- (void)handleControlEvent:(UIControlEvents)event withBlock:(void (^)(UIButton *btn))block;
@end
@implementation UIButton (Blocks)
static char overviewKey;
- (void)handleControlEvent:(UIControlEvents)event withBlock:(void (^)(UIButton *btn))block {
// 关联对象方法
// object:给哪个对象添加关联
// key:被关联者的索引key
// value:被关联者
// policy:关联时采用的协议,有assign,retain,copy等协议,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
objc_setAssociatedObject(self, &overviewKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self addTarget:self action:@selector(callActionBlock:) forControlEvents:event];
}
- (void)callActionBlock:(id)sender {
// 通过key 获取关联对象
void (^block)(UIButton *btn) = objc_getAssociatedObject(self, &overviewKey);
if (block) {
block(sender);
}
}
@end
2、自定义类型自动归档、解归档
概述: 归档,持久化保存数据的一种方法。系统的类如果遵循了NSCoding协议都可以直接进行归档(如:NSString,NSArray等),但是自定义对象如果要进行归档,需要:
1、遵循NSCoding
协议
2、实现解档方法:-(id)initWithCoder:(NSCoder *)aDecoder
和归档方法:-(void)encodeWithCoder:(NSCoder *)aCoder
知识点: 获取一个类的成员变量
需求:一劳永逸的给自定义类型实现自动归档
思路:获取自定义类的成员变量,获取成员变量字符串,再通过KVC取值,实现归/解档。
还可以做:字典转模型、获取系统类的私有属性,进行操作。如 UITextField占位文字的颜色 [self setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
实现代码:
// 归档
- (void)encodeWithCoder:(NSCoder *)aCoder{
// 遍历,对父类的属性执行归档方法
Class c = self.class;
while (c &&c != [NSObject class]) {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 通过KVC取值
id value = [self valueForKeyPath:key];
[aCoder encodeObject:value forKey:key];
}
free(ivars);
c = [c superclass];
}
}
// 解档
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
// 遍历,对父类的属性执行解档方法
Class c = self.class;
while (c &&c != [NSObject class]) {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(c, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
c = [c superclass];
}
}
return self;
}
3、数组越界crash处理
知识点:获取某个类的方法、方法交换
需求:如果数组越界,避免程序闪退
思路:首先看一下数组越界后所报的原因
报错原因提到了"__NSArrayI"而不是"NSArray"这是因为:NSArray这个类在设计的时候采用了“抽象工厂”模式,内部是个类簇,真正起作用的是内部的:"__NSArray0"(空数组)、"__NSSingleObjectArrayI"(个数为1的数组),"__NSArrayI"(不可变数组)以及"__NSArrayM"(可变数组)。所以我们只需要给NSArray添加个category替换这几个类的
objectAtIndex:
方法即可。还可以做:截获系统方法,替换成自己的方法。如:字典添加nil的crash,按钮暴力点击重复响应,字体大小适配等
代码:
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool {
[self exchangeMethodWithClass:objc_getClass("__NSArray0") orginSelector:@selector(objectAtIndex:) customSelector:@selector(emptyObjectIndex:)];
[self exchangeMethodWithClass:objc_getClass("__NSSingleObjectArrayI") orginSelector:@selector(objectAtIndex:) customSelector:@selector(singleArrObjectIndex:)];
[self exchangeMethodWithClass:objc_getClass("__NSArrayI") orginSelector:@selector(objectAtIndex:) customSelector:@selector(arrObjectIndex:)];
[self exchangeMethodWithClass:objc_getClass("__NSArrayM") orginSelector:@selector(objectAtIndex:) customSelector:@selector(mutableObjectIndex:)];
[self exchangeMethodWithClass:objc_getClass("__NSArrayM") orginSelector:@selector(insertObject:atIndex:) customSelector:@selector(mutableInsertObject:atIndex:)];
}
});
}
+ (void)exchangeMethodWithClass:(Class)class orginSelector:(SEL)orginS customSelector:(SEL)customS{
Method orginMethod = class_getInstanceMethod(class, orginS);
Method customMethod = class_getInstanceMethod(class, customS);
method_exchangeImplementations(orginMethod, customMethod);
}
- (id)emptyObjectIndex:(NSInteger)index{
return nil;
}
- (id)singleArrObjectIndex:(NSInteger)index{
@autoreleasepool {
if (index >= self.count || index < 0) {
return nil;
}
return [self singleArrObjectIndex:index];
}
}
- (id)arrObjectIndex:(NSInteger)index{
@autoreleasepool {
if (index >= self.count || index < 0) {
return nil;
}
return [self arrObjectIndex:index];
}
}
- (id)mutableObjectIndex:(NSInteger)index{
@autoreleasepool {
if (index >= self.count || index < 0) {
return nil;
}
return [self mutableObjectIndex:index];
}
}
- (void)mutableInsertObject:(id)object atIndex:(NSUInteger)index{
@autoreleasepool {
if (object) {
[self mutableInsertObject:object atIndex:index];
}
}
}
- (id)myObjectAtIndex:(NSUInteger)index
{
@autoreleasepool {
if (index < self.count) {
return [self myObjectAtIndex:index];
} else {
return nil;
}
}
}
到这里本篇文章基本结束,文中所涉及代码都在这里
最后:喜欢我文章的可以多多点赞和关注,您的鼓励是我写作的动力。O(∩_∩)O~
网友评论