理解使用runtime

前言

作为一个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_第1张图片
UIAlertController.png

结束

runtime runtime还有很多难点,接下来准备好好看看YYModel源码,估计也很多看不懂,加油勉励。

你可能感兴趣的:(理解使用runtime)