前言
作为一个runtime的初学着,决定去了解一下runtime的底层方法和数据结构,以方便加深我对于runtime的理解。
什么是runtime ?
runtime是一个C和汇编语言的的动态库,底层提供了大量的api。我们平常写的OC到最后都会转化为rumtime的C代码执行。OC是一个动态性的语言,它将工作尽可能的放在运行时而非编译时期处理,和其他语言不同,OC的动态性基础主要包括三种: 动态类型,动态绑定,动态加载。
了解runtime的各种数据结构
Class
我将Class的定义取出为 typedef struct objc_class *Class;
struct objc_class {
//isa 指针,OC一切都可以看成对象,isa指向的是对象的类对象。即实例对象的isa指针指向该对象的类,类对象的isa指针指向其元类。
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
//成员变量列表
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
//方法列表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
//缓存,方法的调用会先调用缓存中的方法列表,若没找到,其次才调用全部的方法类别,算是优化,因为类的方法列表频繁使用的方法其实是极少的,这样能提高系统的运行效率。
struct objc_cache *cache OBJC2_UNAVAILABLE;
//协议类别
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
以上列表我们可以通过runtime的方法来获取,方法为. class_copy...
unsigned int count = 0 ;
objc_property_t *propertyList = class_copyPropertyList([UIButton class], &count); //获取属性列表
for (int i = 0; i
unsigned int count = 0 ;
Ivar *ivarList = class_copyIvarList([UILabel class], &count); //获取实例变量列表
for (int i = 0; i
unsigned int count = 0 ;
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i
unsigned int count = 0 ;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([UIView class], &count);
for (int i = 0; i
Method
typedef struct objc_method *Method;
struct objc_method {
//方法选择器 可以通过sel来获取方法选择器的名称
SEL method_name OBJC2_UNAVAILABLE;
//方法的参数类型
char *method_types OBJC2_UNAVAILABLE;
//存储方法的实现,类似函数指针。
IMP method_imp OBJC2_UNAVAILABLE;
}
IMP
typedefine id (*IMP)(id, SEL, ...)
这个结构体相当于在SEL和IMP之间作了一个绑定。这样有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。IMP本质是一个函数指针,在oc调用方法之后,只要不抛出异常,都会找到相应方法的实现,而IMP则存储着方法的实现地址,即IMP执行方法的实现地址。
Cache
struct objc_cache {
// 缓存bucket的总数
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
// 实际缓存bucket的总数
unsigned int occupied OBJC2_UNAVAILABLE;
// 指向Method数据结构指针的数组
Method buckets[1] OBJC2_UNAVAILABLE;
};
方法的调用会先调用缓存中的方法列表,方法调用是懒调用。若没找到,其次才调用全部的方法类别,算是优化,因为类的方法列表频繁使用的方法其实是极少的,这样能提高系统的运行效率。
Ivar
struct objc_ivar {
// 成员变量名
char *ivar_name OBJC2_UNAVAILABLE;
// 获取成员变量类型
char *ivar_type OBJC2_UNAVAILABLE;
//成员变量的偏移量
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
Property
objc_property_t是表示Objective-C声明的属性的类型,其实际是指向objc_property结构体的指针,其定义如下:
typedef struct objc_property *objc_property_t;
消息转发机制
我们都知道OC的代码在运行时会转化成为C语言的代码
id objc_msgSend ( id self, SEL op, ... );
当objc_msgSend函数调用时
1 : 会检查当前selector是不是要忽略。
2 : 检测这个 selector 的 target 是不是 nil,Objc 允许我们对一个 nil 对象执行任何方法不会 Crash,因为运行时会被忽略掉。
3 : 如果上面都ok,则会开始查找IMP,先从缓存中查找,如果找到了就执行相应的方法。
4 :如果没有找到,则从类的元类的方法列表中查找相应的方法,如果找到了就执行相应的方法。
5 :如果还没找到,则从当前类的父类的元类方法列表中查找相应的方法,如果找到了,类对象就响应相应的方法。
6 : 一直执行第五步,知道NSObject类,如果还没有找到,则会走消息转发。
走消息转发
第一步,如果方法列表中找不到相应的IMP时,runtime会给我们调用 resolveInstanceMethod: 或 resolveClassMethod: 的机会去添加相应的IMP。例
void walkFunc(id self, SEL _cmd) { // 这是IMP的实现
//let the dog walk
NSLog(@"走23步");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel { //动态方法解析
NSString * selString = NSStringFromSelector(sel);
if ([selString isEqualToString:@"walk"]) {
class_addMethod(self.class, @selector(walk), (IMP)walkFunc, "@:"); //为类动态添加方法
}
return [super resolveInstanceMethod:sel];
}
第二步,如果第一步返回NO,则走第二步将方法转发给另外一个对象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSString *selStr = NSStringFromSelector(aSelector);
if ([selStr isEqualToString:@"walk" ]) {
return person2; //这里将消息转发给person2对象
}
return [super forwardingTargetForSelector:aSelector];
}
第三步,如果第二步返回nil或者self ,则开始走完整的消息转发。参数是个 NSInvocation 类型的对象,该对象封装了原始的消息和消息的参数。我们可以实现 forwardInvocation: 方法来对不能处理的消息做一些处理。也可以将消息转发给其他对象处理,而不抛出错误
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
RunTime 经常使用的方法
method_exchangeImplementations 交换方法的IMP 实现
了解load方法和 initialize方法
load 方法
·当一个类引入项目时,就会调用load方法。
·类和子类同时实现了load方法时,父类的执行顺势先于子类。
·类中的load方法执行顺序先与类别
·子类的load方法未实现时,不会调用父类的load方法。(不会)
initialize:
· 类或子类的第一个方法被调用前调用
·当子类实现了initialize方法,会覆盖父类的initialize方法。
·多个类目实现了initialize方法,执行最后一个引入的类目。
·当子类的initialize方法未实现,会调用父类的initialize方法(会)
+ (void)load
{
//获取系统方法
Method Method1 = class_getInstanceMethod([self class], @selector(fun1));
// 获取fch_imageNamed
Method Method2 = class_getInstanceMethod([self class], @selector(fun2));
// 交互方法:runtime
method_exchangeImplementations(Method1, Method2);
}
- (void)fun1
{
NSLog(@"运行fun1");
}
- (void)fun2
{
NSLog(@"运行fun2");
}
运行[self fun1] ,控制台输出
运行fun2
使用runtime中objc_setAssociatedObject()和objc_getAssociatedObject()方法,为类新增属性(设置关联对象);
/* 动态添加方法:
第一个参数表示Class cls 类型;
第二个参数表示待调用的方法名称;
第三个参数(IMP)myAddingFunction,IMP一个函数指针,这里表示指定具体实现方法myAddingFunction;
第四个参数表方法的参数,0代表没有参数;
*/
- (NSString *)addStr
{
return objc_getAssociatedObject(self, "addStrKey");
}
-(void)setAddStr:(NSString *)addStr
{
objc_setAssociatedObject(self, //赋值的对象
"addStrKey", //属性的key值
addStr, //属性的value值
OBJC_ASSOCIATION_RETAIN); //修饰策略
}
后面看到人说可以通过runtime获取UIAlertController 和UIAlertAction的 实例变量ivar ,把实例变量当作key值,通过KVC来修改UIAlertController的样式。
获取实例变量
- (void)getAllVariable
{
unsigned int count = 0;
Ivar *allIvar = class_copyIvarList([UIAlertAction class], &count);
for (int i = 0; i
得到实例变量
// UIAlertAction class
//_title,
//_titleTextAlignment,
//_enabled,
//_checked,
//_isPreferred,
//_imageTintColor,
//_titleTextColor,
//_style,
//_handler,
//_simpleHandler,
//_image,
//_shouldDismissHandler,
//__descriptiveText,
//_contentViewController,
//_keyCommandInput,
//_keyCommandModifierFlags,
//__representer,
//__interfaceActionRepresentation,
//__alertController
同理UIAlertController得实例变量 ,这个比较多,我拿了我用到的
// _attributedTitle,
// _attributedMessage,
修改UIAlertController的显示
- (void)showAlert
{
[self presentViewController:self.alert animated:YES completion:nil];
NSMutableAttributedString *str = [[NSMutableAttributedString alloc]initWithString:self.alert.message];
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc]init];
[style setAlignment:NSTextAlignmentRight];
[str addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(0, str.length)];
[str addAttribute:NSForegroundColorAttributeName value:[UIColor greenColor] range:NSMakeRange(0, str.length)];
[self.alert setValue:str forKey:@"attributedMessage"];
NSMutableAttributedString *str1 = [[NSMutableAttributedString alloc]initWithString:self.alert.title];
[str1 addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, str1.length)];
NSMutableParagraphStyle *style1 = [[NSMutableParagraphStyle alloc]init];
[style1 setLineSpacing:2.0];
[style1 setLineBreakMode:NSLineBreakByWordWrapping];
[str1 addAttribute:NSParagraphStyleAttributeName value:style1 range:NSMakeRange(0, str1.length)];
[self.alert setValue:str1 forKey:@"attributedTitle"];
[self.alert.actions.firstObject setValue:[UIColor redColor] forKey:@"titleTextColor"]; //修改第一个action按钮颜色
[self.alert.actions.lastObject setValue:[UIColor orangeColor] forKey:@"titleTextColor"]; //修改最后一个action按钮颜色
[[self.alert.actions objectAtIndex:1] setValue:[UIColor cyanColor] forKey:@"titleTextColor"];//修改第二个action按钮颜色
}
- (UIAlertController *)alert
{
if (!_alert) {
_alert = [UIAlertController alertControllerWithTitle:@"runtime标题" message:@"runtime留言" preferredStyle:UIAlertControllerStyleAlert];
[_alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.placeholder = @"runtime输入框";
}];
[_alert addAction:[UIAlertAction actionWithTitle:@"runtime动作1" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"点击了按钮1");
}]];
[_alert addAction:[UIAlertAction actionWithTitle:@"runtime动作2" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"点击了按钮2");
}]];
[_alert addAction:[UIAlertAction actionWithTitle:@"runtime动作3" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"点击了按钮3");
}]];
}
return _alert;
}
样子很丑........
结束
runtime runtime还有很多难点,接下来准备好好看看YYModel源码,估计也很多看不懂,加油勉励。