美文网首页
iOS-runtime理解与运用

iOS-runtime理解与运用

作者: 百有异用是書生 | 来源:发表于2016-12-08 14:57 被阅读0次

    在我们刚刚开始学习oc这门语言时,总有一些词我们在各种基础入门教程上反复提到,例如,Objective-C语言是一门动态语言,且面向对象,why?

    Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。

    这种特性意味着Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于Objective-C来说,这个运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行。这个运行时系统即Objc Runtime。Objc Runtime其实是一个Runtime库,它基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。

    一、什么是类与对象?

    一个对象通过一个类来描述它的特性,就如car类,有价格price,载客量peopleNumber等成员变量,或者属性来描述车,还有一系列方法,包括实例方法和类方法来实现车的特性,比如-(void)begin;-(void)stop;
    Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:
    typedef struct objc_class *Class;

    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class super_class                       OBJC2_UNAVAILABLE;  // 父类
        const char *name                        OBJC2_UNAVAILABLE;  // 类名
        long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
        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;
    

    其实一个类oc中其实就是一个结构体,这个结构体又是一些什么属性能够描述一个类呢?

    • Class isa OBJC_ISA_AVAILABILITY isa指针指向它的metaClass(元类),可以看成储存这个类的类方法的一个类,而这个元类其中的结构体结构也有一个isa指针,是指向Nsobject的metaClass的,这样就形成完美闭环

    • metaClass 存储着这个类所有的类方法,当向一个类发送消息时,则会通过isa指针找到相应的方法,当向一个类实例对象发送消息,则通过struct objc_method_list **methodLists找到相应的方法

    runtime提供了大量的函数来操作类与对象。类的操作方法大部分是以class为前缀的,而对象的操作方法大部分是以objc或object_为前缀。下面我们将根据这些方法的用途来分类讨论这些方法的使用。

    -动态交换系统方法

    #import <Foundation/Foundation.h>
    
    @interface runtimeManage : NSObject
    //自定义两个对象方法
    -(void)run;
    -(void)sleep;
    @end
    
    #import "runtimeManage.h"
    
    @implementation runtimeManage
    //实现两个对象方法
    -(void)run
    {
        NSLog(@"run");
    }
    -(void)sleep
    {
        NSLog(@"sleep");
    }
    @end
    

    接下来就是关键代码,怎么交换

    //通过SEL映射 取到对应的方法,分别是run sleep
        Method m1=class_getInstanceMethod([runtimeManage class], @selector(run));
        Method m2=class_getInstanceMethod([runtimeManage class], @selector(sleep));
        //然后就交换
        method_exchangeImplementations(m1, m2);
        runtimeManage *manage=[[runtimeManage alloc]init];
        //调用方法检验是否交换成功
        [manage run];
        [manage sleep];
    

    方法总结class_getInstanceMethodmethod_exchangeImplementations

    -拦截系统方法并添加功能或者替换

    当整个项目临时需要更换另外一种字体,不紧急的情况是可以搜索出来一个个改,任务量和修改的完整度视项目大小而定,可以使用runtime来实现整体修改功能

    #import <objc/runtime.h>
    #import "UIFont+user.h"
    
    @implementation UIFont (user)
    
    +(UIFont *)UserSystemFontOfSize:(CGFloat)fontSize
    {
        //这里可以增加判断,什么情况下要交换,在7以上使用带后缀的字体
        double version = [[UIDevice currentDevice].systemVersion doubleValue];
        if (version >= 7.0) {
            // 如果系统版本是7.0以上,使用另外一套文件名结尾是‘_os7’的扁平化图片
            fontSize=fontSize+10;
        }
        return [UIFont UserSystemFontOfSize:fontSize];
    }
    +(void)load
    {
        //此方法仅调用一次
        Method m1=class_getClassMethod([UIFont class], @selector(systemFontOfSize:
                                                                 ));
        Method m2=class_getClassMethod([UIFont class], @selector(UserSystemFontOfSize:));
        method_exchangeImplementations(m1, m2);
    }
    @end
    
    

    -动态添加方法与消息转发

    当方法太多,在编译期间会把所有方法添加到相应的地方,这是会消耗内存的,动态添加会减少内存消耗,(具体原因做个标记,后期调研)-------------------按照正常流程,当一个消息发送后,会在消息接收的对象的缓存方法列表Cache里查找方法,找不到则会调用resolveInstanceMethod:或者resolveClassMethod:,在这两个方法里我们有一次机会来添加方法

    //方法是c语言格式的方法,其中id,sel是两个隐藏参数,是必须要有的
    void dynamicMethodIMP(id self, SEL _cmd) {
        // implementation ....
    }
    @implementation MyClass
    //如果是-开头的实例方法,则在这个函数,类方法resolveClassMethod:中
    + (BOOL)resolveInstanceMethod:(SEL)aSEL
    {
        //如果是调用的没有实现的方法,也就是我们要动态添加实现的这个方法名
        if (aSEL == @selector(resolveThisMethodDynamically)) {
        //第一个参数 :为哪个类添加方法,第三个参数:IMP理解为这个方法的具体实现的地址 第四个参数:是一种编码格式,需要去学习一下Type Encodings
              class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
    //返回yes代表此次消息处理到此为止,不在往下进行,返回no则进行消息转发步骤
              return YES;
        }
        return [super resolveInstanceMethod:aSEL];
    }
    @end
    

    消息转发

    消息转发.png

    转发机制开始前,我们可以还有一次机会可以更换消息的接受者,在方法(id)forwardingTargetForSelector:(SEL)aSelector

    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        if(aSelector == @selector(mysteriousMethod:)){
      //此处返回一个另外的类,则会在另外的类里去查找是否有此方法的实现,返回nil或者self,则正式进入消息转发流程forwardInvocation:
            return alternateObject;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    

    最后一步,也是我们对这个没人要的消息作处理的最后一次机会forwardInvocation:

    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        if ([someOtherObject respondsToSelector:
                [anInvocation selector]])
            [anInvocation invokeWithTarget:someOtherObject];
        else
            [super forwardInvocation:anInvocation];
    }
    
    //重点:注意:参数 anInvocation 是从哪来的?
    在 forwardInvocation: 消息发送前,Runtime 系统会向对象发送methodSignatureForSelector: 消息,并取到返回的方法签名用于生成 NSInvocation 对象。所以重写 forwardInvocation: 的同时也要重写 methodSignatureForSelector: 方法,否则会抛异常。
    

    这一步我们把没人接纳的这条消息都发给这个垃圾桶类来处理,这样不会崩溃,留了一个坑,后续加深学习在整理,这样通过消息转发我们可以实现类似多继承的效果,实际用到的地方只能实际问题实际分析,熟悉远离,遇到问题才能灵活的去利用这个知识点解决问题,直接复制来心里总是不踏实的。。。。

    -分类增加属性

    给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。

    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
        // 给系统NSObject类动态添加属性name
    
        NSObject *objc = [[NSObject alloc] init];
        objc.name = @"小码哥";
        NSLog(@"%@",objc.name);
    
    }
    
    
    @end
    
    
    // 定义关联的key
    static const char *key = "name";
    
    @implementation NSObject (Property)
    
    - (NSString *)name
    {
        // 根据关联的key,获取关联的值。
        return objc_getAssociatedObject(self, key);
    }
    
    - (void)setName:(NSString *)name
    {
        // 第一个参数:给哪个对象添加关联
        // 第二个参数:关联的key,通过这个key获取
        // 第三个参数:关联的value
        // 第四个参数:关联的策略
        objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    @end
    

    -获取类的属性和方法以及运用

    获取属性主要就是熟悉下面的方法,直接摘录一段其他文章的方法列表,作为纪录和查找之用
    获得某个类的类方法Method class_getClassMethod(Class cls , SEL name)
    获得某个类的实例对象方法Methodclass_getInstanceMethod(Class cls , SEL name)
    交换两个方法的实现void method_exchangeImplementations(Method m1 , Method m2)
    set方法,将值value 跟对象object 关联起来(将值value 存储到对象object 中) 参数 object:给哪个对象设置属性 参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节 参数 value:给属性设置的值 参数policy:存储策略 (assign 、copy 、 retain就是strong)void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)
    利用参数key 将对象object中存储的对应值取出来id objc_getAssociatedObject(id object , const void *key)
    获得某个类的所有成员变量(outCount 会返回成员变量的总数) 参数: 1、哪个类 2、放一个接收值的地址,用来存放属性的个数 3、返回值:存放所有获取到的属性,通过下面两个方法可以调出名字和类型Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
    获得成员变量的名字const char *ivar_getName(Ivar v)
    获得成员变量的类型const char *ivar_getTypeEndcoding(Ivar v)
    获取协议列表protocol_getName
    获取属性列表property_getName

    相关文章

      网友评论

          本文标题:iOS-runtime理解与运用

          本文链接:https://www.haomeiwen.com/subject/waunpttx.html