ランタイムカスタムKVO、理論解析を使用してください

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である 。1122である01.251412 + 0800ランタイム[ 184943545181オブジェクト観察された]:<学生: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),实现各种属性的监听实现。

具体操作自己动手去吧,不在本文做演示。

おすすめ

転載: www.cnblogs.com/XYQ-208910/p/11665770.html