iOS-自定义KVO

2018-02-27 11:14:48来源:https://www.jianshu.com/p/d54b236659c2作者:linbj人点击

分享





image.png

当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制


如果原类为Person,那么生成的派生类名为NSKVONotifying_Person


每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类”


#import <Foundation/Foundation.h>
typedef void(^PGObservingBlock)(id observedObject, NSString *observedKey, id oldValue, id newValue);
@interface NSObject (KVO)
/**
手动实现kvo并添加block
@param observer 观察者
@param key key
@param block block
*/
- (void)PG_addObserver:(NSObject *)observer
forKey:(NSString *)key
withBlock:(PGObservingBlock)block;
- (void)PG_removeObserver:(NSObject *)observer forKey:(NSString *)key;
@end
#import "NSObject+KVO.h"
#import <objc/runtime.h>
#import <objc/message.h>
NSString *const kPGKVOClassPrefix = @"PGKVOClassPrefix_";
NSString *const kPGKVOAssociatedObservers = @"PGKVOAssociatedObservers";

#pragma mark - PGObservationInfo
@interface PGObservationInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) PGObservingBlock block;
@end
@implementation PGObservationInfo
- (instancetype)initWithObserver:(NSObject *)observer Key:(NSString *)key block:(PGObservingBlock)block
{
self = [super init];
if (self) {
_observer = observer;
_key = key;
_block = block;
}
return self;
}
@end

#pragma mark - Debug Help Methods
static NSArray *ClassMethodNames(Class c)
{
NSMutableArray *array = [NSMutableArray array];

unsigned int methodCount = 0;
Method *methodList = class_copyMethodList(c, &methodCount);
unsigned int i;
for(i = 0; i < methodCount; i++) {
[array addObject: NSStringFromSelector(method_getName(methodList[i]))];
}
free(methodList);

return array;
}

static void PrintDescription(NSString *name, id obj)
{
NSString *str = [NSString stringWithFormat:
@"%@: %@/n/tNSObject class %s/n/tRuntime class %s/n/timplements methods <%@>/n/n",
name,
obj,
class_getName([obj class]),
class_getName(object_getClass(obj)),
[ClassMethodNames(object_getClass(obj)) componentsJoinedByString:@", "]];
printf("%s/n", [str UTF8String]);
}

#pragma mark - Helpers
static NSString * getterForSetter(NSString *setter)
{
if (setter.length <=0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) {
return nil;
}

// remove 'set' at the begining and ':' at the end
NSRange range = NSMakeRange(3, setter.length - 4);
NSString *key = [setter substringWithRange:range];

// lower case the first letter
NSString *firstLetter = [[key substringToIndex:1] lowercaseString];
key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1)
withString:firstLetter];

return key;
}

/**
给传入的getter 第一个字符变大写,前后分别拼接set 和 :变成一个setStr:形式
@param getter 传入字符串
@return 修改后的字符串
*/
static NSString * setterForGetter(NSString *getter)
{
if (getter.length <= 0) {
return nil;
}

// upper case the first letter
NSString *firstLetter = [[getter substringToIndex:1] uppercaseString];
NSString *remainingLetters = [getter substringFromIndex:1];

// add 'set' at the begining and ':' at the end
NSString *setter = [NSString stringWithFormat:@"set%@%@:", firstLetter, remainingLetters];

return setter;
}

#pragma mark - Overridden Methods
// 重写kvo类的setter方法,当调用setter方法的时候触发block
static void kvo_setter(id self, SEL _cmd, id newValue)
{
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = getterForSetter(setterName);

if (!getterName) {
NSString *reason = [NSString stringWithFormat:@"Object %@ does not have setter %@", self, setterName];
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:reason
userInfo:nil];
return;
}

// 获取gettername对应的老的值
id oldValue = [self valueForKey:getterName];

struct objc_super superclazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};

// cast our pointer so the compiler won't complain
// objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;

// call super's setter, which is original class's setter method
// 派生类让类改变新值
objc_msgSendSuperCasted(&superclazz, _cmd, newValue);

// look up observers and call the blocks
// 从数组中找到对应的观察者调用block
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
for (PGObservationInfo *each in observers) {
if ([each.key isEqualToString:getterName]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
each.block(self, getterName, oldValue, newValue);
});
}
}
}

static Class kvo_class(id self, SEL _cmd)
{
return class_getSuperclass(object_getClass(self));
}

