kvo的用法就不再赘述。
[self.name 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(@"%@", change);
}
核心思想,以观察name属性为例:
重写name属性的setter方法,当name值改变时在setter方法中调用observeValueForKeyPath方法。
先说说看我自己的思路:
1.替换name属性的setter方法。
- (void)dy_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
NSString* setterMethodName = setterForGetter(keyPath);
SEL setterSEL = NSSelectorFromString(setterMethodName);
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char* setterTypes = method_getTypeEncoding(setterMethod);
//替换name的setter方法自己实现
class_replaceMethod([self class], setterSEL, (IMP)dy_setter, setterTypes);
//将观察者存到数组作为属性,方便后面使用
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge void *)@"observerArr")?: [NSMutableArray array];
if (![observerArr containsObject:observer]) [observerArr insertObject:observer atIndex:0];
objc_setAssociatedObject(self, (__bridge void *)@"observerArr", observerArr, OBJC_ASSOCIATION_RETAIN);
}
2.在name改变时通过重写name属性的setter方法调用observeValueForKeyPath方法。
static void dy_setter(id self, SEL _cmd, id newValue) {
NSString* setterName = NSStringFromSelector(_cmd);
NSString* key = getterForSetter(setterName);
char *char_content = (char *)[[NSString stringWithFormat:@"_%@",key] cStringUsingEncoding:NSASCIIStringEncoding];
Ivar ivar = class_getInstanceVariable([self class], char_content);
//给name属性赋值
object_setIvar(self, ivar, newValue);
// 通知观察者, 值发生改变了
id observerArr = objc_getAssociatedObject(self, (__bridge void *)@"observerArr");
for (id observer in observerArr) {
[observer observeValueForKeyPath:key ofObject:self change:@{key:newValue} context:nil];
}
}
3.setterForGetter & getterForSetter。
#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];
getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
return getter;
}
4.移除观察者.
如果Person中重写了setName方法这样做会导致Peroson中setName无效,在苹果的实现中,先创建了一个self的子类,在子类中重写 了setName方法,这样能保证Peroson中重写的setName有效。
代码类似,代码在这里
网友评论