1.KVO的简单使用
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.person = [YXPerson alloc];
//1.添加监听
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.person.name = [NSString stringWithFormat:@"%@+",self.person.name];
}
//2.响应监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"新值- %@",[change objectForKey:NSKeyValueChangeNewKey]);
}
//移除监听
- (void)dealloc
{
[self.person removeObserver:self forKeyPath:@"name"];
}
自动开关
@implementation YXPerson
//自动开关
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:@"name"]) {
return NO;//返回NO,就监听不到
}
return YES;
}
- (void)setName:(NSString *)name
{
//手动开关,自动开关关闭的时候,可以通过手动开关来打开监听
// [self willChangeValueForKey:@"name"];
_name = name;
// [self didChangeValueForKey:@"name"];
}
@end
KVO观察数组
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[[self.person mutableArrayValueForKey:@"myArray"] addObject:@"1"];
}
原理
- 1.动态生成子类:NSKVONotifiy_A
- 1.防止实例变量的影响
- 2.动态子类的过程A:生成类B:添加class方法C:注册
- 3.对类的存在性进行判断
- 2.给动态子类添加setter方法
- 3.消息转发给父类
- 验证过程需要以下方法,有兴趣的同学可以自己验证
#pragma mark - 打印类所有的方法
- (void)printAllMethodsWithCls:(Class)cls
{
unsigned int count = 0;
Method *methods = class_copyMethodList(cls, &count);
for (int i = 0; i < count; i ++) {
Method method = methods[i];
SEL sel = method_getName(method);
NSLog(@"方法名- %@",NSStringFromSelector(sel));
}
free(methods);
}
#pragma mark - 遍历类以及子类
- (void)printClass:(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);
}
简单自定义KVO
#import
NS_ASSUME_NONNULL_BEGIN
typedef void (^YXKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
@interface NSObject (YXKVO)
- (void)yx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(YXKVOBlock)block;
- (void)yx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
NS_ASSUME_NONNULL_END
#import "NSObject+YXKVO.h"
#import
static NSString *const k_YXKVOPrefix = @"YXKVONotifying_";
static NSString *const k_YXKVOAssiociateKey = @"kYXKVO_AssiociateKey";
@interface YXInfo : NSObject
@property(nonatomic,weak)NSObject * observer;
@property(nonatomic,copy)NSString * keyPath;
@property(nonatomic,copy)YXKVOBlock handelBlock;
@end
@implementation YXInfo
- (instancetype)initWithObserver:(NSObject *)observer keyPath:(NSString *)keyPath handelBlock:(YXKVOBlock)handelBlock
{
if (self == [super init]) {
_observer = observer;
_keyPath = keyPath;
_handelBlock = handelBlock;
}
return self;
}
@end
@implementation NSObject (YXKVO)
- (void)yx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(YXKVOBlock)block
{
//1.验证是否存在set方法
[self judgeSetMethodFormKeyPath:keyPath];
//2.动态生成子类
Class newClass = [self creatChildClassWithKeyPath:keyPath];
//添加set方法
SEL setSel = NSSelectorFromString([self setterForGetter:keyPath]);
Method setMetod = class_getInstanceMethod([self class], setSel);
const char *setType = method_getTypeEncoding(setMetod);
class_addMethod(newClass, setSel, (IMP)yx_setter, setType);
//3.更改isa指向
object_setClass(self, newClass);
//4.保存信息
YXInfo *info = [[YXInfo alloc] initWithObserver:observer keyPath:keyPath handelBlock:block];
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey));
if (!mArray) {
mArray = [NSMutableArray arrayWithCapacity:1];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[mArray addObject:info];
}
- (void)yx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey));
if (observerArr.count<=0) {
return;
}
for (YXInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
[observerArr removeObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
}
if (observerArr.count<=0) {
// 指回给父类
Class superClass = [self class];
object_setClass(self, superClass);
}
}
#pragma mark - 验证是否存在set方法
- (NSString *)setterForGetter:(NSString *)keyPath
{
if (keyPath.length < 0) {
return @"";
}
NSString *firstString = [[keyPath substringToIndex:1] uppercaseString];
NSString *leaveString = [keyPath substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
- (void)judgeSetMethodFormKeyPath:(NSString *)keyPath
{
Class superClass = object_getClass(self);
SEL setterSel = NSSelectorFromString([self setterForGetter:keyPath]);
Method setterMethod = class_getInstanceMethod(superClass, setterSel);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"当前没有%@的set方法",keyPath] userInfo:nil];
}
}
#pragma mark - 动态生成子类
- (Class)creatChildClassWithKeyPath:(NSString *)keyPath
{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",k_YXKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
if (newClass) {
return newClass;
}
//申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
//注册类
objc_registerClassPair(newClass);
//添加class方法
SEL classSel = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSel);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSel, (IMP)yx_class, classTypes);
//添加dealloc
SEL deallocSEL = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
const char *deallocType = method_getTypeEncoding(deallocMethod);
class_addMethod(newClass, deallocSEL, (IMP)yx_dealloc, deallocType);
return newClass;
}
Class yx_class(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
static void yx_setter(id self,SEL _cmd,id newValue){
NSLog(@"子类set");
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
//消息转发:转发给父类
//改变父类的值
void(*yx_msgSendSuper)(void *,SEL,id) = (void *)objc_msgSendSuper;
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
yx_msgSendSuper(&superStruct,_cmd,newValue);
//数据回调
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(k_YXKVOAssiociateKey));
for (YXInfo *info in mArray) {
if ([info.keyPath isEqualToString:keyPath] && info.handelBlock) {
info.handelBlock(info.observer, keyPath, oldValue, newValue);
}
}
}
static void yx_dealloc(id self,SEL _cmd){
Class superClass = [self class];
object_setClass(self, superClass);
}
#pragma mark - 从set方法获取getter方法的名称 set:===> 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];
}
@end
- 以上只是简单自定义,主要是为了对系统的KVO加深理解,如果想更深入得了解,可以看一下GNU下载地址和FBKVOController下载地址,下载进行学习