模拟系统KVO实现

1.主要方法
■添加通知 :cf_ddObserver...
■监听通知 :cf_observerValueForKey...
■删除通知 :cf_removeObserver...
2.核心工作
■ 修改 isa
■ 添加修改后的Setter方法
■ 添加修改后的class方法
■ 通知外界

NSObject+CFKVO.h
#import <Foundation/Foundation.h>
#import "CFObserverInfo.h"
@interface NSObject (CFKVO)
//添加KVO
- (void)cf_addObserver:(NSObject *)observer forKey:(NSString *)key options:(CFKeyValueObservingOptions)options;
//监听KVO
-(void)cf_observeValueForKey:(NSString *)key ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change;
//删除KVO
- (void)cf_removeObserver:(NSObject *)observer forKey:(NSString *)key;
@end

NSObject+CFKVO.m

#import "NSObject+CFKVO.h"
#import <objc/message.h>
#import <objc/runtime.h>
static const char KVO_observerArr;

static NSString *CFKVONotifying_=@"CFKVONotifying_";

@implementation NSObject (CFKVO)
/*
 *关键工作
 1.注册类,继承self
 2.修改isa
 3.重写setter方法
 4.重写class方法
 5.通知外界
 */
//也可以写成block形式
- (void)cf_addObserver:(NSObject *)observer forKey:(NSString *)key options:(CFKeyValueObservingOptions)options
{
    /*
     *1.获取setter方法名
     */
    NSString *setterName = [NSString stringWithFormat:@"set%@:",[key capitalizedString]];
    SEL setterSEL = NSSelectorFromString(setterName);
    /*
     *2.获取对应setter方法
     */
   Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    if (!setterMethod) {
        NSLog(@"指定key不存在,或者没有setter方法!");
        return;
    }
    /*
     *3.判断是否已经替换过isa
     */
    Class isaClass = object_getClass(self);
    NSString *isaName = NSStringFromClass(isaClass);
    if (![isaName hasPrefix:CFKVONotifying_]) {
        /*
         *4.注册新类
         */
        NSString *oldClassName = NSStringFromClass([self class]);
        NSString *isaClassName = [CFKVONotifying_ stringByAppendingString:oldClassName];
        isaClass = NSClassFromString(isaClassName);
        if (!isaClass) {
            //创建新类
            isaClass = objc_allocateClassPair([self class], [isaClassName UTF8String], 0);
            //注册新类
            objc_registerClassPair(isaClass);
        }
        /*
         *5.修改原类的isa
         */
        object_setClass(self, isaClass);
    }
    /*
     *6.添加setter方法
     此时[self class]=CFKVONotifying_xxx,或者用isaClass
     */
    class_addMethod([self class], setterSEL, (IMP)KVO_setter, method_getTypeEncoding(setterMethod));
    /*
     *7.添加class方法
     */
    SEL classSEL = @selector(class);
    Method classMethod = class_getInstanceMethod([self class], classSEL);
    class_addMethod([self class], classSEL, (IMP)KVO_class, method_getTypeEncoding(classMethod));
    /*
     *8.处理观察者
     */
    CFObserverInfo *info = [[CFObserverInfo alloc]initWithObserver:observer withKey:key withOptions:options];
    NSMutableArray *observerArr = objc_getAssociatedObject(self, &KVO_observerArr);
    if (!observerArr) {
        observerArr = [NSMutableArray array];
    }
    [observerArr addObject:info];
    objc_setAssociatedObject(self, &KVO_observerArr, observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
}

void KVO_setter(id self, SEL _cmd, id newValue)
{
    /*
     *(1)获取key setName:--->name
     */
    NSString *setterStr = NSStringFromSelector(_cmd);
    //key的首字母小写:n
    NSString *str1 = [[setterStr substringWithRange:NSMakeRange(3, 1)] lowercaseString];
    //key的剩余字母:ame
    NSString *str2 = [setterStr substringWithRange:NSMakeRange(4, [setterStr rangeOfString:@":"].location-4)];
    NSString *key = [NSString stringWithFormat:@"%@%@",str1,str2];
    
    /*
     *(2)获取以前的value值
     */
    id oldValue = [self valueForKey:key];
    
    /*
     *(3)调用父类的setter方法
     */
    struct objc_super superClass;
    superClass.receiver = self;
    superClass.super_class = class_getSuperclass(object_getClass(self));
    ((void (*)(void *,SEL,id))(void *)objc_msgSendSuper)(&superClass,_cmd,newValue);
    /*
     *(4)通知外界
     */
    NSMutableArray *observers = objc_getAssociatedObject(self, &KVO_observerArr);
    for (CFObserverInfo *info in observers) {
        if ([info.key isEqualToString:key]) {
           /*
            *(5)封装回传消息
            CFKeyValueObservingOptionNew|CFKeyValueObservingOptionOld = 11
            info.options&CFKeyValueObservingOptionNew =11&01 = 01
            info.options&CFKeyValueObservingOptionOld =11&10 = 10
            */ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                NSMutableDictionary<NSKeyValueChangeKey,id> *change =[NSMutableDictionary dictionaryWithCapacity:2];
                if (info.options&CFKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
                if (info.options&CFKeyValueObservingOptionOld) {
                    [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                }
                ((void (*)(id,SEL,id,id,id))(void *)objc_msgSend)(info.observer,@selector(cf_observeValueForKey:ofObject:change:),info.key,self,change);
            });
        }
    }
    
}

Class KVO_class(id self, SEL _cmd)
{
    //获取isa、在获取isa的父类
    return class_getSuperclass(object_getClass(self));
}

-(void)cf_observeValueForKey:(NSString *)key ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change
{
    
}

- (void)cf_removeObserver:(NSObject *)observer forKey:(NSString *)key;
{
    NSMutableArray *observers = objc_getAssociatedObject(self, &KVO_observerArr);
    if (!observers||observers.count<=0) {
        return;
    }
    for (CFObserverInfo *info in observers) {
        if ([info.key isEqualToString:key]) {
            [observers removeObject:info];
            objc_setAssociatedObject(self, &KVO_observerArr, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            break;
        }
    }
    /*
     *当observers为空时,重新设置isa
     */
    if(observers.count<=0)
    {
        object_setClass(self, [self class]);
    }
}

@end


NSObject+CFKVO.h
#import <Foundation/Foundation.h>
typedef NS_OPTIONS(NSUInteger, CFKeyValueObservingOptions)
{
    CFKeyValueObservingOptionNew = 0x01,        //01
    CFKeyValueObservingOptionOld = 0x02          //10
};
@interface CFObserverInfo : NSObject
//监听者
@property(nonatomic,strong)id observer;
//被监听的key
@property(nonatomic,copy)NSString *key;
//监听策略
@property(nonatomic,assign)CFKeyValueObservingOptions options;

- (instancetype)initWithObserver:(id)observer withKey:(NSString *)key withOptions:(CFKeyValueObservingOptions)options;
@end

NSObject+CFKVO.m

#import "CFObserverInfo.h"

@implementation CFObserverInfo
- (instancetype)initWithObserver:(id)observer withKey:(NSString *)key withOptions:(CFKeyValueObservingOptions)options
{
    self = [super init];
    if (self) {
        self.observer = observer;
        self.key = key;
        self.options = options;
    }
    return self;
}
@end

CFPerson.h

#import <Foundation/Foundation.h>
#import "NSObject+CFKVO.h"
@interface CFPerson : NSObject
@property(nonatomic,copy)NSString *name;
@end

CFPerson.m
#ifdef DEBUG
#define NSLog(FORMAT, ...) fprintf(stderr,"%s\n",[[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);
#else
#define NSLog(...)
#endif
#import "CFPerson.h"
@implementation CFPerson
- (void)cf_observeValueForKey:(NSString *)key ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change
{
    NSLog(@"属性%@改变之前的值为:%@",key,change[NSKeyValueChangeOldKey]);
    NSLog(@"属性%@改变之后的值为:%@",key,change[NSKeyValueChangeNewKey]);
}

main.m
#ifdef DEBUG
#define NSLog(FORMAT, ...) fprintf(stderr,"%s\n",[[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);
#else
#define NSLog(...)
#endif

#import <Foundation/Foundation.h>
#import "CFRuntimeKit.h"
#import "CFPerson.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CFPerson *person = [[CFPerson alloc]init];
        NSLog(@"变化之前========================");
        //1.方法列表
        NSLog(@"methodArr:%@",[CFRuntimeKit fetchMethodList:object_getClass(person)]);
        //2.class
        NSLog(@"class:%@",[person class]);
        //3.isa
        NSLog(@"isa:%@",object_getClass(person));
        //4.setter方法的imp
        NSLog(@"setterIMP:%p",[person methodForSelector:@selector(setName:)]);
        //5.class方法的imp
        NSLog(@"classIMP:%p",[person methodForSelector:@selector(class)]);
        
        person.name = @"lilei";
        [person cf_addObserver:person forKey:@"name" options:CFKeyValueObservingOptionNew|CFKeyValueObservingOptionOld];
        person.name = @"wanger";
        
        NSLog(@"变化之后========================");
        //1.方法列表
        NSLog(@"methodArr:%@",[CFRuntimeKit fetchMethodList:object_getClass(person)]);
        //2.class
        NSLog(@"class:%@",[person class]);
        //3.isa
        NSLog(@"isa:%@",object_getClass(person));
        //4.setter方法的imp
        NSLog(@"setterIMP:%p",[person methodForSelector:@selector(setName:)]);
        //5.class方法的imp
        NSLog(@"classIMP:%p",[person methodForSelector:@selector(class)]);
        
        [person cf_removeObserver:person forKey:@"name"];
        NSLog(@"还原之后========================");
        //1.方法列表
        NSLog(@"methodArr:%@",[CFRuntimeKit fetchMethodList:object_getClass(person)]);
        //2.class
        NSLog(@"class:%@",[person class]);
        //3.isa
        NSLog(@"isa:%@",object_getClass(person));
        //4.setter方法的imp
        NSLog(@"setterIMP:%p",[person methodForSelector:@selector(setName:)]);
        //5.class方法的imp
        NSLog(@"classIMP:%p",[person methodForSelector:@selector(class)]);
    }
    return 0;
}

