Runtime 的应用(四)

前面我们说到:
Runtime 消息传递机制
Runtime 消息转发机制
Runtime 交换方法
今天我们来谈谈Runtime的一些常用的用法

1.KVO的底层实现

KVO:Key-Value-Observe 键值监听,动态监听属性的变化
具体用法

//这里以监听Person的age属性为例
self.person = [Person new];
self.person.age = 21;
//此行注册监听 
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];

然后实现如下方法,当每次age改变,都会调用下面的方法

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    NSLog(@"%@监听到%@属性的改变为%@",object,keyPath,change);
}

那么KVO是怎么实现的呢?

1、自定义当前类的子类
2、重写set方法,在内部恢复父类的做法,通知观察者
3、通过修改当前类对象的isa指针,指向自定义的子类来让外界调用自定义类的方法
//此行注册监听后,利用Runtime在程序运行过程中,改变了这个对象的类型:NSKVONotifing_Person  
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];

而生成的NSKVONotifing_Person类中会重写age的set方法

/** 重写set方法 */
-(void)setAge:(int)age{
    [super setAge:age];
    //这两个方法会默认调用观察者的observeValueForKeyPath方法
    [self willChangeValueForKey:@"age"];
    [self didChangeValueForKey:@"age"];
}

原来是在重写的set方法里面做了手脚。

如下是没有设置监听age属性时,对Person的断点调试


image.png

如下是设置监听age属性后,对Person的断点调试


image.png

2.获取对象的所有(包括私有)属性、变量、方法

2.1获取对象的所有属性

- (IBAction)allProperty:(id)sender {
    
    // 定义变量
    unsigned int count;
    // 运行时获取私有属性列表
    objc_property_t *properties = class_copyPropertyList([Person class], &count);

    for (int index = 0; index < count; ++index) {
        // 声明属性
        objc_property_t property = properties[index];

        // 指针数组. 通过property_getName获取属性的名称
        const char *cName = property_getName(property);
        // UTF8转码
        NSString *name = [NSString stringWithUTF8String:cName];
        NSLog(@"name -----> %@", name);
    }
    
    free(properties);
}

2.2获取对象的所有属性+变量

- (IBAction)allIvar:(id)sender {
    
    unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i++) {
        //拿出属性的名称
        Ivar ivar = ivars[I];
        const char * name = ivar_getName(ivar);
        NSString * KEY = [NSString stringWithUTF8String:name];
        NSLog(@"name -----> %@", KEY);
    }
    
    //c语言里面需要释放指针!!
    free(ivars);
    
}

2.3获取对象的所有方法

- (IBAction)allMethod:(UIButton *)sender {
    
    // 定义变量
    unsigned int count;
    // 运行时获取私有方法列表
    Method *methods = class_copyMethodList([Person class], &count);
    
    for (int index = 0; index < count; ++index) {
        Method method = methods[index];
        
        // 这步完了之后, 可以获取到方法的地址
        SEL selector = method_getName(method);
        
        NSString *name = NSStringFromSelector(selector);
        // 确定方法类型
        const char *type = method_getTypeEncoding(method);
        NSLog(@"fun-----%@,  type-----> %s",name,type);
    }
    free(methods);
}

3.序列化与反序列化

还记得当初代码里面关于序列化与反序列化的代码一堆一堆的

Runtime 的应用(四)_第1张图片
image.png

如上是属性比较少的,我看到过多的有上百行的,简直要翻好几页才能看完,更何况是写呢?
你是不是也想过简化这段代码,现在,可以用runtime来简化

#import "CodingModel.h"
#import 
@implementation CodingModel
- (void)encodeWithCoder:(NSCoder *)coder
{
    unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i < count; i++) {
        //拿出属性的名称
        Ivar ivar = ivars[i];
        const char * name = ivar_getName(ivar);
        NSString * KEY = [NSString stringWithUTF8String:name];
        //取出属性的值
        id value = [self valueForKey:KEY];
        //归档
        [coder encodeObject:value forKey:KEY];
    }
    
    //c语言里面需要释放指针!!
    free(ivars);
}

//解档!!
- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super init];
    if (self) {
        unsigned int count = 0;
        Ivar * ivars = class_copyIvarList([self class], &count);
        for (int i = 0; i < count; i++) {
            //拿出属性的名称
            Ivar ivar = ivars[i];
            const char * name = ivar_getName(ivar);
            NSString * KEY = [NSString stringWithUTF8String:name];
            //解档
            id value = [coder decodeObjectForKey:KEY];
            //将值设置到属性上 KVC
            [self setValue:value forKey:KEY];
        }
        
        //c语言里面需要释放指针!!
        free(ivars);
    }
    return self;
}
@end

哈哈,总共20行,搞定
你可以写个基类,在需要用到序列化的Model里直接继承这个基类。搞定

文章就到这里
提供一份Demo,希望对你有帮助
想了解更多RunTime,可以参考:
Runtime 消息传递机制
Runtime 消息转发机制
Runtime 交换方法

写在最后:
希望这篇文章对您有帮助。当然如果您发现有可以优化的地方,希望您能慷慨的提出来。最后祝您工作愉快!

你可能感兴趣的:(Runtime 的应用(四))