I.はじめに
KVOは何ですか?フルネームキー値オブザーバ、キーの観察、オブザーバーデザインパターンの別の実装。その役割は、観察者のコールバック機能モニタのプロパティ値による変更にあります。
第二に、原則
ISAミックスを書くための技術を使用して達成するために、KVOランタイムベースのメカニズム、
リスナーの特性の変化は、クラスAをリッスンし、動的にクラスAのサブクラスを作成するシステムはNSKVONotifying_A、クラス、サブクラスのISAポインタとリダイレクションであります
システムは、クラスにセッターメソッドを書き換えます。(それぞれ前とコールwillChangeValueForKey割り当て後とdidChangeValueForKey古い値と新しい値を追跡します)
属性が変更されると、システムはリスナーに通知したときに、クラスA、observeValueForKey呼び出し:ofObject:変更:コンテキスト方法であってもよいです
第三に、アイコン
第三に、基本的な実現
学生カテゴリ
// 2019年10月12日に夏远全によって作成されます。 #import <財団/ Foundation.h> NS_ASSUME_NONNULL_BEGINの @interfaceの学生:NSObjectのの @property(アトミック、コピー)NSStringの * 名前を。 @終わり
ViewControllerを类
- (無効)test_objc_KVO { // オブジェクトの作成 self.stu = [[学生のalloc]のinit]; self.stu.name = @ " ジョー・スミスの" ; // 登録したオブザーバ [self.stuのaddObserver:自己forKeyPathを:@ " 名前" オプションを:NSKeyValueObservingOptionNewコンテキスト:NULLを]; // 値を変更 self.stu.name = @" ジョン・ドウ" ; } - (無効)observeValueForKeyPath:(NSStringの*)キーパスofObject :( ID)オブジェクトの変更:(NSDictionaryの<NSKeyValueChangeKeyを、上記のIDに述べた > *)の変更コンテキストを:(ボイド * {)コンテキスト のNSLog("@ %@ N- \観察対象物である:%@、特性が観察された:%変化値@ "、オブジェクト、キーパス、変更); }
ブレークポイントの結果:STUクラス型のオブジェクトが変更された、聞いた後、学生 - > NSKVONotifying_Student
印刷結果
2019 - 10 - 13である 。11:22である:01.251412 + 0800ランタイム[ 18494:3545181オブジェクト観察された]:<学生:0x600003d9f3c0 > 、観察属性:名前、値の変化:{ 種類 = 1 ; 新しい新しい = " \ U674e \ U56db " ; }
第四に、カスタム
(1)考えました:
- リスナーメソッドを追加します。
- サブクラスを作成します
- 親ポインタ書き換えISA
- 関連するオブザーバ
- setterメソッドをオーバーライドします
- 给父类发送setter消息
- 给观察者发送observeValueForKeyPath:ofObject:change:context:消息
(2)实现
Student类
// Created by 夏远全 on 2019/10/12. #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Student : NSObject @property (nonatomic, copy) NSString *name; ///自定义的监听方法 -(void)xyq_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; @end NS_ASSUME_NONNULL_END
// // Student.m // 运行时 // // Created by 夏远全 on 2019/10/12. // #import "Student.h" #import <objc/message.h> @implementation Student void setterMethod(id self, SEL _cmd, NSString *name) { //5、调用父类的方法 struct objc_super superClass = { self, class_getSuperclass([self class]) }; objc_msgSendSuper(&superClass, _cmd, name); //6、通知观察者调用observeValueForKeyPath:ofObject:change:context: id observer = objc_getAssociatedObject(self, (__bridge const void *)@"observer"); NSString *methodName = NSStringFromSelector(_cmd); NSString *key = getValueKey(methodName); objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), key, self, @{key:name}, nil); } /// 通过set方法截取属性名 NSString *getValueKey(NSString *setter){ //去掉setter if ([setter hasPrefix:@"set"]) { setter = [setter stringByReplacingOccurrencesOfString:@"set" withString:@""]; } //去掉: if ([setter hasSuffix:@":"]) { setter = [setter stringByReplacingOccurrencesOfString:@":" withString:@""]; } //首字母小写 NSString *key = [setter stringByReplacingOccurrencesOfString:[setter substringToIndex:1] withString:[[setter substringToIndex:1] lowercaseString] options:0 range:NSMakeRange(0,1)]; return key; } - (void)xyq_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context { //1、生成子类 const char * clazz = class_getName([self class]); NSString *className = [NSString stringWithCString:clazz encoding:NSUTF8StringEncoding]; NSString *subClassName = [NSString stringWithFormat:@"NSKVONotifying_%@",className]; Class subClass = objc_getClass(subClassName.UTF8String); subClass = objc_allocateClassPair([self class], [subClassName UTF8String], 0); objc_registerClassPair(subClass); //2、isa指向子类 object_setClass(self, subClass); //3、关联观察者 objc_setAssociatedObject(self, (__bridge const void *)@"observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); //4、重写set方法 NSString *setNameStr = [NSString stringWithFormat:@"set%@:",keyPath.capitalizedString]; SEL setSelector = NSSelectorFromString(setNameStr); class_addMethod(subClass, setSelector, (IMP)setterMethod, "v@:@"); } @end
ViewController类
-(void)test_objc_KVO { //创建对象 self.stu = [[Student alloc] init]; self.stu.name = @"张三"; //注册观察者 [self.stu xyq_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL]; //修改值 self.stu.name = @"李四"; } -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"被观测对象:%@, 被观测的属性:%@, 值的改变: %@\n", object, keyPath, change); }
断点结果:监听前后,stu对象的类类型发生了变化,Student -> NSKVONotifying_Student
打印结果
2019-10-13 11:35:17.929772+0800 运行时[18720:3562649] 被观测对象:<NSKVONotifying_Student: 0x6000002e0420>, 被观测的属性:name, 值的改变: { name = "\U674e\U56db"; }
五、扩展
这个案例只是浅浅的探究了一下实现原理,其他这个还有更大的应用。
我们可以给NSObject创建一个分类NSObject(KVO),实现各种属性的监听实现。
具体操作自己动手去吧,不在本文做演示。