查看: 2916|回复: 0

[手机开发] iOS菜鸟成长季——KVO进阶(四)

发表于 2018-1-19 08:00:03
前言

这篇写一下KVO的原理及手动实现

正文

先看这样一段代码

KVO

isa是真实类型,class是系统提供方法获取的类型。可见,对person进行观察后,从第三句输出可以看到,person的真实类型发生了改变,并且中间类继承自原类,即NSKVONtifying_Person -> Person
,而苹果欺骗了我们。结合前文写过的KVO内部通知,可以来看看KVO究竟做了那些事

重写被观察对象属性对应的setter 改变被观察对象的真实类型,且与原类为继承关系 重写被观察对象的(Class)class
知道苹果都干了啥,自己就可以开工了。

这里模仿如何自己动手实现 KVO写了一份代码

首先调试一下环境

图片描述

  1. @interface Person : NSObject
  2. @property (copy, nonatomic) NSString *name;
  3. @end
  4. @implementation Person
  5. - (instancetype)init {
  6. if (self = [super init]) {
  7. _name = @"default";
  8. }
  9. return self;
  10. }
  11. @end
复制代码

简单的搞个Person类,这没啥好说的

然后搞个NSObject的分类(利用block回调更方便,个人也不是很习惯苹果的API)

  1. typedef void(^JKKVOBlock)(id observedObj, NSString *observedKey, id oldValue, id newValue);
  2. @interface NSObject (JKKVO)
  3. - (void)jk_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
  4. - (void)jk_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(JKKVOBlock)block;
  5. @end
复制代码
  1. #import "NSObject+JKKVO.h"
  2. #import <objc/objc-runtime.h>
  3. NSString *const JKKVOClassPrefix = @"JKKVONotifying_";
  4. const char * JKKVOAssociateKey;
  5. @interface JKObserverInfo : NSObject
  6. @property (weak, nonatomic) NSObject *observer;
  7. @property (copy, nonatomic) NSString *key;
  8. @property (copy, nonatomic) JKKVOBlock block;
  9. @end
复制代码

接着就是最重要的部分,实现KVO。文中关键部分有注释,耐心点看应该不难懂

  1. @implementation JKObserverInfo
  2. - (instancetype)initWithObserver:(NSObject *)observer Key:(NSString *)key block:(JKKVOBlock)block {
  3. if (self = [super init]) {
  4. _observer = observer;
  5. _key = key;
  6. _block = block;
  7. }
  8. return self;
  9. }
  10. @end
  11. @implementation NSObject (JKKVO)
  12. - (NSString *)setterFromKey:(NSString *)key {
  13. NSMutableString *mutableKey = [NSMutableString stringWithString:key];
  14. [mutableKey replaceCharactersInRange:NSMakeRange(0, 1) withString:[[key substringToIndex:1] uppercaseString]];
  15. return [NSString stringWithFormat:@"set%@:", mutableKey];
  16. }
  17. - (Class)kvoObservedClassFromOriginClass:(Class)cls {
  18. NSString *clsStr = NSStringFromClass(cls);
  19. NSString *newClsStr = [JKKVOClassPrefix stringByAppendingString:clsStr];
  20. Class newCls = NSClassFromString(newClsStr);
  21. if (newCls) return newCls;
  22. newCls = objc_allocateClassPair(cls, newClsStr.UTF8String, 0);
  23. [self rewriteClassMethodWithNewClass:newCls];
  24. objc_registerClassPair(newCls);
  25. return newCls;
  26. }
  27. - (void)rewriteClassMethodWithNewClass:(Class)newCls {
  28. Method clsMethod = class_getInstanceMethod([self class], @selector(class));
  29. const char *types = method_getTypeEncoding(clsMethod);
  30. class_addMethod(newCls, @selector(class), (IMP)newClass, types);
  31. }
  32. Class newClass(id self, SEL _cmd) {
  33. return class_getSuperclass(object_getClass(self));
  34. }
  35. - (void)addObservers:(NSObject *)obs {
  36. // 设置关联属性用于添加观察者(因为在分类中,不能直接设置属性)
  37. NSMutableArray *observers = objc_getAssociatedObject(self, JKKVOAssociateKey);
  38. if (!observers) {
  39. observers = [NSMutableArray array];
  40. objc_setAssociatedObject(self, JKKVOAssociateKey, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  41. }
  42. [observers addObject:obs];
  43. }
  44. void newSetter(id self, SEL _cmd, id newValue) {
  45. NSString *setter = NSStringFromSelector(_cmd);
  46. NSString *getter = [self getterFromSetter:setter];
  47. if (!getter) {
  48. NSString *reason = [NSString stringWithFormat:@"找不到%@对应属性%@的setter", self, getter];
  49. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil];
  50. return;
  51. }
  52. NSString *key = [[getter componentsSeparatedByString:@":"] firstObject];
  53. id oldValue = [self valueForKey:key];
  54. struct objc_super supercls = {
  55. .receiver = self,
  56. .super_class = class_getSuperclass(object_getClass(self))
  57. };
  58. objc_msgSendSuper(&supercls, _cmd, newValue);
  59. NSMutableArray *observers = objc_getAssociatedObject(self, JKKVOAssociateKey);
  60. for (JKObserverInfo *obs in observers) {
  61. if ([obs.key isEqualToString:key]) {
  62. obs.block(self, key, oldValue, newValue);
  63. }
  64. }
  65. }
  66. - (NSString *)getterFromSetter:(NSString *)setter {
  67. NSString *getter = [setter substringFromIndex:3];
  68. NSString *firstLow = [getter substringToIndex:1].lowercaseString;
  69. return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstLow];
  70. }
  71. - (void)jk_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(JKKVOBlock)block {
  72. // 获取观察对象属性的setter
  73. NSString *setter = [self setterFromKey:key];
  74. SEL setterS = NSSelectorFromString(setter);
  75. Method setterM = class_getInstanceMethod([self class], setterS);
  76. if (!setterM) {
  77. NSString *reason = [NSString stringWithFormat:@"找不到%@对应属性%@的setter", self, key];
  78. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil];
  79. return;
  80. }
  81. // 生成中间类并重写 - (Class)class
  82. Class newCls = [self kvoObservedClassFromOriginClass:[self class]];
  83. // self 重定向
  84. object_setClass(self, newCls);
  85. // 重写setter
  86. const char *types = method_getTypeEncoding(setterM);
  87. class_addMethod(newCls, setterS, (IMP)newSetter, types);
  88. // 添加观察者
  89. JKObserverInfo *observerInfo = [[JKObserverInfo alloc] initWithObserver:observer Key:key block:block];
  90. [self addObservers:observerInfo];
  91. }
  92. - (void)jk_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
  93. NSMutableArray *observers = objc_getAssociatedObject(self, JKKVOAssociateKey);
  94. for (JKObserverInfo *obsInfo in observers) {
  95. if (obsInfo.observer == observer && [obsInfo.key isEqualToString:keyPath]) {
  96. [observers removeObject:obsInfo];
  97. break;
  98. }
  99. }
  100. }
  101. @end
复制代码

OK,大功告成!来测试一下吧
图片描述
然而并没有什么卵用,有些东西自己写着玩就好,要么就搞个开源框架,否则你永远不知道自己的代码有多烂



回复

使用道具 举报