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