iOS KVO底层原理
KVO(Key-Value observing),俗称键值观察
,KVO是一种观察机制,允许将其他对象指定属性的更改通知给对象
在KVO的官方文档上有这么一句话:理解KVO之前,必须先理解KVC
In order to understand key-value observing, you must first understand key-value coding.
KVC是键值编码
,在对象创建完成后,可以动态的给对象属性赋值
,而KVO是键值观察
,提供了一种监听机制
,当指定的对象的属性被修改后,则对象会收到通知,所以可以看出KVO是基于KVC的基础上对属性动态变化的监听
KVO和NSNotificationCenter的区别
-
相同点
- 1、都是
观察者模式
,用于监听 - 2、都能实现
一对多
- 1、都是
-
不同点
-
KVO只能用于监听对象属性的变化
,记录新旧值得变化
,并且都是通过NSString来查找的,编译器不会帮你检测对错和补全 -
NSNotificationCenter
的post
我们可以自己控制,KVO
是由系统控制
-
KVO的使用
1、基础使用
- 注册观察者
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
- 监听回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"%@",change);
}
}
- 移除观察者
[self.person removeObserver:self forKeyPath:@"nick" context:NULL];
2、context参数的使用
官方文档说明:
image.png
翻译:
addObserver:forKeyPath:options:context:方法中的上下文context指针包含任意数据,这些数据将在相应的更改通知中传递回观察者。可以通过指定context为NULL,从而依靠keyPath即键路径字符串传来确定更改通知的来源,但是这种方法可能会导致对象的父类由于不同的原因也观察到相同的键路径而导致问题。所以可以为每个观察到的keyPath创建一个不同的context,从而完全不需要进行字符串比较,从而可以更有效地进行通知解析
总结:
通俗的说,context上下文的主要作用是区分不同对象的同名属性
,从而在KVO的回调中可以直接使用context来区分,可以提高性能和代码的可读性
3、移除KVO
官方文档说明
image.png
翻译:
-
如果观察者还没有注册就去移除,会导致
NSRangeException
,当你调用一次addObserver:forKeyPath:options:context:
时,你必须进行一次removeObserver:forKeyPath:context:
调用。如果在你的应用中不可行,可以在try / catch块
内调用removeObserver:forKeyPath:context:
处理异常 -
观察者在释放时不会自动移除自身
,被释放对象继续发送通知,而不会理会观察者状态。但是,一个更改的通知,和任何其他消息一样,发送到已释放的对象,会触发内存访问异常,因此,我们可以在确保观察者在内存消失之前将自己移除
-
协议没有提供询问对象是观察者还是被观察者的方法。构造代码为了避免发布相关的错误。一个典型的模式就是在观察者初始化期间注册为观察者(例如在
init或者viewDidLoad中注册为观察者
),在释放期间取消注册(通常在dealloc移除观察者
),确保正确配对和有序添加、删除消息,并确保观察者在从内存中释放之前取消注册
总结:
KVO在注册和取消观察者必须是成对出现
,如果只注册,不移除会出现类似野指针的崩溃
,
崩溃的原因:由于第一次注册观察者后没有移除
,再次进入界面的时候会第二次注册,导致重复注册观察者
,而第一次的通知对象还在内存中,没有释放,如果此时监听到属性值的变化,会出现找不到第一次的通知对象,只能找到现有的通知对象
,所有会导致类似野指针的崩溃
,即一直保持这一个野通知,一直在监听
4、KVO的自动触发和手动触发
- 自动开关,返回NO,就监听不到,返回YES,表示监听
// 自动开关
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
return YES;
}
- 自动开关关闭的时候,可以通过手动开关监听
- (void)setName:(NSString *)name{
//手动开关
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
5、KVO一对多观察
一对多:通过注册一个观察者来监听多个属性的变化
比如目前有一个需求,需要根据总的下载量totalData
和当前下载量currentData
来计算当前的下载进度currentProcess
,实现有两种方式
-
1、分别观察总的下载量
totalData
和当前下载量currentData
两个属性,来计算当前的下载进度currentProcess
-
2、实现
keyPathsForValuesAffectingValueForKey
方法,将两个合并为一个,类似RAC中的信号合并
//1、合二为一的观察方法
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"currentProcess"]) {
NSArray *affectingKeys = @[@"totalData", @"currentData"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
//2、注册KVO观察
[self.person addObserver:self forKeyPath:@"currentProcess" options:(NSKeyValueObservingOptionNew) context:NULL];
//3、触发属性值变化
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.currentData += 10;
self.person.totalData += 1;
}
//4、移除观察者
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"currentProcess"];
}
6、KVO观察 可变数组
因为KVO是在KVC基础上的,所有当可变数组通过addObject:
方法直接添加数据是不会调用setter
方法的,因此不会触发KVO的回调
在KVO的官方文档中,针对可变数组
的集合类型,我们可以通过mutableArrayValueForKey
方法将元素添加到数组中
//1、注册可变数组KVO观察者
self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
[self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL];
//2、KVO回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
//3、移除观察者
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"dateArray"];
}
//4、触发数组添加数据
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"];
}
运行结果
image.png
其中kind
表示键值变化的类型
,是一个枚举,有以下四种
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
NSKeyValueChangeSetting = 1,//设值
NSKeyValueChangeInsertion = 2,//插入
NSKeyValueChangeRemoval = 3,//移除
NSKeyValueChangeReplacement = 4,//替换
};
KVO底层原理
1、KVO只能对属性进行观察
KVO对成员变量不观察
,只对属性观察
,属性和成员变量的区别在于属性多一个 setter 方法,而KVO恰好观察的是setter 方法
2、中间类
根据官方文档的描叙,在注册KVO观察者后,观察对象的isa指针指向会发生改变
-
注册观察者之前
image.png -
注册观察者之后
image.png
在注册观察者后,实例对象的isa指针
指向由LGPerson
类变为了NSKVONotifying_LGPerson
中间类,即实例对象的isa指针指向发生了变化
2-1、判断中间类是否是派生类 即子类?
#pragma mark - 遍历类以及子类
- (void)printClasses:(Class)cls{
// 注册类的总数
int count = objc_getClassList(NULL, 0);
// 创建一个数组, 其中包含给定对象
NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
// 获取所有已注册的类
Class* classes = (Class*)malloc(sizeof(Class)*count);
objc_getClassList(classes, count);
for (int i = 0; i<count; i++) {
if (cls == class_getSuperclass(classes[i])) {
[mArray addObject:classes[i]];
}
}
free(classes);
NSLog(@"classes = %@", mArray);
}
//********调用********
[self printClasses:[LGPerson class]];
我们可以通过遍历LGPerson的子类可以判断出NSKVONotifying_LGPerson
是LGPerson的子类
2-2、中间类中有什么?
#pragma mark - 遍历方法-ivar-property
- (void)printClassAllMethod:(Class)cls{
unsigned int count = 0;
Method *methodList = class_copyMethodList(cls, &count);
for (int i = 0; i<count; i++) {
Method method = methodList[i];
SEL sel = method_getName(method);
IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
}
free(methodList);
}
//********调用********
[self printClassAllMethod:objc_getClass("NSKVONotifying_LGPerson")];
我们通过遍历NSKVONotifying_LGPerson
的方法列表,可以得出NSKVONotifying_LGPerson
类中有四个方法:setNickName 、 class 、 dealloc 、 _isKVOA
,由于继承
的方法不会再子类中遍历出来,所有我们可以得出下面结论👇
-
NSKVONotifying_LGPerson
重写了父类LGPerson
的setNickName
方法 -
dealloc
是释放方法 -
_isKVOA
判断当前是否是kvo类
-
NSKVONotifying_LGPerson
中间类重写了基类NSObject
的class 、 dealloc 、 _isKVOA
方法
2-3、dealloc中移除观察者后,isa指向是谁,以及中间类是否会销毁?
-
移除观察者之前:实例对象的isa指向仍是NSKVONotifying_LGPerson中间类
image.png -
移除观察者之后:实例对象的isa指向更改为LGPerson类
image.png
所有在移除KVO观察者后,isa
的指向由NSKVONotifying_LGPerson
重新指向LGPerson
并且在dealloc方法中移除观察者之后,中间类NSKVONotifying_LGPerson
并不会销毁,还吃存在内存中,这为了重用
,即中间类注册到内存中,为了考虑后续的重用问题,所以中间类一直存在
中间类的总结:
- 在注册KVO观察者后,实例对象的
isa
由原来类
指向中间类
- 中间类重写了
属性的setter方法、class、dealloc、_isKVOA
- 在移除KVO观察者后,实例对象
isa
由中间类
指向原来类
-
中间类
一旦创建,为了后续的重用
,就一直存在内存中,不会被销毁
自定义KVO
自定义KVO的流程和系统一直,只是在系统基础上做了部分优化
- 通过
函数式编程
,将注册、响应和block方法结合在一起 - 实现
KVO自动销毁机制
大致流程:
- 注册观察者 和 响应
- 1、验证是否存在setter方法
- 2、保存信息
- 3、动态生成子类,重写
class
和setter
方法 - 4、在子类的setter方法中向父类发送消息
- 5、让观察者响应
- 移除观察者
- 1、更改isa的指向,指回原来类
- 2、重写子类的
dealloc
方法
准备条件:创建NSObject类的分类YPKVO
注册观察者
- 1、判断当前观察值keyPath的setter方法是否存在
#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath
{
Class superClass = object_getClass(self);
SEL setterSelector = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superClass, setterSelector);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"YPKVO - 没有当前%@的setter方法", keyPath] userInfo:nil];
}
}
- 2、动态生成子类,将需要重写的class方法添加到中间类中
#pragma mark - 动态生成子类
- (Class)createChildClassWithKeyPath:(NSString *)keyPath
{
//获取原本的类名
NSString *oldClassName = NSStringFromClass([self class]);
//拼接新的类名
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kYPKVOPrefix,oldClassName];
//获取新类
Class newClass = NSClassFromString(newClassName);
//如果子类存在,则直接返回
if (newClass) return newClass;
//2.1 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
//2.2 注册
objc_registerClassPair(newClass);
//2.3 添加方法
SEL classSel = @selector(class);
Method classMethod = class_getInstanceMethod([self class], classSel);
const char *classType = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSel, (IMP)yp_class, classType);
return newClass;
}
//*********class方法*********
#pragma mark - 重写class方法,为了与系统类对外保持一致
Class yp_class(id self, SEL _cmd){
//在外界调用class返回LGPerson类
return class_getSuperclass(object_getClass(self));//通过[self class]获取会造成死循环
}
- 3、isa指向由原有类,改为指向中间类
object_setClass(self, newClass);
- 4、保存信息:这里用的数组,也可以使用map,需要创建信息的model模型类
//*********KVO信息的模型类/*********
#pragma mark 信息model类
@interface YPKVOInfo : NSObject
@property(nonatomic, weak) NSObject *observer;
@property(nonatomic, copy) NSString *keyPath;
@property(nonatomic, copy) YPKVOBlock handleBlock;
- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(YPKVOBlock)block;
@end
@implementation YPKVOInfo
- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(YPKVOBlock)block{
if (self = [super init]) {
_observer = observer;
_keyPath = keyPath;
_handleBlock = block;
}
return self;
}
@end
//*********保存信息*********
//- 保存多个信息
YPKVOInfo *info = [[YPKVOInfo alloc] initWithObserver:observer forKeyPath:keyPath handleBlock:block];
//使用数组存储 -- 也可以使用map
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey));
if (!mArray) {//如果mArray不存在,则重新创建
mArray = [NSMutableArray arrayWithCapacity:1];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[mArray addObject:info];
KVO响应
主要是给子类动态添加setter
方法,其目的是为了在setter方法中向父类发送消息,告知其属性值的变化
- 5、将setter方法重写添加到子类中(主要是在注册观察者方法中添加)
//获取sel
SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
//获取setter实例方法
Method method = class_getInstanceMethod([self class], setterSel);
//方法签名
const char *type = method_getTypeEncoding(method);
//添加一个setter方法
class_addMethod(newClass, setterSel, (IMP)yp_setter, type);
- 6、通过将系统的
objc_msgSendSuper
强制类型转换自定义的消息发送yp_msgSendSuper
//往父类LGPerson发消息 - 通过objc_msgSendSuper
//通过系统强制类型转换自定义objc_msgSendSuper
void (*yp_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
//定义一个结构体
struct objc_super superStruct = {
.receiver = self, //消息接收者 为 当前的self
.super_class = class_getSuperclass(object_getClass(self)), //第一次快捷查找的类 为 父类
};
//调用自定义的发送消息函数
yp_msgSendSuper(&superStruct, _cmd, newValue);
- 7、告知vc去响应:获取信息,通过block传递
/*---函数式编程*/
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey));
for (YPKVOInfo *info in mArray) {
NSMutableDictionary<NSKeyValueChangeKey, id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
info.handleBlock(info.observer, keyPath, oldValue, newValue);
}
}
移除观察者
为了避免在外界不断的调用removeObserver方法,在自定义KVO中实现自动移除观察者
- 8、实现yp_removeObserver:forKeyPath:方法,主要是清空保存的数组,以及isa指向更改
原理:当LGPerson调用dealloc
,会自动走到重写的yp_dealloc
方法中,因为person
对象的isa
指向变了,指向中间类,但是实例对象的地址是不变的
,所有子类的释放,相当于释放了外界的person
,重写yp_dealloc
相当于重写了LGPerson的dealloc
,达到自动释放的目的
- (void)yp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
//清空数组
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey));
if (mArray.count <= 0) {
return;
}
for (YPKVOInfo *info in mArray) {
if ([info.keyPath isEqualToString:keyPath]) {
[mArray removeObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
if (mArray.count <= 0) {
//isa指回父类
Class superClass = [self class];
object_setClass(self, superClass);
}
}
- 9、在子类中重写dealloc方法,当子类销毁时,会自动调用dealloc方法(在动态生成子类的方法中添加)
#pragma mark - 动态生成子类
- (Class)createChildClassWithKeyPath:(NSString *)keyPath
{
//...
//添加dealloc 方法
SEL deallocSel = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod([self class], deallocSel);
const char *deallocType = method_getTypeEncoding(deallocMethod);
class_addMethod(newClass, deallocSel, (IMP)yp_dealloc, deallocType);
return newClass;
}
//************重写dealloc方法*************
void yp_dealloc(id self, SEL _cmd){
NSLog(@"来了");
Class superClass = [self class];
object_setClass(self, superClass);
}
注意
-
关于
objc_msgSend
的检查关闭:target -> Build Setting -> Enable Strict Checking of objc_msgSend Calls
设置为NO
-
为了保证对外的类一致,
class
方法必须重写,不然会导致KVO在注册前后的实例对象[person class]不一致,注册前是LGPerson,注册后是YPNSKVONotifying_LGPerson
自定义KVO代码
#import <Foundation/Foundation.h>
typedef void(^YPKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
@interface NSObject (YPKVO)
//------响应式编程
- (void)yp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)yp_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
- (void)yp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
//------函数式编程
- (void)yp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(YPKVOBlock)block;
@end
#import "NSObject+YPKVO.h"
#import <objc/message.h>
static NSString *const kYPKVOPrefix = @"kYPKVONotifying_";
static NSString *const kYPKVOAssociateKey = @"kYPKVO_AssociateKey";
#pragma mark 信息model类
@interface YPKVOInfo : NSObject
@property(nonatomic, weak) NSObject *observer;
@property(nonatomic, copy) NSString *keyPath;
@property(nonatomic, assign) NSKeyValueObservingOptions options;
@property(nonatomic, copy) YPKVOBlock handleBlock;
//构造方法
- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options;
- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(YPKVOBlock)block;
@end
@implementation YPKVOInfo
- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options{
if (self = [super init]) {
_observer = observer;
_keyPath = keyPath;
_options = options;
}
return self;
}
- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(YPKVOBlock)block{
if (self = [super init]) {
_observer = observer;
_keyPath = keyPath;
_handleBlock = block;
}
return self;
}
@end
#pragma mark 自定义KVO分类
@implementation NSObject (YPKVO)
#pragma mark - 注册观察者 - 响应式编程
- (void)yp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
//1、验证是否存在setter方法
[self judgeSetterMethodFromKeyPath:keyPath];
//保存信息
/*//- 仅保存一个信息
if (!objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey))) {
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey), observer, OBJC_ASSOCIATION_RETAIN);
}
*/
//- 保存多个信息
YPKVOInfo *info = [[YPKVOInfo alloc] initWithObserver:observer forKeyPath:keyPath options:options];
//使用数组存储 -- 也可以使用map
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey));
if (!mArray) {//如果mArray不存在,则重新创建
mArray = [NSMutableArray arrayWithCapacity:1];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[mArray addObject:info];
//判断automaticallyNotifiesObserversForKey方法返回的布尔值
BOOL isAutomatically = [self yp_performSelectorWithMethodName:@"automaticallyNotifiesObserversForKey:" keyPath:keyPath];
if (!isAutomatically) return;
//2、动态生成子类、
/*
2.1 申请类
2.2 注册
2.3 添加方法
*/
Class newClass = [self createChildClassWithKeyPath:keyPath];
//3、isa指向
object_setClass(self, newClass);
//4、父类 setter
//5、观察者去响应
//获取sel
SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
//获取setter实例方法
Method method = class_getInstanceMethod([self class], setterSel);
//方法签名
const char *type = method_getTypeEncoding(method);
//添加一个setter方法
class_addMethod(newClass, setterSel, (IMP)yp_setter, type);
}
#pragma mark - 注册观察者 - 函数式编程
- (void)yp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(YPKVOBlock)block{
//1、验证是否存在setter方法
[self judgeSetterMethodFromKeyPath:keyPath];
//保存信息
/*//- 仅保存一个信息
if (!objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey))) {
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey), observer, OBJC_ASSOCIATION_RETAIN);
}
*/
//- 保存多个信息
YPKVOInfo *info = [[YPKVOInfo alloc] initWithObserver:observer forKeyPath:keyPath handleBlock:block];
//使用数组存储 -- 也可以使用map
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey));
if (!mArray) {//如果mArray不存在,则重新创建
mArray = [NSMutableArray arrayWithCapacity:1];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[mArray addObject:info];
//判断automaticallyNotifiesObserversForKey方法返回的布尔值
BOOL isAutomatically = [self yp_performSelectorWithMethodName:@"automaticallyNotifiesObserversForKey:" keyPath:keyPath];
if (!isAutomatically) return;
//2、动态生成子类、
/*
2.1 申请类
2.2 注册
2.3 添加方法
*/
Class newClass = [self createChildClassWithKeyPath:keyPath];
//3、isa指向
object_setClass(self, newClass);
//4、父类 setter
//5、观察者去响应
//获取sel
SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
//获取setter实例方法
Method method = class_getInstanceMethod([self class], setterSel);
//方法签名
const char *type = method_getTypeEncoding(method);
//添加一个setter方法
class_addMethod(newClass, setterSel, (IMP)yp_setter, type);
//进行方法交换
// static dispatch_once_t onceToken;
// dispatch_once(&onceToken, ^{
//// [NSObject yp_hookOrigInstanceMenthod:NSSelectorFromString(@"dealloc") newInstanceMenthod:@selector(myDealloc)];
// Method oriMethod = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
// Method swiMethod = class_getInstanceMethod([self class], NSSelectorFromString(@"myDealloc"));
// method_exchangeImplementations(oriMethod, swiMethod);
// });
}
#pragma mark - 移除观察者 - 响应式编程
- (void)yp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
//清空数组
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey));
if (mArray.count <= 0) {
return;
}
for (YPKVOInfo *info in mArray) {
if ([info.keyPath isEqualToString:keyPath]) {
[mArray removeObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
if (mArray.count <= 0) {
//isa指回父类
Class superClass = [self class];
object_setClass(self, superClass);
}
}
#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath
{
Class superClass = object_getClass(self);
SEL setterSelector = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superClass, setterSelector);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"YPKVO - 没有当前%@的setter方法", keyPath] userInfo:nil];
}
}
#pragma mark - 动态生成子类
- (Class)createChildClassWithKeyPath:(NSString *)keyPath
{
//获取原本的类名
NSString *oldClassName = NSStringFromClass([self class]);
//拼接新的类名
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kYPKVOPrefix,oldClassName];
//获取新类
Class newClass = NSClassFromString(newClassName);
//如果子类存在,则直接返回
if (newClass) return newClass;
//2.1 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
//2.2 注册
objc_registerClassPair(newClass);
//2.3 添加方法
SEL classSel = @selector(class);
Method classMethod = class_getInstanceMethod([self class], classSel);
const char *classType = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSel, (IMP)yp_class, classType);
//添加dealloc 方法
SEL deallocSel = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod([self class], deallocSel);
const char *deallocType = method_getTypeEncoding(deallocMethod);
class_addMethod(newClass, deallocSel, (IMP)yp_dealloc, deallocType);
return newClass;
}
#pragma mark - 重写setter方法,向父类发消息(响应式编程 / 函数式编程)
static void yp_setter(id self, SEL _cmd, id newValue){
NSLog(@"来了:%@",newValue);
//此时应该有willChange的代码
//往父类LGPerson发消息 - 通过objc_msgSendSuper
//通过系统强制类型转换自定义objc_msgSendSuper
void (*yp_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
//定义一个结构体
struct objc_super superStruct = {
.receiver = self, //消息接收者 为 当前的self
.super_class = class_getSuperclass(object_getClass(self)), //第一次快捷查找的类 为 父类
};
//调用自定义的发送消息函数
yp_msgSendSuper(&superStruct, _cmd, newValue);
//此时应该有didChange的代码
//让vc去响应
//--- 仅保存一个信息的获取
/*
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
//获取observer
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey));
//判断
if (observer && [observer respondsToSelector:@selector(yp_observeValueForKeyPath:ofObject:change:context:)]) {
//消息发送
[observer yp_observeValueForKeyPath:keyPath ofObject:self change:@{keyPath: newValue} context:NULL];
}
*/
//--- 保存多个信息的获取
/*-- 响应式编程
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey));
for (YPKVOInfo *info in mArray) {
NSMutableDictionary<NSKeyValueChangeKey, id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
if ([info.keyPath isEqualToString:keyPath]) {
if (info.options & NSKeyValueObservingOptionNew){
[change setValue:newValue forKey:NSKeyValueChangeNewKey];
}else {
[change setValue:@"YP旧值" forKey:NSKeyValueChangeOldKey];
[change setValue:newValue forKey:NSKeyValueChangeNewKey];
}
//消息发送
if (info.observer && [info.observer respondsToSelector:@selector(yp_observeValueForKeyPath:ofObject:change:context:)]) {
[info.observer yp_observeValueForKeyPath:info.keyPath ofObject:self change:change context:NULL];
}
}
}
*/
/*---函数式编程*/
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kYPKVOAssociateKey));
for (YPKVOInfo *info in mArray) {
NSMutableDictionary<NSKeyValueChangeKey, id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
info.handleBlock(info.observer, keyPath, oldValue, newValue);
}
}
}
#pragma mark - 重写class方法,为了与系统类对外保持一致
Class yp_class(id self, SEL _cmd){
//在外界调用class返回LGPerson类
return class_getSuperclass(object_getClass(self));//通过[self class]获取会造成死循环
}
#pragma mark - 重写dealloc方法
void yp_dealloc(id self, SEL _cmd){
NSLog(@"来了 %@", __func__);
Class superClass = [self class];
object_setClass(self, superClass);
}
#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter){
if (getter.length <= 0) { return nil;}
NSString *firstString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
#pragma mark - 从set方法获取getter方法的名称 set<Key>:===> key
static NSString *getterForSetter(NSString *setter){
if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
NSRange range = NSMakeRange(3, setter.length-4);
NSString *getter = [setter substringWithRange:range];
NSString *firstString = [[getter substringToIndex:1] lowercaseString];
return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}
#pragma mark - 动态调用类方法,返回调用方法的返回值
/// @param methodName 方法名
/// @param keyPath 观察属性
- (BOOL)yp_performSelectorWithMethodName:(NSString *)methodName keyPath:(id)keyPath {
if ([[self class] respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
BOOL i = [[self class] performSelector:NSSelectorFromString(methodName) withObject:keyPath];
return i;
#pragma clang diagnostic pop
}
return NO;
}
#pragma mark - 方法交换封装
+ (BOOL)yp_hookOrigInstanceMenthod:(SEL)oriSEL newInstanceMenthod:(SEL)swizzledSEL {
Class cls = self;
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
if (!swiMethod) {
return NO;
}
if (!oriMethod) {
class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
return YES;
}
@end