#pragma mark - KVO Category
@implementation NSObject (KVO)
- (void)PG_addObserver:(NSObject *)observer
forKey:(NSString *)key
withBlock:(PGObservingBlock)block
{
// 输入的key转换为 setKey: 变成SEL
SEL setterSelector = NSSelectorFromString(setterForGetter(key));

// 当前类是否实现了key 的setter方法
Method setterMethod = class_getInstanceMethod([self class], setterSelector);

// 没有实现弹出错误
if (!setterMethod) {
NSString *reason = [NSString stringWithFormat:@"Object %@ does not have a setter for key %@", self, key];
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:reason
userInfo:nil];

return;
}

// 获取本类的class并且转为string类型
Class clazz = object_getClass(self);
NSString *clazzName = NSStringFromClass(clazz);

// 如果class不是修改后的kvo类的话去创建一个kvo类(类名以PGKVOClassPrefix_为前缀)
// if not an KVO class yet
if (![clazzName hasPrefix:kPGKVOClassPrefix]) {
clazz = [self makeKvoClassWithOriginalClassName:clazzName];
object_setClass(self, clazz);
}

// add our kvo setter if this class (not superclasses) doesn't implement the setter?
// 判断类是否包含setkey: 这个方法
if (![self hasSelector:setterSelector]) {
// 不包含则手动添加一个method
const char *types = method_getTypeEncoding(setterMethod);

// * Adds a new method to a class with a given name and implementation.
// 给本类添加一个setter方法
class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);
}

// 添加一个观察者类并且将observer key block 给它。
PGObservationInfo *info = [[PGObservationInfo alloc] initWithObserver:observer Key:key block:block];
// 获取类的所有的观察者放入数组中
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
if (!observers) {
// 创建一个新的观察者数组
observers = [NSMutableArray array];
objc_setAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// 添加观察者
[observers addObject:info];
}

- (void)PG_removeObserver:(NSObject *)observer forKey:(NSString *)key
{
NSMutableArray* observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));

PGObservationInfo *infoToRemove;
for (PGObservationInfo* info in observers) {
if (info.observer == observer && [info.key isEqual:key]) {
infoToRemove = info;
break;
}
}

[observers removeObject:infoToRemove];
}

/**
传入classname,生成一个以PGKVOClassPrefix_开头的派生类
@param originalClazzName name
@return 派生类的class
*/
- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
{
NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName];
Class clazz = NSClassFromString(kvoClazzName);

if (clazz) {
return clazz;
}

// class doesn't exist yet, make it
// 创建一个派生类
Class originalClazz = object_getClass(self);

// 创建一个派生与originalclass的新类
Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);

// grab class method's signature so we can borrow it
// 获取originalClazz 的method并复制给派生类
Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
const char *types = method_getTypeEncoding(clazzMethod);
class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);

objc_registerClassPair(kvoClazz);

return kvoClazz;
}

/**
判断当前类是否包含selector
@param selector selector
@return bool
*/
- (BOOL)hasSelector:(SEL)selector
{
Class clazz = object_getClass(self);
unsigned int methodCount = 0;
Method* methodList = class_copyMethodList(clazz, &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
SEL thisSelector = method_getName(methodList[i]);
if (thisSelector == selector) {
free(methodList);
return YES;
}
}

free(methodList);
return NO;
}

@end

参考github:自定义kvo


用到的部分runtime方法如下


//  根据传入的class返回它的instance 方法 
// class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
Method setterMethod = class_getInstanceMethod([self class], setterSelector);

// 通过传入的超类创建一个新的派生类
// objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name,
size_t extraBytes)
Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);

// 获取originalClazz 的method并复制给派生类
Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
const char *types = method_getTypeEncoding(clazzMethod);
class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);

// 注册一个class
// objc_registerClassPair(Class _Nonnull cls)
objc_registerClassPair(kvoClazz);

// 返回一个字符串描述一个方法的参数和返回类型。
// method_getTypeEncoding(Method _Nonnull m)
const char *types = method_getTypeEncoding(setterMethod);


// 构建一个objc_super的结构体
struct objc_super superclazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};


// 函数签名对不上,声明一个函数指针指向函数实现的指针 强转一下就得了
// objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;
// 派生类让类改变新值
objc_msgSendSuperCasted(&superclazz, _cmd, newValue);








最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台