KVC
KVC的定义都是对NSObject的扩展来实现的,Objective-C中有个显式的NSKeyValueCoding类别名,所以对于所有继承了NSObject的类型,都能使用KVC(一些纯Swift类和结构体是不支持KVC的,因为没有继承NSObject)
KVC使用
KVC在iOS开发中是绝不可少的利器,这种基于运行时的编程方式极大地提高了灵活性,简化了代码,甚至实现很多难以想像的功能,KVC也是许多iOS开发黑魔法的基础。
下面列举iOS开发中KVC的使用场景.
- 动态地取值和设值
利用KVC动态的取值和设值是最基本的用途了。 - 用KVC来访问和修改私有变量
对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的。 - Model和字典转换
这是KVC强大作用的又一次体现,KVC和Objc的runtime组合可以很容易的实现Model和字典的转换。 - 修改一些控件的内部属性
这也是iOS开发中必不可少的小技巧。众所周知很多UI控件都由很多内部UI控件组合而成的,但是Apple度没有提供这访问这些控件的API,这样我们就无法正常地访问和修改这些控件的样式。
而KVC在大多数情况可下可以解决这个问题。最常用的就是个性化UITextField中的placeHolderText了。 - 操作集合
Apple对KVC的valueForKey:方法作了一些特殊的实现,比如说NSArray和NSSet这样的容器类就实现了这些方法。所以可以用KVC很方便地操作集合。
通过调用valueForKey:给容器中每一个对象发送操作消息,并且结果会被保存在一个新的容器中返回,这样我们能很方便地利用一个容器对象创建另一个容器对象另外,valueForKeyPath:还能实现多个消息的传递,厉害了
NSArray*array=[NSArrayarrayWithObject:@"10.11",
@"20.22",nil];
NSArray*resultArray=[array valueForKeyPath:@"doubleValue.intValue"];
NSLog(@"%@",resultArray);
//打印结果
(
10,
20
)
- 用KVC实现高阶消息传递
当对容器类使用KVC时,valueForKey:将会被传递给容器中的每一个对象,而不是容器本身进行操作。结果会被添加进返回的容器中,这样,开发者可以很方便的操作集合来返回另一个集合。
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSArray *arrStr = @[@"English", @"franch", @"chinese"];
NSArray *arrCapStr = [arrStr valueForKey:@"capitalizedString"];
for (NSString *str in arrCapStr) {
NSLog(@"%@", str);
}
NSArray* arrCapStrLength = [arrStr valueForKeyPath:@"capitalizedString.length"];
for (NSNumber* length in arrCapStrLength) {
NSLog(@"%ld",(long)length.integerValue);
}
}
return 0;
}
对于keyPath是搜索机制第一步就是分离key,用小数点.来分割key,然后再像普通key一样按照先前介绍的顺序搜索下去
setValue 设值顺序
查找setKey 方法,有则调用,没有则查找看有没有_setKey方法,有则调用。没有则查看 accessInstanceVariablesDirectly 这个类方法的返回值,是yes则按照_key 、_isKey 、key 、iskey 顺序查找成员变量。如果没有找到则抛出异常 NSUnknownKeyException 。如果accessInstanceVariablesDirectly返回值是 NO,则程序抛出异常
然后valueForKey是一样的
KVO 原理
网上有许多总结的KVO的isa-swizzling技术的大体实现流程。让我们总结一下
- 当类实例被KVO后,系统会替换实例的isa指针内容。让其指向NSKVONotifing_XX类型的新类。
- 在NSKVONotifing_XX类中,会:重写KVO属性的set方法,支持KVO。重写class方法,来伪装自己仍然是XX类。添加_isKVOA方法,来说明自己是一个KVO类。重写dealloc方法,让实例下析构时,好做一些检查和清理工作
- 为了让用户在KVO isa-swizzling后,仍然能够调用原始XX类中的方法,系统还会将NSKVONotifing_XX类设置为原始XX类的子类。
- 当移除KVO后,系统会将isa指针中的内容复原。
原理中提到新建了一个NSKVONotifing_XX来。。。
这样在项目开发中就有了一种新的方法:Method Swizzling
交换方法的实现一些需求
Method Swizzling类簇
??
NSArray __NSArrayI
NSMutableArra __NSArrayM
NSDictionary __NSDictionaryI
NSMutableDictionary __NSDictionaryM
? 封装的Method Swizzling ????????????????????eJRSwizzle
源码分析
static GSKVOReplacement * replacementForClass(Class c) {
GSKVOReplacement *r;
setup();
[kvoLock lock];
r = (GSKVOReplacement*)NSMapGet(classTable, (void*)c);
if (r == nil)
{
r = [[GSKVOReplacement alloc] initWithClass: c];
NSMapInsert(classTable, (void*)c, (void*)r);
}
[kvoLock unlock];
return r;
}
这个函数呢就生成了新的派生类GSKVOReplacement中的一个名为replacement的Class对象
在这个函数中,首先执行了静态内联函数setup()。在setup函数中,初始化了classTable等相关的表,并且初始化了静态变量baseClass,这个baseClass是一个GSKVOBase类对象
在replacementForClass()函数中通过传入的原Class比如MyObject,在classTable中找寻派生类比如KVO_MyObject。如果没找到,通过GSKVOReplacement的initWithClass:方法新建一个并且插入到classTable中。
那么看看initWithClass:又是如何实现的
original = aClass;
/*
* Create subclass of the original, and override some methods
* with implementations from our abstract base class.
*/
superName = NSStringFromClass(original);
name = [@"GSKVO" stringByAppendingString: superName];
template = GSObjCMakeClass(name, superName, nil);
GSObjCAddClasses([NSArray arrayWithObject: template]);
replacement = NSClassFromString(name);
GSObjCAddClassBehavior(replacement, baseClass);
/* Create the set of setter methods overridden.
*/
keys = [NSMutableSet new];
return self;
根据代码大致流程如下:
- 通过原类拼接派生类的子类名
- 通过GSObjCAddClasses创建新类,在该函数中,通过superName反射创建superClass,也就是MyObject的class。通过name和objc_allocateClassPair函数创建一个新的class,也就是派生类。将生成的class对象包装在NSValue中返回给template
- 通过GSObjCAddClasses将template里的派生类对象注册好
- 注册好了以后就可以通过name 反射得到派生类replacement
- 通过GSObjCAddClassBehavior为replacement添加相应的方法,这个函数的两个参数为派生类和baseClass,而这个baseClass呢,就是
GSKVOBase类,这个类大概就是所有派生类的基类吧
GSObjCAddClassBehavior(Class receiver, Class behavior) {
unsigned int count;
Method *methods;
Class behavior_super_class = class_getSuperclass(behavior);
...
/* 一些不是很关键的代码 */
/* Add instance methods */
methods = class_copyMethodList(behavior, &count);
BDBGPrintf(" instance methods from %s %u\n", class_getName(behavior), count);
if (methods == NULL)
{
BDBGPrintf(" none.\n");
}
else
{
GSObjCAddMethods (receiver, methods, NO);
free(methods);
}
/* Add class methods */
methods = class_copyMethodList(object_getClass(behavior), &count);
BDBGPrintf(" class methods from %s %u\n", class_getName(behavior), count);
if (methods == NULL)
{
BDBGPrintf(" none.\n");
}
else
{
GSObjCAddMethods (object_getClass(receiver), methods, NO);
free(methods);
}
/* Add behavior's superclass, if not already there. */
if (!GSObjCIsKindOf(receiver, behavior_super_class))
{
GSObjCAddClassBehavior (receiver, behavior_super_class);
}
GSFlushMethodCacheForClass (receiver);
}
主要意思呢,就是把behavior中的方法列表copy一份到receiver中,根据上面的函数调用,我们知道behavior就是GSKVOBase类,而receiver就是派生类,也就是obj的isa最后指向的对象
那么这个GSKVOBase又是什么呢
- (Class) class
{
return class_getSuperclass(object_getClass(self));
}
- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
Class c = [self class];
void (*imp)(id,SEL,id,id);
imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];
if ([[self class] automaticallyNotifiesObserversForKey: aKey])
{
[self willChangeValueForKey: aKey];
imp(self,_cmd,anObject,aKey);
[self didChangeValueForKey: aKey];
}
else
{
imp(self,_cmd,anObject,aKey);
}
}
首先重写了自己的class方法,返回的自己的superClass,那么就相当于派生类的class方法返回的就是MyObject类,所以就是为什么你使用了KVO,打印的类还是原类
第二个方法是通过automaticallyNotifiesObserversForKey判断该key是否被kvo了,如果是就插入[self willChangeValueForKey: aKey]和self didChangeValueForKey: aKey];方法,如果没有就直接调用set方法。
这个模版类还是有许多方法的,所有的派生类方法列表都来自该模版类。
手动实现KVO
针对以上代码观察,我们来自己实现一波
为NSObject添加一个分类NSObject+KVOBlock来实现KVO的方法
- (void)sw_addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
callback:(sw_KVOObserverBlock)callback;
- (void)sw_removeObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath;
这是大家熟悉的两个KVO方法,那么怎么具体实现呢?看过原理,代码当然还是不容易啊
- (void)sw_addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
callback:(sw_KVOObserverBlock)callback {
// 1. 通过keyPath获取当前类对应的setter方法,如果获取不到,说明setter 方法即不存在与KVO类,也不存在与原始类,这总情况正常情况下是不会发生的,触发Exception
NSString *setterString = sw_setterByGetter(keyPath);
SEL setterSEL = NSSelectorFromString(setterString);
Method method = class_getInstanceMethod(object_getClass(self), setterSEL);
if (method) {
// 2. 查看当前实例对应的类是否是KVO类,如果不是,则生成对应的KVO类,并设置当前实例对应的class是KVO类
Class objectClass = object_getClass(self);
NSString *objectClassName = NSStringFromClass(objectClass);
if (![objectClassName hasPrefix:sw_KVOClassPrefix]) {
Class kvoClass = [self makeKvoClassWithOriginalClassName:objectClassName];
//改变当前obj的isa指向
object_setClass(self, kvoClass);
}
// 3. 在KVO类中查找是否重写过keyPath 对应的setter方法,如果没有,则添加setter方法到KVO类中
if (![self hasMethodWithMethodName:setterString]) {
class_addMethod(object_getClass(self), NSSelectorFromString(setterString), (IMP)sw_kvoSetter, method_getTypeEncoding(method));
}
// 4. 注册Observer
NSMutableArray<SWKVOObserverItem *> *observerArray = objc_getAssociatedObject(self, sw_KVOObserverAssociatedKey);
if (observerArray == nil) {
observerArray = [NSMutableArray new];
objc_setAssociatedObject(self, sw_KVOObserverAssociatedKey, observerArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
SWKVOObserverItem *item = [SWKVOObserverItem new];
item.keyPath = keyPath;
item.observer = observer;
item.callback = callback;
[observerArray addObject:item];
NSLog(@"%@--observerArray----", observerArray);
}else {
NSString *exceptionReason = [NSString stringWithFormat:@"%@ Class %@ setter SEL not found.", NSStringFromClass([self class]), keyPath];
NSException *exception = [NSException exceptionWithName:@"NotExistKeyExceptionName" reason:exceptionReason userInfo:nil];
[exception raise];
}
}
- (void)sw_removeObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath {
NSMutableArray<SWKVOObserverItem *> *observerArray = objc_getAssociatedObject(self, sw_KVOObserverAssociatedKey);
[observerArray enumerateObjectsUsingBlock:^(SWKVOObserverItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.observer == observer && [obj.keyPath isEqualToString:keyPath]) {
[observerArray removeObject:obj];
}
}];
if (observerArray.count == 0) { // 如果已经没有了observer,则把isa复原,销毁临时的KVO类
Class originalClass = [self class];
Class kvoClass = object_getClass(self);//获得的是isa指向
object_setClass(self, originalClass);
objc_disposeClassPair(kvoClass);
}
}
其中有一些小方法的实现细节,可以看ErenShi101/BlockKVO 其中根据KVO源码的原理,我们在手动实现时,需要注意的就是
- 在创建派生类时需要重写class方法和setter方法
- 通过一个观察者数组来添加被观察的对象,然后实现回调