KVO-基本使用方法-底层原理探究-自定义KVO-对容器类的监听
书读百变,其义自见!
将KVO形式以代码实现呈现,通俗易懂,更容易掌握 :GitHub -链接如果失效请自动搜索:https://github.com/henusjj/KVO_base
代码中有详细的注释
一、KVO-常用方法
//注册
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; //监听方法
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context; //移除
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; //监听模式(手动,自动),默认是自动Yes
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key; //属性的依赖,返回监听属性类的集合 +(NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key;
二、KVO-基本使用
KVO监听属性值变化,从而做业务逻辑处理,监听属性变化,我们需要实现三步走
1.注册监听对象
2.实现监听方法
3.移除监听对象,避免crash
//
// ViewController.m
// KVO-基本用法
//
// Created by GuoYanjun on 2019/1/9.
// Copyright © 2019年 shiyujin. All rights reserved.
// #import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property(nonatomic,strong)Person *p;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
_p=[[Person alloc]init];
// 注册
[_p addObserver:self forKeyPath:NSStringFromSelector(@selector(name)) options:NSKeyValueObservingOptionNew context:nil]; } //监听方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ // 自动模式
static int a =;
_p.name = [NSString stringWithFormat:@"%d",a++]; // 手动
// [_p willChangeValueForKey:@"name"];
// _p.name = [NSString stringWithFormat:@"%d",a++];
// [_p didChangeValueForKey:@"name"];
// }
-(void)dealloc{
[_p removeObserver:self forKeyPath:@"name"];
}
@end
三、底层原理探究
这里我总结三个地方
1.创建一个子类,名字是:NSKVONotifying_Person ,person是本项目中的类
这里为什么是子类不是分类呢,这里说明以下,如果使用分类 ,他会覆盖set方法,导致原set方法中的逻辑处理失效
2.重写了set方法
这里的重写不是重写父类的的set,而是重写子类的
3.外界改变isa指针
此处可以在注册方法打一个断点,观察其isa指针的变化
self->_p->isa:Person
改变为
self->_p->isa:
NSKVONotifying_Person
四、对容器的监听
对于数组,我们添加元素的时候,都是addObject........
但是我们知道,KVO是针对set方法从而监听的,因为,
addObject........是不会响应的,此时,苹果给我提供了
// [_p.arry addObject:[NSString stringWithFormat:@"%d",a++]];//这一步不会触发监听方法因为监听是监听set的方法,addObject不是set方法 //解决方法
NSMutableArray *tenp =[_p mutableArrayValueForKey:@"arry"];
[tenp addObject:[NSString stringWithFormat:@"%d",a++]];
五、自定义KVO
此处需要用到Runtime,KVO文档中,我们会发现,相关方法是在NSobjet的分类,所以
1.创建一个NSObject的分类,定义注册方法
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface NSObject (JK_KVO)
- (void)JK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end NS_ASSUME_NONNULL_END
2.实现该方法
#import "NSObject+JK_KVO.h"
#import <objc/message.h> @implementation NSObject (JK_KVO) - (void)JK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{ // 1.创建一个类 -- self.class 就是Person
NSString *oldname = NSStringFromClass(self.class);
NSString *newNem = [@"JKKVO_" stringByAppendingString:oldname];
Class myclass = objc_allocateClassPair(self.class, newNem.UTF8String, );
// 注册类
objc_registerClassPair(myclass); // 2.重写子类set方法 -- 所谓的重写就是给子类添加f这个方法 setName,因为子类没有父类的setName方法!!!
/* class :给那个类添加方法
*sel:方法编号
*imp :方法实现(函数指针)
*type :返回值类型
*/
class_addMethod(myclass, @selector(setName:), (IMP)setName, "v@:@"); // 3.修改isa指针
object_setClass(self, myclass); // 4.将观察保存到当前对象
objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_ASSIGN); } void setName(id self,SEL _cmd,NSString *newName){
NSLog(@"来了--%@",newName);
// 调用父类的setName方法
Class class =[self class];
object_setClass(self, class_getSuperclass(class));//改成父类 objc_msgSend(self,@selector(setName:),newName);//发送消息给父类 // 观察者
id observer = objc_getAssociatedObject(self, @"observer"); if (observer) {
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"new:":newName,@"kind:":@""},nil);
} // 改回子类
object_setClass(self, class);
}
@end
3.调用
[_p JK_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
----!!!!!!!!
OK,结束。代码已经整理完毕。下班
KVO-基本使用方法-底层原理探究-自定义KVO-对容器类的监听的更多相关文章
- jQuery封装自定义事件--valuechange(动态的监听input,textarea)之前值,之后值的变化
jQuery封装自定义事件--valuechange(动态的监听input,textarea)之前值,之后值的变化 js监听输入框值的即时变化 网上有很多关于 onpropertychange.oni ...
- js事件底层原理探究
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...
- vue自定义组件添加原生事件监听
注:全局或局部注册的组件称为子组件,其中声明的组件名称(如下demo中的child)是一个自定义组件 Demo1-直接给父组件添加事件监听 <!DOCTYPE html> <html ...
- Java基础---Java---IO流-----LineNumberReader方法及原理、自定义一个LineNumberReader、字节流、图片复制、mp3复制、
LineNumberReader 跟综行号的缓冲字符输入流,些类定义了setLineNumber(int)和getLineNumber(int),它们可分别用于设置和获取当前行号 import jav ...
- 虚拟机研究系列-「GC本质底层机制」SafePoint的深入分析和底层原理探究指南
SafePoint前提介绍 在高度优化的现代JVM里,Safepoint有几种不同的用法.GC safepoint是最常见.大家听说得最多的,但还有deoptimization safepoint也很 ...
- JMM和Volatile底层原理分析
JMM和volatile分析 1.JMM:Java Memory Model,java线程内存模型 JMM:它是一个抽象的概念,描述的是线程和内存间的通信,java线程内存模型和CPU缓存模型类似,它 ...
- synchronized底层原理
synchronized底层语义原理 Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现. 在 Java 语言中,同步用的最多的地方可能是被 syn ...
- Unity3D热更新之LuaFramework篇[04]--自定义UI监听方法
时隔一个多月我又回来啦! 坚持真的是很难的一件事,其它事情稍忙,就很容易说服自己把写博客的计划给推迟了. 好在终于克服了自己的惰性,今天又开始了. 本篇继续我的Luaframework学习之路. 一. ...
- DownEditTextView【自定义Edittext对Android 软键盘向下的监听】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 记录自定义EditText控件实现监听软键盘隐藏事件的功能.基本上和参考资料相同. 效果图 代码分析 自定义EditText子 ...
随机推荐
- Spring 中的文件上传与下载控制
先创建根应用上下文配置,WebDemo/src/main/java/com/seliote/webdemo/config/RootContextConfig.java package com.seli ...
- idea 常用设置
1.修改为Eclipse快捷键 File -> Settings -> Keymap => Keymaps改为 Eclipse copy 2.显示行号: File -> S ...
- 关于springboot 打包问题 jar包和 war包
起因:项目开发完成 需要打包部署了 发现自己不会打包 那么开始网上学习打包? 那么怎么来打包那? 我们以前没有采用springboot 时候我们都是直接将项目打成war包形式 然后放到tomc ...
- 【Spring实战】----开篇(包含系列目录链接)
[Spring实战]----开篇(包含系列目录链接) 置顶2016年11月10日 11:12:56 阅读数:3617 终于还是要对Spring进行解剖,接下来Spring实战篇系列会以应用了Sprin ...
- 预装win8的笔记本如何重装win7
测试电脑联想T440. 开机按F1,然后Enter,进入Bios设置. 先关闭Secure Boot,然后设置为Legacy Boot. 之后才能设置U盘为第一启动盘. 进入老毛桃的PE系统,使用Di ...
- 『JavaScript』封装
封装可以被定义为对对象的内部数据表现形式和实现细节进行隐藏.通过封装可以强制实施信息隐藏. 在JavaScript中,并没有显示的声明私有成员的关键字等.所以要想实现封装/信息隐藏就需要从另外的思路出 ...
- MQTT 开源代理mosquitto的网络层封装相当sucks
最近学习MQTT协议,选择了当前比较流行的MQTT Broker “mosquitto”,但是在阅读代码过程中发现其网络底层库封装的相当差劲. 对于MQTT协议的变长头长度的读取上,基本上采取每次一个 ...
- 【多校联合】(HDU6095)Rikka with Competition
题意:给定$n$个数,代表$n$个选手的能量高低,现在再给一个$k$,任意在$n$个选手中挑取两个选手比赛,如果$|a_i−a_j|>K$,那么能量高的选手获胜,另一个将被淘汰,否则两个人都有机 ...
- ubuntu 把软件源修改为国内源
国内有很多Ubuntu的镜像源,比如:阿里源.网易源等,还有很多教育网的源,比如:清华源.中科大源等. 这里以清华源为例讲解如何修改Ubuntu 18.04里面默认的源. 修改步骤 第一步:备份原始源 ...
- 自动化测试--封装JDBCUnit
在进行测试的时候,经常需要对数据库进行操作.我们知道,通过代码与数据库交互,需要以下几步: 1.加载驱动 之前有盆友问我,为什么Selenium操作浏览器的时候,非要下载浏览器驱动?为啥对数据库进行操 ...