系统KVO这个响应式的机制很有挖掘的潜力,最近在这上面花了一点时间,总结一下自己的一些心得
* 系统KVO
- (void)viewDidLoad {
[super viewDidLoad];
self.p = [[Peroson alloc] init];
[self addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
self.p.name = @"Cooci";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
// value
NSLog(@"%@",change);
}
还有KVO开关
//KVO的自动开关
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
if ([key isEqualToString:@"age"]) {
//返回NO就不会自动观察 这样就可以灵活敏捷开发
return NO;
}
return YES;
}
- (void)setAge:(int)age{
//自动开关关闭, 还可以通过手动开关监听
[self willChangeValueForKey:@"age"];
_age = age;
[self didChangeValueForKey:@"age"];
}
//对于对象嵌套对象,只需要观察对象的对象 就可以观察到嵌套层的对象的属性
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *sets = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"dog"]){
//嵌套层的处理
NSSet *affectingKeys = [NSSet setWithObjects:@"_age", @"_name",nil];
sets = [sets setByAddingObjectsFromSet:affectingKeys];
}
return sets;
}
数组的观察 需要借助KVC,mutableArrayValueForKey KVC的数组取值方式,要实现KVO必须通过KVC对数据进行操作.
[self.p addObserver:self forKeyPath:@"arrs" options:(NSKeyValueObservingOptionNew) context:nil];
[[self.p mutableArrayValueForKey:@"arrs"] addObject:@"Cooci"];
[[self.p mutableArrayValueForKey:@"arrs"] removeObject:@"Cooci"];
typedef NS_ENUM(NSUInteger, NSKeyValueSetMutationKind) {
NSKeyValueUnionSetMutation = 1, //setter
NSKeyValueMinusSetMutation = 2, //add
NSKeyValueIntersectSetMutation = 3,//remove
NSKeyValueSetSetMutation = 4 //set集合类
};
KVO的原理:
- 1:动态生成子类----NSKVONotifying_A //可以通过isa查看
- 2:setter方法的添加
- 3:消息转发给父类
- 自定义KVO --- 猜测实现
//利用函数式编程思想,这里添加观察的代码与逻辑代码可以放到一起
- (void)kc_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath withBlock:(KCKVOBlock)block;
[self.p kc_addObserver:self forKeyPath:@"name" withBlock:^(id observer, NSString *keyPath, id newValue, id oldValue) {
//逻辑代码区域
}];
//核心代码的实现
- (void)kc_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath withBlock:(KCKVOBlock)block{
//实例
//setter keypath是否 刷掉实例 name
//SEL 方法选择器 ====> 找方法 方法编号
SEL setterSeletor = NSSelectorFromString(setterfromGetter(keyPath));
Method setterMethod = class_getInstanceMethod(object_getClass(self), setterSeletor);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"没有setter方法,可能是实例变量" userInfo:nil];
}
//2:动态创建子类
NSString *superClassName = NSStringFromClass([self class]);
Class newClas = [self creatClassFromSuperClass:superClassName];
if (newClas) {
//ias swilzing 替换父类
object_setClass(self, newClas);
}
const char *types = method_getTypeEncoding(setterMethod);
//3:setter方法
if (![self hasSeletor:setterSeletor]) {
class_addMethod(newClas, setterSeletor, (IMP)KCKVO_Setter, types);
}
//4;消息转发
KCInfo *info = [[KCInfo alloc] initWithObserver:observer keyPath:keyPath observerHandle:block];
info.target = self;
NSMutableArray *obserArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kKVOAssioKey));
if (!obserArray) {
obserArray = [NSMutableArray arrayWithCapacity:1];
/**
关联属性
1:对象
2:关键字
3:关联对象
4:策略
*/
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kKVOAssioKey), obserArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[obserArray addObject:info];
}
//其他代码也给大家粘出来:主要封装了动态生成类的方法,以及过滤方法,还有setter方法得到getter属性,getter属性===>>setter:方法的函数实现.还有动态子类添加的setter的IMP的函数,一个class的IMP函数
- (Class)creatClassFromSuperClass:(NSString *)superName{
Class superClass = NSClassFromString(superName);
/**
动态生成类
1:父类
2:名字
3:空间
*/
// NSKVONotifiying_ ===>>> KCKVC_
NSString *newClassName = [kcKVOPrefix stringByAppendingString:superName];
Class newClass = NSClassFromString(newClassName);
if (newClass) {
return newClass;
}
newClass = objc_allocateClassPair(superClass, newClassName.UTF8String, 0);
//添加Class
Method classMethod = class_getClassMethod(superClass, @selector(class));
const char *type = method_getTypeEncoding(classMethod);
/**
添加方法
1:类
2:方法名字
3:IMP 函数实现的指针
4:类型
*/
class_addMethod(newClass, @selector(class), (IMP)KCKVC_Class, type);
//注册类
objc_registerClassPair(newClass);
return newClass;
}
- (BOOL)hasSeletor:(SEL)setterSeletor{
Class observerClass = object_getClass(self);
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList(observerClass, &methodCount);
for (int i = 0; i<methodCount; i++) {
SEL sel = method_getName(methodList[i]);
if (setterSeletor == sel) {
free(methodList);
return YES;
}
}
free(methodList);
return NO;
}
#pragma mark 函数去区域
static Class KCKVC_Class(id self){
return class_getSuperclass(object_getClass(self));
}
- (void)setName:(NSString *)str{
}
static void KCKVO_Setter(id self,SEL _cmd,id newValue){
//手动监听
//_cmd ===
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = getterfromSetter(setterName);
id oldValue = [self valueForKey:getterName];
[self willChangeValueForKey:getterName];
//消息转发
void(*KCMsgSend)(void *,SEL,id) = (void *)objc_msgSendSuper;
struct objc_super kcSuperStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
KCMsgSend(&kcSuperStruct,_cmd,newValue);
[self didChangeValueForKey:getterName];
NSMutableArray *obserArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kKVOAssioKey));
// NSMutableArray *obserArray = [KCInfoShare shared].infoArray;
for (KCInfo *info in obserArray) {
dispatch_async(dispatch_queue_create(0, 0), ^{
info.blockHandle(self, info.keyPath, newValue, oldValue);
});
}
}
//name ===>> setName:
static NSString * setterfromGetter(NSString * getter){
if (getter.length <= 0) { return nil;}
//name
NSString *firtString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1]; //ame
return [NSString stringWithFormat:@"set%@%@:",firtString,leaveString];
}
//setName: ===>>> name
static NSString * getterfromSetter(NSString * setter){
if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
//setName: ===>> Name: ====> Name ==> name
NSRange range = NSMakeRange(3, setter.length-4);
NSString *getter = [setter substringWithRange:range];
NSString *firstString = [[getter substringToIndex:1] lowercaseString]; //n
getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
return getter;
}
//这里的增加了一个信息保存的类KCInfo
@interface KCInfo:NSObject
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, copy) KCKVOBlock blockHandle;
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, unsafe_unretained) NSObject *target;
@property (nonatomic, weak) KCInfo *info;
@property (nonatomic, strong) NSMutableArray *obserArray;
@end
@implementation KCInfo
- (instancetype)initWithObserver:(NSObject *)observer keyPath:(NSString *)keyPath observerHandle:(KCKVOBlock)blockHandle{
if (self = [super init]) {
_keyPath = keyPath;
_observer = observer;
_blockHandle = blockHandle;
}
return self;
}
@end
//为了保障整个代码的完善性:添加移除的方法
- (void)kc_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
//KCInfo保存的数据是不是要移除
NSMutableArray *obserArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kKVOAssioKey));
//删除一个还是可以的,但是删除多个是会出现是异常的。(会出现索引跳位)
[obserArray enumerateObjectsUsingBlock:^(KCInfo *info, NSUInteger idx, BOOL * _Nonnull stop) {
if ([info.keyPath isEqualToString:keyPath]&&[info.observer isEqual:observer]) {
[obserArray removeObject:info];
}
}];
if (obserArray.count == 0) obserArray = nil;
//要将动态子类的isa指针 指回来Person
NSString *nowClassName = NSStringFromClass(object_getClass(self));
NSString *superClassName = [nowClassName stringByReplacingOccurrencesOfString:kcKVOPrefix withString:@""];
Class superClass = NSClassFromString(superClassName);
object_setClass(self, superClass);
NSLog(@"%@",observer);
}
//其实添加这个销毁方法还不是我希望的,我希望每一次用就起,不用这个KVO自动销毁
//利用runtime的机制我拦截Person的dealloc方法
Method m2 = class_getInstanceMethod(newClas, @selector(myDealloc));
Method m1 = class_getInstanceMethod(newClas, NSSelectorFromString(@"dealloc"));
method_exchangeImplementations(m2, m1);
- (void)myDealloc{
//要将动态子类的isa指针 指回来Person
NSString *nowClassName = NSStringFromClass(object_getClass(self));
NSString *superClassName = [nowClassName stringByReplacingOccurrencesOfString:kcKVOPrefix withString:@""];
Class superClass = NSClassFromString(superClassName);
object_setClass(self, superClass);
//KCInfo保存的数据是不是要移除
NSMutableArray *obserArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kKVOAssioKey));
//删除一个还是可以的,但是删除多个是会出现是异常的。(会出现索引跳位)
[obserArray removeAllObjects];
[self myDealloc];
}
//最后通过类搜索与isa的判别发现与系统的KVO相差无几
unsigned int outCount;
//该函数的作用是获取所有已注册的类
Class *classes = objc_copyClassList(&outCount);
for (int i = 0; i < outCount; i++) {
NSLog(@"%s", class_getName(classes[i]));
}
free(classes);
写到这里也就完成了,以上就是我对KVO的理解,希望大家能给与指正