美文网首页runtime&runloop
runtime实用面试,使用

runtime实用面试,使用

作者: 爱新觉罗fate | 来源:发表于2016-07-28 22:14 被阅读53次

    runtime概述

    RunTime简称运行时。就是系统在运行的时候的一些机制,其中最主要的是消息机制。对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。大部分时候runtime是在幕后运行工作着的。在编译含有OC类、方法的代码时,编译器通过Runtime的消息机制在幕后完成创建数据、调用函数。Runtime的实质是消息的发送,官方文档称之为Messaging,翻译成中文为消息机制。消息机制在OC源码使用过程中会被调用。
    所有对象都继承NSObject,在NSObjcet中存在一个Class的isa指针,系统定义如下

    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class super_class                                        // 指向其父类
        const char *name                                         // 类名
        long version                                             // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
        long info                                                // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
        long instance_size                                       // 该类的实例变量大小(包括从父类继承下来的实例变量);
        struct objc_ivar_list *ivars                             // 用于存储每个成员变量的地址
        struct objc_method_list **methodLists                    // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
        struct objc_cache *cache                                 // 指向最近使用的方法的指针,用于提升效率;
        struct objc_protocol_list *protocols                     // 存储该类遵守的协议
    #endif
    
    } OBJC2_UNAVAILABLE;
    /* Use `Class` instead of `struct objc_class *` */
    
    

    那OC是怎么实现动态调用的呢?下面我们来看看OC通过发送消息来达到动态调用的秘密。假如在OC中写了这样的一个代码:

    [obj makeText];
    

    其中obj是一个对象,makeText是一个函数名称。对于这样一个简单的调用。在编译时RunTime会将上述代码转化成

    objc_msgSend(obj,@selector(makeText));
    

    @selector (makeText):这是一个SEL方法选择器。SEL其主要作用是快速的通过方法名字(makeText)查找到对应方法的函数指针,然后调用其函 数。SEL其本身是一个Int类型的一个地址,地址中存放着方法的名字。对于一个类中。每一个方法对应着一个SEL。所以iOS类中不能存在2个名称相同 的方法,即使参数类型不同,因为SEL是根据方法名字生成的,相同的方法名称只能对应一个SEL。

    下面我们就来看看具体消息发送之后是怎么来动态查找对应的方法的。

    首先,编译器将代码[obj makeText];转化为objc_msgSend(obj, @selector (makeText));,在objc_msgSend函数中。首先通过obj的isa指针找到obj对应的class。在Class中先去cache中 通过SEL查找对应函数method(猜测cache中method列表是以SEL为key通过hash表来存储的,这样能提高函数查找速度),若 cache中未找到。再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加 入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。

    下面看一些具体的使用

    定义如下属性,可以通过runtime获得

    @interface Person : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *gender;
    @property (nonatomic, strong) NSNumber *age;
    @property (nonatomic, assign) NSInteger weight;
    @end
    

    涉及到的方法函数如下

    Ivar *class_copyIvarList(Class cls, unsigned int *outCount)      //获取所有成员变量
     const char *ivar_getName(Ivar v)            //获取某个成员变量的名字
     const char *ivar_getTypeEncoding(Ivar v)   //获取某个成员变量的类型编码
     Ivar class_getInstanceVariable(Class cls, const char *name)    //获取某个类中指定名称的成员变量
     id object_getIvar(id obj, Ivar ivar)    //获取某个对象中的某个成员变量的值
     void object_setIvar(id obj, Ivar ivar, id value)    //设置某个对象的某个成员变量的值
    

    1 获得属性的名字和类型

    - (NSString *)description
    {
        /**
         * class:要获取的某个类,outCount:通过这一个函数执行之后会将成员变量的个数赋值到此
         */
        unsigned int outCount;
        Ivar *ivarList = class_copyIvarList([Person class], &outCount);
        for (NSInteger i = 0; i < outCount; i++) {
            // 每次获取一个成员变量
            Ivar ivar = ivarList[i];
            // 打印成员变量的名字和类型编码
            NSLog(@"name = %s, type = %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
        }
        return nil;
    }
    

    运行结果如下

    2016-07-28 21:43:56.455 runtime[7346:409409] name = _name, type = @"NSString"
    2016-07-28 21:43:56.455 runtime[7346:409409] name = _gender, type = @"NSString"
    2016-07-28 21:43:56.455 runtime[7346:409409] name = _age, type = @"NSNumber"
    2016-07-28 21:43:56.455 runtime[7346:409409] name = _weight, type = q
    2016-07-28 21:43:56.456 runtime[7346:409409] name = _dog, type = @"Dog"
    

    2 给属性赋值取值

    + (Person *)personWithName:(NSString *)name gender:(NSString *)gender age:(NSNumber *)age weight:(NSInteger)weight
    {
        Person *person = [Person new];
        unsigned int outCount;
        Ivar *ivarList = class_copyIvarList(self, &outCount);
        // obj:要设置的对象    ivar:要设置的对象的某一个属性    value:value
        object_setIvar(person, ivarList[0], name);
        object_setIvar(person, ivarList[1], gender);
        object_setIvar(person, ivarList[2], age);
        object_setIvar(person, ivarList[3], @(weight));
        return person;
    }
    
    - (void)getPersonMessage
    {
        unsigned int outCount;
        Ivar *ivarList = class_copyIvarList([Person class], &outCount);
        for (NSInteger i = 0; i < outCount; i++) {
            NSLog(@"name = %s, value = %@", ivar_getName(ivarList[i]), object_getIvar(self, ivarList[i]));
        }
    }
    

    3 给方法动态添加实现

    涉及到的方法如下:

     按优先级排序
     1 + resolveInstanceMethod:(SEL)sel      // 为一个实例方法动态添加实现
       + resolveClassMethod:(SEL)sel         //   为一个类方法动态添加实现
     2 - (id)forwardingTargetForSelector:(SEL)aSelector
     //为没有实现的方法指定一个对象
     3 - (void)forwardInvocation:(NSInvocation *)anInvocation
     //子类重载这个方法为消息指定其他对象
    
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        NSString *selString = NSStringFromSelector(sel);
    // 没有参数的方法
        if ([selString isEqualToString:@"walkOnTheStreet"]) {
            // 为一个没有实现的方法动态添加实现
            /**
             *  cls:类
                name:没有实现的方法
                IMP:要添加的实现
                types:动态添加的实现的类型编码
             */
            class_addMethod(self, @selector(walkOnTheStreet), (IMP)walkFunc, "V");
            
        }
    //有参数的方法
        if ([selString isEqualToString:@"walkOnTheStreet:"]) {
           
            class_addMethod(self, @selector(walkOnTheStreet:), (IMP)walkFunction, "V@:@");
            
        }
    
        return [super resolveInstanceMethod:sel];
    }
    void walkFunc(){
        NSLog(@"Person---%s",__func__);
    }
    
    void walkFunction(id self, SEL sel, NSString *str){
        NSLog(@"Person---%s--%@", __func__, str);
    }
    

    4 切换消息转发对象

    • 方式1
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        NSString *selString = NSStringFromSelector(aSelector);
        if ([selString isEqualToString:@"walkOnTheStreet"]){
            self.dog = [Dog new];
            return self.dog;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    • 方式2
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        if ([Dog instancesRespondToSelector:anInvocation.selector]) {
            self.dog = [Dog new];
            [anInvocation invokeWithTarget:self.dog];
        }
    }
    // 给方法制定一个有效的签名
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
        if (!methodSignature) {
            methodSignature = [Dog instanceMethodSignatureForSelector:aSelector];
        }
        return methodSignature;
    }
    

    5 归档、反归档

    // 编码
    - (void)encodeWithCoder:(NSCoder *)aCoder{
        unsigned int outCount;
        Ivar *ivarList = class_copyIvarList([Person class], &outCount);
        for (NSInteger i = 0; i < outCount; i++) {
    //        [aCoder encodeObject:self.name forKey:@"name"]; 普通方法举例
            const char *cName = ivar_getName(ivarList[i]);
            NSString *name = [NSString stringWithUTF8String:cName];
            [aCoder encodeObject:[self valueForKey:name] forKey:name];
            
        }
    }
    // 解码
    - (instancetype)initWithCoder:(NSCoder *)aDecoder{
        self = [super init];
        if (self) {
    //        _name = [aDecoder decodeObjectForKey:@"name"]; 普通方法举例
            unsigned int outCount;
            Ivar *ivarList = class_copyIvarList([Person class], &outCount);
            for (NSInteger i = 0; i < outCount; i++) {
                const char *cName = ivar_getName(ivarList[i]);
                NSString *name = [NSString stringWithUTF8String:cName];
                [self setValue:[aDecoder decodeObjectForKey:name] forKey:name];
            }
        }
        return self;
    }
    

    附加面试题

    不使用继承,给系统类加一个可被外界访问和使用的属性

    当我们用类目实现的时候加一个属性时,虽然可以访问属性,但给这个属性赋值并取值的时候,就会Crash,系统不认识这个属性,这时要用runtime来解决
    给NSDictionary加一个name的属性

    @interface NSDictionary (MyDict)
    @property (nonatomic, copy) NSString *name;
    @end
    
    #import "NSDictionary+MyDict.h"
    #import <objc/runtime.h>
    @implementation NSDictionary (MyDict)
    
    /**
     void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)  //为某个类关联某个对象
     id objc_getAssociatedObject(id object, const void *key) //获取到某个类的某个关联对象
     void objc_removeAssociatedObjects(id object) //移除已经关联的对象
     */
    
    - (void)setName:(NSString *)name{
        // object:要关联的对象,key:成员变量对应的key值,@selector(属性名) value:value
        objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)name{
        return objc_getAssociatedObject(self, @selector(name));
    }
    
    @end
    
    

    ok

    相关文章

      网友评论

        本文标题:runtime实用面试,使用

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