Runtime

作者: Jean_Lina | 来源:发表于2021-05-12 11:43 被阅读0次

    runtime运行时机制
    1:通过runtime,实现方法交换(交换两个类方法、交换两个实例方法)
    2:通过runtime,在分类中设置属性。
    3:通过runtime,获取类的属性列表。可以通过KVC动态设置值。
    4:通过runtime,获取类的方法列表。可以通过sendMessage动态发送消息。
    5:通过runtime,获取类的协议列表。
    6:通过runtime,获取类的成员变量列表。字典转模型
    7:通过runtime,调用对象方法,让对象发送消息。
    8:通过runtime,动态添加方法。
    9:通过runtime,实现NSCoding的自动归档和解档。
    10:通过runtime,实现字典转模型的自动转换。
    11:通过runtime,动态修改变量的值。

    1:通过runtime,实现方法交换(交换两个类方法、交换两个实例方法)

    - (void)viewDidLoad {
        [super viewDidLoad];
        int a = 9;
        int b = 11;
        NSLog(@"交换前:a = %d --- b = %d",a,b);
        
        //通过异或实现两个整数的交换
        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
        NSLog(@"交换后:a = %d --- b = %d",a,b);
        // Do any additional setup after loading the view.
        
        Person *p = [Person new];
        Method m1 = class_getClassMethod([Person class], @selector(run));
        Method m2 = class_getClassMethod([Person class], @selector(study));
        method_exchangeImplementations(m1, m2);
        
        //交换两个类方法
        [Person run];
        [Person study];
    
        Method m3 = class_getInstanceMethod([p class], @selector(eat));
        Method m4 = class_getInstanceMethod([p class], @selector(sheep));
        method_exchangeImplementations(m3, m4);
        //交换两个实例方法
        [p eat];
        [p sheep];
    }
    

    2:通过runtime,在分类中设置属性。

    @property(nonatomic,copy)NSString *name;
    
    static const char *nameKey = "nameKey";
    
    - (NSString *)name {
        return objc_getAssociatedObject(self, nameKey);
    }
    - (void)setName:(NSString *)name {
        objc_setAssociatedObject(self, nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    

    3:获取类的属性列表

    static const char *propertiesKey = "propertiesKey";
    + (NSArray *)propertiesList {
        unsigned int count = 0;
        objc_property_t *list = class_copyPropertyList([self class], &count);
        NSMutableArray *arrayM = [NSMutableArray array];
        //遍历所有的属性
        for (unsigned int i = 0; i < count; i++) {
            objc_property_t pty = list[I];
            const char *cname = property_getName(pty);
            //获取 属性 名称
            NSString *name = [NSString stringWithUTF8String:cname];
            [arrayM addObject:name];
        }
        free(list);
        //设置关联对象
        objc_setAssociatedObject(self, propertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
        return objc_getAssociatedObject(self, propertiesKey);
    }
    

    4:获取类的ivar成员变量列表

    static const char *ivarsKey = "ivarsKey";
    + (NSArray *)ivarsList {
        unsigned int count = 0;
        Ivar *list = class_copyIvarList([self class], &count);
        NSMutableArray *arrayM = [NSMutableArray array];
        //遍历所有的属性
        for (unsigned int i = 0; i < count; i++) {
            Ivar ivar = list[I];
            const char *cname = ivar_getName(ivar);
            //获取 属性 名称
            NSString *name = [NSString stringWithUTF8String:cname];
            [arrayM addObject:name];
        }
        free(list);
        objc_setAssociatedObject(self, ivarsKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
        return objc_getAssociatedObject(self, ivarsKey);
    }
    

    5:获取类的方法列表

    static const char *methodsKey = "methodsKey";
    + (NSArray *)methodsList {
        unsigned int count = 0;
        Method *list = class_copyMethodList([self class], &count);
        NSMutableArray *arrayM = [NSMutableArray array];
        //遍历所有的属性
        for (unsigned int i = 0; i < count; i++) {
            Method method = list[I];
            SEL selector = method_getName(method);
            NSString *methodName = NSStringFromSelector(selector);
            [arrayM addObject:methodName];
        }
        free(list);
        objc_setAssociatedObject(self, methodsKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
        return objc_getAssociatedObject(self, methodsKey);
    }
    

    6:获取类的协议列表

    static const char *protocolKey = "protocolKey";
    + (NSArray *)protocolList {
        unsigned int count = 0;
        __unsafe_unretained Protocol **list = class_copyProtocolList([self class], &count);
        NSMutableArray *arrayM = [NSMutableArray array];
        //遍历所有的属性
        for (unsigned int i = 0; i < count; i++) {
            Protocol *protocol = list[I];
            const char *cname = protocol_getName(protocol);
            //获取 属性 名称
            NSString *name = [NSString stringWithUTF8String:cname];
            [arrayM addObject:name];
        }
        free(list);
        objc_setAssociatedObject(self, protocolKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
        return objc_getAssociatedObject(self, protocolKey);
    }
    

    7:通过runtime,动态修改变量的值

    
    - (void)dynamicModifyValue {
        Teacher *teacher = [Teacher new];
        teacher.address = @"东北省哈尔滨市";
        NSLog(@"address1 = %@",teacher.address);
        unsigned int count = 0;
        Ivar *ivar = class_copyIvarList([teacher class], &count);
        for (int i = 0; i<count; I++)
        {
            Ivar var = ivar[I];
            const char *varName = ivar_getName(var);
            NSString *proname = [NSString stringWithUTF8String:varName];
            if ([proname isEqualToString:@"_address"])
            {
                //给属性加下划线
                object_setIvar(teacher, var, @"河南省郑州市");
                break;
            }
        }
        NSLog(@"address2 = %@",teacher.address);
    }
    

    8:通过runtime,实现NSCoding的自动归档和解档

    - (void)encodeAndDecoder
    {
        Teacher *teacher = [[Teacher alloc] init];
        teacher.title = @"English Teacher";
        teacher.course = @"English";
        teacher.age = 60;
        teacher.address = @"北京市朝阳区";
        teacher.wage = 8000;
        
        NSString *documentPath  = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
        NSString *filePath = [documentPath stringByAppendingPathComponent:@"userInfo"];
        //模型写入文件
        [NSKeyedArchiver archiveRootObject:teacher toFile:filePath];
        
        //读取
        Teacher *myTeacher = [NSKeyedUnarchiver unarchiveObjectWithFile: filePath];
        NSLog(@"%@", myTeacher);
    }
    //归档
    - (void)encodeWithCoder:(NSCoder *)coder
    {
        unsigned int count = 0;
        Ivar *list = class_copyIvarList([self class], &count);
        //遍历所有的属性
        for (unsigned int i = 0; i < count; I++)
        {
            //取出i位置对应的成员变量
            Ivar ivar = list[I];
            //查看成员变量
            const char *name = ivar_getName(ivar);
            NSString *key = [NSString stringWithUTF8String:name];
            //归档
            id value = [self valueForKey:key];
            [coder encodeObject:value forKey:key];
        }
        free(list);
    }
    
    //解归档
    - (id)initWithCoder:(NSCoder *)coder
    {
        self = [super init];
        if (self)
        {
            unsigned int count = 0;
            Ivar *list = class_copyIvarList([self class], &count);
            //遍历所有的属性
            for (unsigned int i = 0; i < count; I++)
            {
                //取出i位置对应的成员变量
                Ivar ivar = list[I];
                //查看成员变量
                const char *name = ivar_getName(ivar);
                NSString *key = [NSString stringWithUTF8String:name];
                //归档
                id value = [coder decodeObjectForKey:key];
                //设置到成员变量身上
                [self setValue:value forKey:key];
            }
            free(list);
        }
        return self;
    }
    

    9:通过runtime,实现字典转模型的自动转换

    //字典转模型
    + (instancetype)objectWithDict:(NSDictionary *)dict
    {
        // 创建对应模型对象
        id objc = [[self alloc] init];
        unsigned int count = 0;
        // 1.获取成员属性数组
        Ivar *ivarList = class_copyIvarList(self, &count);
        // 2.遍历所有的成员属性名,一个一个去字典中取出对应的value给模型属性赋值
        for (int i = 0; i < count; I++)
        {
            // 2.1 获取成员属性
            Ivar ivar = ivarList[I];
            // 2.2 获取成员属性名 C -> OC 字符串
            NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
            // 2.3 _成员属性名 => 字典key
            NSString *key = [ivarName substringFromIndex:1];
            // 2.4 去字典中取出对应value给模型属性赋值
            id value = dict[key];
            // 获取成员属性类型
            NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            // 二级转换,字典中还有字典,也需要把对应字典转换成模型
            // 判断下value,是不是字典
            if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) { //  是字典对象,并且属性名对应类型是自定义类型
                // user User
                // 处理类型字符串 @\"User\" -> User
                ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
                ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
                // 自定义对象,并且值是字典
                // value:user字典 -> User模型
                // 获取模型(user)类对象
                Class modalClass = NSClassFromString(ivarType);
                // 字典转模型
                if (modalClass)
                {
                    // 字典转模型 user
                    value = [modalClass objectWithDict:value];
                }
            }
            // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
            // 判断值是否是数组
            if ([value isKindOfClass:[NSArray class]])
            {
                // 判断对应类有没有实现字典数组转模型数组的协议
                if ([self respondsToSelector:@selector(arrayContainModelClass)])
                {
                    // 转换成id类型,就能调用任何对象的方法
                    id idSelf = self;
                    // 获取数组中字典对应的模型
                    NSString *type =  [idSelf arrayContainModelClass][key];
                    // 生成模型
                    Class classModel = NSClassFromString(type);
                    NSMutableArray *arrM = [NSMutableArray array];
                    // 遍历字典数组,生成模型数组
                    for (NSDictionary *dict in value)
                    {
                        // 字典转模型
                        id model =  [classModel objectWithDict:dict];
                        [arrM addObject:model];
                    }
                    // 把模型数组赋值给value
                    value = arrM;
                }
            }
            // 2.5 KVC字典转模型
            if (value)
            {
                [objc setValue:value forKey:key];
            }
        }
        // 返回对象
        return objc;
    }
    

    1 isa指针?
    对象的isa指针指向所属的类
    类的isa指针指向了所属的元类
    元类的isa指向了根元类
    根元类指向了自己。

    2 介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?
    category:可以给类或者系统类添加实例方法。
    添加的实例方法,会被动态的添加到类结构里面的methodList列表里面。

    3 给类添加一个属性后,在类结构体里哪些元素会发生变化?

        //实例对象大小
        NSInteger instanceSize = class_getInstanceSize([Test class]);
    

    instance_size :实例的内存大小
    属性列表
    成员变量列表

    4 类方法和实例方法有什么区别?
    调用的方式不同,类方法必须使用类调用,在方法里面不能调用属性,类方法里面也必须调用类方法。存储在元类结构体里面的methodLists里面。

    实例方法必须使用实例对象调用,可以在实例方法里面使用属性,实例方法也必须调用实例方法。存储在类结构体里面的methodLists里面。

    5 runtime如何实现weak变量的自动置nil?
    runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

    6 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
    1.不能向编译后得到的类增加实例变量
    2.能向运行时创建的类中添加实例变量
    解释:
    (1)编译后的类已经注册在runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,runtime会调用class_setvarlayout或class_setWeaklvarLayout来处理strong weak引用.所以不能向存在的类中添加实例变量
    (2)运行时创建的类是可以添加实例变量,调用class_addIvar函数.但是的在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上.

    7 objc在向一个对象发送消息时,发生了什么?
    根据对象的isa指针找到类对象id,在查询类对象里面的methodLists方法函数列表,如果没有找到,在沿着superClass,寻找父类,再在父类methodLists方法列表里面查询,最终找到SEL,根据id和SEL确认IMP(指针函数),发送消息;

    8 什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?
    当发送消息的时候,我们会根据类里面的methodLists列表去查询我们要动用的SEL,当查询不到的时候,我们会一直沿着父类查询,当最终查询不到的时候我们会报unrecognized selector错误
    当系统查询不到方法的时候,会调用+(BOOL)resolveInstanceMethod:(SEL)sel动态解释的方法来给我一次机会来添加,调用不到的方法。或者我们可以再次使用-(id)forwardingTargetForSelector:(SEL)aSelector重定向的方法来告诉系统,该调用什么方法,一来保证不会崩溃。

    9 为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?
    默认情况下,block里面的变量,拷贝进去的是变量的值,而不是指向变量内存的指针。
    当使用__block修饰后的变量,拷贝到block里面的就是指向变量的指针,所以我们就可以修改变量的值。

    func propertyList() {
        //属性的数量
        var count: UInt32 = 0
        let list = class_copyPropertyList(UITextField.self, &count)
        var array = [String]()
        for i in 0..<Int(count) {
            //根据下标获取属性
            guard let pty = list?[i] else { continue }
            //获取属性名称C语言字符串
            let cname = property_getName(pty)
            //将属性名称,转换为String
            guard let name = String(utf8String: cname) else { continue }
            array.append(name)
        }
        //释放C语言对象
        free(list)
        print("array = \(array)")
        //class_copyIvarList(<#T##cls: AnyClass?##AnyClass?#>, <#T##outCount: UnsafeMutablePointer<UInt32>?##UnsafeMutablePointer<UInt32>?#>)
        //class_copyMethodList(<#T##cls: AnyClass?##AnyClass?#>, <#T##outCount: UnsafeMutablePointer<UInt32>?##UnsafeMutablePointer<UInt32>?#>)
        //class_copyIvarList(<#T##cls: AnyClass?##AnyClass?#>, <#T##outCount: UnsafeMutablePointer<UInt32>?##UnsafeMutablePointer<UInt32>?#>)
    }
    

    10 rumtime、runloop原理和实用场景
    runloop运行循环、跑圈。其实也就是一个循环跑圈,用来处理线程里面的事件和消息。

    runloop和线程的关系:每条线程都有唯一的一个与之对应的runloop对象。每个线程如果想继续运行,不被释放,就必须有一个runloop来不停的跑圈,以来处理线程里面的各个事件和消息。

    线程 =====》 runloop对象(处理线程里面的各个事件和消息)
    主线程默认是开启一个runloop。也就是这个runloop才能保证我们程序正常的运行。
    子线程是默认没有开启runloop的。

    runloop基本作用:保持程序的持续运行,处理App中的各种事件,节省CPU资源,提高程序性能。
    runloop的生命周期:Runloop在第一次获取时创建,在线程结束时销毁。
    如何让子线程不死:给这条子线程开启一个runloop

    11 runloop的mode是用来做什么的?有几种mode?
    model:是runloop里面的模式,不同的模式下的runloop处理的事件和消息有一定的差别。
    系统默认注册了5个Mode:
    (1)kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
    (2)UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
    (3)UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
    (4)GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
    (5)kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
    注意iOS 对以上5中model进行了封装
    NSDefaultRunLoopMode;
    NSRunLoopCommonModes

    12 为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?
    NSTimer对象是在 NSDefaultRunLoopMode下面调用消息的,但是当我们滑动scrollview的时候,NSDefaultRunLoopMode模式就自动切换到UITrackingRunLoopMode模式下面,却不可以继续响应NStime发送的消息。所以如果想在滑动scrollview的情况下面还调用NSTimer的消息,我们可以把NSRunLoop的模式更改为NSRunLoopCommonModes。
    类结构

    13 KVO的使用?实现原理?(为什么要创建子类来实现)
    KVO(Key Value Observe)
    每个对象都有isa 指针,指向该对象的类,它告诉 Runtime 系统这个对象的类是什么。所以对象注册为观察者时,isa指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。

    14 KVC的使用?
    KVC(Key-value coding)键值编码
    可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定。

    15 KVC实现原理?(KVC拿到key以后,是如何赋值的?
    setValue forKey 通过key进行赋值
    valueForKey key 直接通过key来取值


    image.png

    16 运行时能增加成员变量么?能增加属性么?如果能,如何增加?如果不能,为什么?
    可以添加属性的,但必须我们实现它的getter和setter方法、同时没有生成带下滑线同名的成员变量。

    相关文章

      网友评论

          本文标题:Runtime

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