变化之前========================
methodArr:(
        {
        methodName = "cf_observeValueForKey:ofObject:change:";
        methodType = "v40@0:8@16@24@32";
    },
        {
        methodName = ".cxx_destruct";
        methodType = "v16@0:8";
    },
        {
        methodName = name;
        methodType = "@16@0:8";
    },
        {
        methodName = "setName:";
        methodType = "v24@0:8@16";
    }
)
class:CFPerson
isa:CFPerson
setterIMP:0x100003180
classIMP:0x7fff6412d4b7
变化之后========================
属性name改变之前的值为:lilei
属性name改变之后的值为:wanger
methodArr:(
        {
        methodName = class;
        methodType = "#16@0:8";
    },
        {
        methodName = "setName:";
        methodType = "v24@0:8@16";
    }
)
class:CFPerson
isa:CFKVONotifying_CFPerson
setterIMP:0x1000036e0
classIMP:0x100003de0
还原之后========================
methodArr:(
        {
        methodName = "cf_observeValueForKey:ofObject:change:";
        methodType = "v40@0:8@16@24@32";
    },
        {
        methodName = ".cxx_destruct";
        methodType = "v16@0:8";
    },
        {
        methodName = name;
        methodType = "@16@0:8";
    },
        {
        methodName = "setName:";
        methodType = "v24@0:8@16";
    }
)
class:CFPerson
isa:CFPerson
setterIMP:0x100003180
classIMP:0x7fff6412d4b7
Program ended with exit code: 0
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容

  • 朋友说,初雪之时,要和最重要的人在一起。 这座小城的雪姗姗来迟,在全国各地飞雪漫天的时候,小城只是连阴了几日,飘了...
    素_尘阅读 860评论 0 1
  • 今晚强哥来喝酒,很好,我很开心。别看平时一个大大咧咧的老好人,其实心中都有一本账,有自己的思考。 人成熟不就是这样...
    20岁少年像木偶阅读 94评论 0 0