カスタムKVO
分析了KVO
実装の为了加深印象模仿系统的方式自定义一个KVO
原則。
まず、インターフェースデザイン
1.1基本機能のインタフェース
模倣システムのKVO
インタフェースには、次の基本的なインターフェイスを提供します。
- 登録オブザーバー
- 削除オブザーバー
- コールバック
// NSObject+LJLKVO.h
#import <Foundation/Foundation.h>
#import "LJLKVOInfo.h"
@interface NSObject (LJLKVO)
//注册观察者
- (void)ljl_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LJLKeyValueObservingOptions)options context:(nullable void *)context;
//回调
- (void)ljl_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
//移除观察者
- (void)ljl_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
抽象オブジェクトLJLKVOInfo
// LJLKVOInfo.h
#import <Foundation/Foundation.h>
typedef NS_OPTIONS(NSUInteger, LJLKeyValueObservingOptions) {
LJLKeyValueObservingOptionNew = 0x01,
LJLKeyValueObservingOptionOld = 0x02,
};
@interface LJLKVOInfo : NSObject
//这个地方 NSObject 要用 weak 进行修饰。
//因这个 对象在外层是(person)self -> mArray -> info - > observer(外层的VC) -> person
//如果使用stong 会造成循环引用
@property(nonatomic, weak) NSObject * observer;
@property(nonatomic, copy) NSString * keyPath;
@property(nonatomic, assign) LJLKeyValueObservingOptions options;
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LJLKeyValueObservingOptions)options;
@end
// LJLKVOInfo.m
#import "LJLKVOInfo.h"
@implementation LJLKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LJLKeyValueObservingOptions)options
{
self = [super init];
if (self) {
self.observer = observer;
self.keyPath = keyPath;
self.options = options;
}
return self;
}
@end
以下の登録機能オブザーバを完了します。
- 基準検査(観察セッター、濾過インスタンス変数という)に
- プロパティをチェック
setter
- 動的オブジェクトのサブクラスを作成します
BLKVOClass_xxx
isa-swizzling
- 書き換え
-class
、-dealloc
方法 - リライト
setter
- オブザーバー情報の保存、コールバック時にプロパティの変更
2.2.1基本的な考え方
- よる
keyPath
取得setter
とに従ってsetter
取得しkeyPath
、参照KVC
変換したいことを検索優先順位を、ノートkeyPath
例最初の文字を - LJLKVONotifying_は、動的に作成
_xxx
するたびので、すでに作成したかどうかを確認したい、一度だけの後に破棄されません作成したサブクラスを、 - オーバーライド方法もあったかどうかを確認します
- オーバーライド
class
メソッドは、呼び出すために外の世界ので、元のクラスである親クラスを返す[obj class]
右のタイプを取得します dealloc
いくつかの特別な方法は、中にARC
直接上書きすること、また、明示的に呼び出すことはできません[super dealloc]
、ために採用達成method-swizzling
図。- あなたがあれば、オブザーバを追加するときにも体重をチェックし
observer
、keyPath
そしてcontext
同じではなく、繰り返し追加されています - 削除オブザーバを設計し、
observer
、keyPath
、context
三つのパラメータを空にすることができ、などobserver
であるnil
そうでない場合は、他の2つのパラメータを削除するために正確に一致し、すべてのオブザーバの除去。 - 以来、
KVO
このようにオブジェクトに関連付けられた視聴者情報により達成保存、分類を通じて達成される、変数が順に現在の辞書関連するオブジェクトビューアに追加されるkeyPath
ためのkey
可変配列でありvalue
、配列要素は可変ですmodel
// NSObject+LJLKVO.m
#import "NSObject+LJLKVO.h"
#import <objc/message.h>
static NSString * const kLJLKVOPrefix = @"LJLKVONotifying_";
static NSString *const kLJLKVOAssiociateKey = @"kLJLKVO_AssiociateKey";
@implementation NSObject (LJLKVO)
-(void)ljl_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LJLKeyValueObservingOptions)options context:(void *)context
{
// 1、验证是否存在setter 方法,过滤实例变量
[self judgeSetterMethodFromKeyPath:keyPath];
// 2、动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa的指向 : LGKVONotifying_LGPerson
object_setClass(self, newClass);
// 4: 保存观察者信息
// 面向对象的封装LJLKVOInfo
LJLKVOInfo *info = [[LJLKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
// 5、处理多属性关联对象的问题
// 获取存放关联对象的数组。
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey));
// 如果不存在关联对象的数组就进行创建
if (!observerArr) {
observerArr = [NSMutableArray arrayWithCapacity:1];
[observerArr addObject:info];
// 创建关联对象集合
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
- (void)ljl_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context{
}
- (void)ljl_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
// 获取关联对象
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey));
if (observerArr.count<=0) {
return;
}
for (LJLKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
[observerArr removeObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
}
if (observerArr.count<=0) {
// 将对象的 isa 指回给父类
Class superClass = [self class];
object_setClass(self, superClass);
}
}
#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
// 1、获取当前类的父类
Class superClass = object_getClass(self);
// 2、方法编号
SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
// 3、方法实现
Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
if (!setterMethod) {
// 如果没有好到setter方法就抛出异常
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"没有找到当前 %@ 的setter",keyPath] userInfo:nil];
}
}
#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
// 1、 获取旧的类名
NSString *oldClassName = NSStringFromClass([self class]);
// 2、生成新的类名
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLJLKVOPrefix,oldClassName];
// 3、用生成的新的类名去获取类
Class newClass = NSClassFromString(newClassName);
// 4、如果获取到了就直接返回。防止重复创建生成新类
if (newClass) return newClass;
/**
* 如果内存不存在,创建生成
* 参数一: 父类
* 参数二: 新类的名字
* 参数三: 新类的开辟的额外空间
*/
// 2.1 : 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 : 注册类
objc_registerClassPair(newClass);
// 2.3.1 : 添加class : class的指向是LJLKVOPerson
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
// class 方法 IMP 是lg_clss 所以后面再调[self class] 是调用的lg_class
// 2.3.2 : 添加setter
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
return newClass;
}
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
// 4: 消息转发 : 转发给父类
// 改变父类的值 --- 可以强制类型转换
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
// 向父类发送消息需要的参数
void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
lg_msgSendSuper(&superStruct,_cmd,newValue);
// 1: 取出观察者
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey));
for (LJLKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
// 对新旧值进行处理
if (info.options & LJLKeyValueObservingOptionNew) {
[change setObject:newValue forKey:NSKeyValueChangeNewKey];
}
if (info.options & LJLKeyValueObservingOptionOld) {
[change setObject:@"" forKey:NSKeyValueChangeOldKey];
if (oldValue) {
[change setObject:oldValue forKey:NSKeyValueChangeOldKey];
}
}
// 2: 消息发送给观察者 (回调)
SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:change:context:);
objc_msgSend(info.observer,observerSEL,keyPath,self,change,NULL);
// 把self 换成superClass更好
// objc_msgSend(info.observer,observerSEL,keyPath,class_getSuperclass(self),change,NULL);
});
}
}
}
Class lg_class(id self,SEL _cmd){
// 这个地方过来的self 是对象,需要获取他的类 object_getClass(self)
return class_getSuperclass(object_getClass(self));
}
#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];
return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}
上記は、完全なKVOが達成されています。以下、それを呼び出してみてください
self.person = [[LJLKVOPerson alloc] init];
[self.person ljl_addObserver:self
forKeyPath:@"name"
options:(LJLKeyValueObservingOptionOld | LJLKeyValueObservingOptionNew)
context:NULL];
[self printClassAllMethod:[LJLKVOPerson class]];
[self printClasses:[LJLKVOPerson class]];
self.person.name = @"LJL";
self.person->_nikcName = @"1234";
多くの場合、もはやはみ出さないだろう特定のコンテンツを印刷し、通常の印刷をテストします。あなたは上の記事を参照することができ KVO - iOSの基礎となる分析
要約します
カスタムKVO
1、チェック。
そこsetterメソッドかどうかを確認するために:インスタンス変数が来るように
2を、動的に生成されたサブクラスを
- LJLKVONotifying_xxx:動的新しいカテゴリを開きます
- Registrationクラス
- クラスを追加します。クラスポイントはLGPersonです
- setterメソッド:セッターによる割り当て、親クラスの実装self.name = @「LJL」。
- ライブ私たち視聴者に関連付けられているobjc_getAssociatedObject
3、ポイントLJLKVONotifying_LGPersonのISA
4、メッセージ転送:対応するコールバック
この方法は、それを書き換えるために、なぜ動的な子クラスを生成するために前の記事KVO分析はセッター、クラス、deallocを、_isKVOA方法、のdeallocをオーバーライドしますか?何を行うことができますか?
deallocデストラクタが解放されます。手動ビューアを削除する必要はありませんKVOの必要性は、これはあまりにも面倒で、ケースはいくつかの問題を取り除くために忘れてしまう場合は、今あなたはKVOが自動的に解除されますカスタマイズしたいです。
//NSObject+LJLKVO.m
//这里不能直接写dealloc 因为这是NSObject 的分类会有很多系统的类调用到(在还没启动的时候都有可能会被调用到)
//- (void)dealloc{
// Class superClass = [self class];
// object_setClass(self, superClass);
//}
//进行方法交换
+ (BOOL)kc_hookOrigInstanceMenthod:(SEL)oriSEL newInstanceMenthod:(SEL)swizzledSEL {
Class cls = self;
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
if (!swiMethod) {
return NO;
}
if (!oriMethod) {
class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
return YES;
}
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self kc_hookOrigInstanceMenthod:NSSelectorFromString(@"dealloc") newInstanceMenthod:@selector(myDealloc)];
});
}
- (void)myDealloc{
//将isa 指针指回去
Class superClass = [self class];
object_setClass(self, superClass);
[self myDealloc];
}
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
......
// 2.3.3 : 添加dealloc
SEL deallocSEL = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
const char *deallocTypes = method_getTypeEncoding(deallocMethod);
class_addMethod(newClass, deallocSEL, (IMP)lg_dealloc, deallocTypes);
//进行方法交换,然后myDealloc 的时候释放 但是这样写的话如果
// Method m1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
// Method m2 = class_getInstanceMethod([self class], @selector(myDealloc));
// method_exchangeImplementations(m1, m2);
// method_exchangeImplementations(m1, m2);
return newClass;
}
ofObject:変更:コンテキストobserveValueForKeyPath -模倣KVOにシステムに加えて、また代わりに、ブロックコールバックコールバックメソッド使用する、機能KVOを達成し 、; ならびに自動的ことなく放出さ、コンテンツに対応するオブジェクトを知っているイージーリスニングとを他の問題原因を忘れないよう、手動で削除します。完全なコード:LGKVO
拡張:
最後に、問題objc_msgSendコールについてのトークが与えられています。必要に設定を変更するときに使用するパラメータを渡す必要があります