美文网首页
KVC、KVO、RunTime、RunLoop

KVC、KVO、RunTime、RunLoop

作者: 沐梓弦乐 | 来源:发表于2018-11-19 16:40 被阅读0次

    KVC-->Key-Value Coding: 键值编码 (KVC)

    原理:KVC运用了一个isa-swizzling技术. isa-swizzling就是类型混合指针机制, 将2个对象的isa指针互相调换, 就是俗称的黑魔法.

    KVC主要通过isa-swizzling, 来实现其内部查找定位的. 默认的实现方法由NSOject提供

      isa指针, 如其名称所指,(就是is a kind of的意思), 指向分发表对象的类. 该分发表实际上包含了指向实现类中的方法的指针, 和其它数据。

    KVC是一种非正式的Protocol,提供一种机制来间接访问对象的属性

    获取值方式:

    1.valueForKey: 传入NSString属性的名字。

    2.valueForKeyPath: 属性的路径,xx.xx

    3.valueForUndefinedKey 默认实现是抛出异常,可重写这个函数做错误处理

    修改值方式:

    1.setValue:forKey:

    2.setValue:forKeyPath:

    3.setValue:forUnderfinedKey:

    4.setNilValueForKey: 对非类对象属性设置nil时调用,默认抛出异常。

    举例:Person对象有2个属性  name(NSSting),age(NSInteger) KVC赋值代码如下

     Person *person = [[Person alloc]init];     //KVC进行赋值    

    [person setValue:[NSNumber numberWithInteger:18] forKey:@"age"];    

    [person setValue:@"Jany" forKey:@"name"];

    KVC字典转模型赋值

    //KVC 字典转模型 字典要和model中的属性一一对应  为防止闪退报错重写model中setValue: forUndefinedKey:   方法

        NSDictionary *dict = @{@"name":@"Jenny",@"age":@26};

        [person setValuesForKeysWithDictionary:dict];

    KVO--> Key-Value Observing 键值观察(KVO), KVO是观察者模式的一种应用

    原理:通过对某个对象的某个属性添加观察者,若该属性值改变,就会调用observeValueForKeyPath:方法

    给上面的Person对象person 添加观察者  观察name属性变化 代码如下

    //KVO 添加观察者  观察person对象中属性name的值改变

    [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];

    观察者监听到值改变了回调方法

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {

        NSLog(@"keyPath: %@", keyPath);

        NSLog(@"object: %@", object);

        NSLog(@"change: %@", change);

        NSLog(@"context: %@", context);

    }

    全代码如下

    #import "Person.h"

    #import <objc/message.h>

    @interface ViewController ()

    @end

    @implementation ViewController

    - (void)viewDidLoad {

        [super viewDidLoad];

        Person *person = [[Person alloc]init];

        //KVO 添加观察者  观察person对象中属性name的值改变

        [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];

        //KVC进行赋值

        [person setValue:[NSNumber numberWithInteger:18] forKey:@"age"];

        [person setValue:@"Jany" forKey:@"name"];

        //KVC 字典转模型 字典要和model中的属性一一对应  为防止闪退报错重写model中setValue:(id)value forUndefinedKey:   方法

        NSDictionary *dict = @{@"name":@"Jenny",@"age":@26};

        [person setValuesForKeysWithDictionary:dict];

        NSLog(@"%zd",person.age);

    }

    // 观察者监听到之后回调方法

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {

        NSLog(@"keyPath: %@", keyPath);

        NSLog(@"object: %@", object);

        NSLog(@"change: %@", change);

        NSLog(@"context: %@", context);

    }

    KVO使用场景:

    1、下拉刷新、下拉加载监听UIScrollerView的ContentOffset方法

    2、webView混排监听contentsize

    3、监听模型属性实时更新UI

    4、监听控制器frame改变,实现抽屉效果

    RunTime

    简介:RunTime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制。

    作用:

    1.发送消息。 objc_msgSend只有对象才能发送消息,因此以objc开头。使用消息机制前提必须导入#import <objc/message.h>

    举例:Person中声明两个方法-(void)eat;  +(void)eat;

    调用对象方法 

    [person eat];

    本质让对象发送消息  objc_msgSend(person,@selector(eat));

    调用类方法:两种方式

    1.[Person eat]; 

    2.[[Person class] eat];

    本质让类对象发送消息 objc_msgSend([Person class],@selector(eat));

    2.交换方法

    开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并保持原有的功能。

    方式:

    1.集成系统的类,重写方法

    2.使用runtime,交换方法

    举例:创建UIImage类别Image  

    .m中实现方法交换代码

    #import <objc/message.h>

    @implementation UIImage (Image)

    + (void)load{

        //获取imageWithName方法地址

        Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));

        //获取imageName方法地址

        Method imageName = class_getClassMethod(self, @selector(imageNamed:));

        method_exchangeImplementations(imageWithName, imageName);

    }

    // 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super.

    // 既能加载图片又能打印

    + (instancetype)imageWithName:(NSString *)name

    {

        // 这里调用imageWithName,相当于调用imageName

        UIImage *image = [self imageWithName:name];

        if (image == nil) {

            NSLog(@"加载空的图片");

        }

        return image;

    }

    实现:

     [UIImage imageNamed:@"123"];  //方法交换imageNamed-->imageWithName

    3.给分类添加属性

    说明:可以为已有的类添加方法,但是却不能直接添加属性,因为即使你添加了@property,它既不会生成实例变量,也不会生成setter、getter方法,即使你添加了也无法使用。所以我们首先需要自己去添加setter、getter方法,这个好办,直接在.m文件里加就可以了,但是要真正添加可以使用的属性,还需要利用Runtime来关联对象

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

    实现:创建一个NSObject类别  名为Property,添加属性为name   @property (nonatomic,strong)NSString *name;

    @interface NSObject (Property)

    @property (nonatomic,strong)NSString *name;

    @end

    #import <objc/message.h>

    // 定义关联的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);

    }

    调用:

    //runtime给NSObject对象添加属性name(通过类别property)

        NSObject *objc = [[NSObject alloc]init];

        objc.name = @"Jany";

        NSLog(@"%@",objc.name);

    RunTime还有些其他的功能

    4.动态添加方法

    5.字典转模型 

    RunLoop  

    说明:

    一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出.

    基本作用:

    1.保持程序的持续运行

    2.处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)

    3.节约CPU资源,提高程序性能:该做事时做事,该休息时休息

    . . . . . . 

    main函数中的RunLoop

    int main(int argc, char * argv[]) {

        @autoreleasepool {

            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

        }

    }

    UIApplicationMain函数内部就启动了一个RunLoop,所以UIApplicationMain函数一直没有返回,保持了程序的持续运行,这个默认启动的RunLoop是跟主线程相关联的

    RunLoop与线程

    1.每条线程都有唯一的一个与之对应的RunLoop对象

    2.主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动去创建

    3.RunLoop在第一次获取时创建,在线程结束时销毁

    获得RunLoop对象

    1.Foundation

    NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];//获得主线程的RunLoop对象

    NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];//获得当前线程的RunLoop对象

    2.Core Foundation

    CFRunLoopGetMain()

    CFRunLoopGetCurrent()

    NSRunLoop使用须知

    1.NSLog(@"%@",[NSRunLoop currentRunLoop]);打印当前线程的RunLoop,懒加载模式,一条线程对应一个RunLoop对象,有返回,没有创建,主线程的RunLoop默认创建,子线程的RunLoop需要手动创建,[NSRunLoop currentRunLoop],同一个线程中若是创建多个RunLoop,则返回的都是同一个RunLoop对象,一个RunLoop里会有多个mode运行模式(系统提供了5个),但运行时只能指定一个RunLoop,若是切换RunLoop,则需要退出当前的RunLoop

    2.定时器NSTimer问题:1:若是创建定时器用timerWithTimeInterval,则需要手动将定时器添加到NSRunLoop中,指定的运行模式为default,但是如果有滚动事件的时候,定时器就会停止工作。解决办法:更改NSRunLoop的运行模式,UITrackingRunLoopMode界面追踪,此模式是当只有发生滚动事件的时候才会开启定时器。若是任何时候都会开启定时器: NSRunLoopCommonModes,

     NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode

     占用,标签,凡是添加到NSRunLoopCommonModes中的事件爱你都会被同时添加到打上commmon标签的运行模式上

    3. 1:scheduledTimerWithTimeInterval此方法创建的定时器默认加到了NSRunLoop中,并且设置运行模式为默认。 2:若是想在子线程开启NSRunLoop:需要手动开启:NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];等到线程销毁的时候currentRunloop对象也随即销毁。2:在子线程的定时器,需要手动加入到runloop:不要忘记调用run方法

    代码案例:

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

        [self timer1];

    //    [self timer2];

    }

    //在runloop中有多个运行模式,但是runloop只能选择一种模式运行

    //mode里面至少要有一个timer或者是source

    -(void)run

    {

        NSLog(@"子线程开启Runloop中添加timer事件,让线程持续存");

    }

    //[NSTimer scheduledTimerWithTimeInter..]此方法创建的定时器默认加到了NSRunLoop中,并且设置运行模式为默认

    -(void)timer2

    {

        NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];

        //该方法内部自动添加到runloop中,并且设置运行模式为默认

        [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

        //开启runloop

        [currentRunloop run];

    }

    //timerWithTimeInterval,需要手动将定时器添加到NSRunLoop中,指定的运行模式为default

    - (void)timer1{

        NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];

        [currentRunloop addTimer:[NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES] forMode:NSDefaultRunLoopMode];

        //控制循环时间

    //    [currentRunloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];

    }

    相关文章

      网友评论

          本文标题:KVC、KVO、RunTime、RunLoop

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