談一談你對(duì)KVO的理解浪规?
A:添加響應(yīng)者结蟋,監(jiān)聽(tīng)對(duì)象變化脯倚,當(dāng)對(duì)象改變時(shí)調(diào)用代理。
B:動(dòng)態(tài)創(chuàng)建NSKVONotifying_XX類,修改被監(jiān)聽(tīng)對(duì)象isa指針指向,只要調(diào)用對(duì)象的set方法,
就會(huì)調(diào)用NSKVONotifying_XX的set方法采盒。本質(zhì):判斷對(duì)象的set方法有沒(méi)有被調(diào)用
二、蘋果官方文檔描述
Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ..
以上文檔的基本內(nèi)容是:
“當(dāng)對(duì)象注冊(cè)觀察者時(shí)憎妙,對(duì)象的ISA指針被修改,指向中間類,而不是原來(lái)真正的類...”,這是蘋果官方給出的文檔尊残。聽(tīng)到是不是很懵炒瘸?沒(méi)關(guān)系,未了解之前夜郁,我和你一樣什燕,看了這篇文章后你將和我一樣(小裝一下粘勒,勿噴)竞端。
舉例說(shuō)明:(監(jiān)聽(tīng)一個(gè)Person類底層實(shí)現(xiàn))
2.1)、動(dòng)態(tài)創(chuàng)建NSKVONotifying_Person,NSKVONotifying_Person是Person子類庙睡,做KVO事富;
2.2)、修改當(dāng)前對(duì)象的isa指針->NSKVONotifying_Person乘陪;
2.3)统台、只要調(diào)用對(duì)象的set,就會(huì)調(diào)用NSKVONotifying_Person的set方法;
2.4)啡邑、重寫NSKVONotifying_Person的set方法贱勃,1[super set:] 2、通知觀察者谤逼,告訴屬性改變贵扰。
三、核心代碼
/**
?添加觀察者?(addObserver:?forKeyPath:?options:?context:)
?@param?observer?觀察者
?@param?keyPath?監(jiān)聽(tīng)對(duì)象屬性
?@param?options?屬性配置
?@param?context?上下文
?*/
-?(void)addObserver:(NSObject?*)observer?forKeyPath:(NSString?*)keyPath
????????????options:(NSKeyValueObservingOptions)options
????????????context:(void*)context
{
????/*
?????*?options
?????*
?????*?NSKeyValueObservingOptionNew???????//?change字典包括改變后的值
?????*?NSKeyValueObservingOptionOld???????//?change字典包括改變前的值
?????*?NSKeyValueObservingOptionInitial???//?注冊(cè)后立刻觸發(fā)KVO通知
?????*?NSKeyValueObservingOptionPrior?????//?值改變前是否也要通知(通知兩次)
?????*/
}
/**
?監(jiān)聽(tīng)按鈕狀態(tài)改變的方法
?@param?keyPath?改變的屬性
?@param?object?被監(jiān)聽(tīng)對(duì)象
?@param?change?改變后的數(shù)據(jù)
?@param?context?注冊(cè)監(jiān)聽(tīng)時(shí)context傳遞過(guò)來(lái)的值
?*/
-?(void)observeValueForKeyPath:(NSString?*)keyPath
??????????????????????ofObject:(id)object
????????????????????????change:(NSDictionary?*)change
???????????????????????context:(void*)context
{
}
到此為止流部,這是我以前了解的所有KVO了戚绕,就這么點(diǎn)東西,用起來(lái)嗖嗖嗖啊枝冀。但是舞丛,大神永遠(yuǎn)是最騷的。前幾天看到一個(gè)大神覺(jué)得系統(tǒng)的KVO不好用果漾,自定義了一個(gè)球切,居然還是15年寫的,我勒個(gè)槽槽槽绒障,如何自己動(dòng)手實(shí)現(xiàn) KVO,就是這篇文章欧聘,讀了半天,終于弄明白了端盆,總結(jié)總結(jié)吧怀骤,別白看了。
首先焕妙,懷著虛榮的心先了解一下蒋伦,為什么要自定義?對(duì)啊焚鹊,蘋果給的多好用痕届,干嘛還要自定義去韧献,脫褲子放屁(多此一舉嘛),別著急研叫,聽(tīng)聽(tīng)big borther 怎么說(shuō)锤窑。
四、進(jìn)階
4.1嚷炉、KVO 缺陷
big brother原話:
"KVO 很強(qiáng)大渊啰,沒(méi)錯(cuò)。知道它內(nèi)部實(shí)現(xiàn)申屹,或許能幫助更好地使用它绘证,或在它出錯(cuò)時(shí)更方便調(diào)試。但官方實(shí)現(xiàn)的 KVO 提供的 API 實(shí)在不怎么樣哗讥。
比如嚷那,你只能通過(guò)重寫 -observeValueForKeyPath:ofObject:change:context: 方法來(lái)獲得通知。想要提供自定義的 selector 杆煞,不行魏宽;想要傳一個(gè) block ,門都沒(méi)有决乎。而且你還要處理父類的情況 - 父類同樣監(jiān)聽(tīng)同一個(gè)對(duì)象的同一個(gè)屬性队询。但有時(shí)候,你不知道父類是不是對(duì)這個(gè)消息有興趣瑞驱。雖然 context 這個(gè)參數(shù)就是干這個(gè)的娘摔,也可以解決這個(gè)問(wèn)題 - 在 -addObserver:forKeyPath:options:context: 傳進(jìn)去一個(gè)父類不知道的 context。但總覺(jué)得框在這個(gè) API 的設(shè)計(jì)下唤反,代碼寫的很別扭凳寺。至少至少,也應(yīng)該支持 block 吧彤侍。
有不少人都覺(jué)得官方 KVO 不好使的肠缨。Mike Ash 的 Key-Value Observing Done Right,以及獲得不少分享討論的 KVO Considered Harmful 都把 KVO 拿出來(lái)吊打了一番盏阶。所以在實(shí)際開(kāi)發(fā)中 KVO 使用的情景并不多晒奕,更多時(shí)候還是用 Delegate 或 NotificationCenter。"
目的:自定義一個(gè)KVO
操作:創(chuàng)建 NSObject+KVO 類名斟,封裝自定義API脑慧;
4.2、創(chuàng)建一個(gè)NSObject的Category,?.h文件中暴露兩個(gè)API砰盐,用于添加和刪除KVO闷袒。例:
#import//?宏定義
NSString?*constkPGKVOClassPrefix?=?@"PGKVOClassPrefix_";
NSString?*constkPGKVOAssociatedObservers?=?@"PGKVOAssociatedObservers";
/**?監(jiān)聽(tīng)回調(diào)用block?*/
typedef?void(^PGObservingBlock)(id?observedObject,
????????????????????????????????NSString?*observedKey,
????????????????????????????????id?oldValue,?id?newValue);
@interfaceNSObject?(KVO)
/**?添加觀察者?*/
-?(void)PG_addObserver:(NSObject?*)observer
????????????????forKey:(NSString?*)key
?????????????withBlock:(PGObservingBlock)block;
/**?移除觀察者?*/
-?(void)PG_removeObserver:(NSObject?*)observer
???????????????????forKey:(NSString?*)key;
@end
.m內(nèi)部實(shí)踐
/*
?*?1、檢查對(duì)象的類有沒(méi)有相應(yīng)的?setter?方法岩梳。如果沒(méi)有拋出異常囊骤;
?*?2晃择、檢查對(duì)象?isa?指向的類是不是一個(gè)?KVO?類。如果不是也物,新建一個(gè)繼承原來(lái)類的子類宫屠,并把?isa?指向這個(gè)新建的子類;
?*?3滑蚯、檢查對(duì)象的?KVO?類重寫過(guò)沒(méi)有這個(gè)?setter?方法浪蹂。如果沒(méi)有,添加重寫的?setter?方法膘魄;
?*?4乌逐、添加這個(gè)觀察者
?*/
-?(void)PG_addObserver:(NSObject?*)observer
????????????????forKey:(NSString?*)key
?????????????withBlock:(PGObservingBlock)block
{
????/*
?????一竭讳、?檢查對(duì)象的類有沒(méi)有相應(yīng)的setter方法创葡,如果沒(méi)有拋出異常
????具體細(xì)節(jié):
????????1.1)、先通過(guò)?setterForGetter()?方法獲得相應(yīng)的?setter?的名字(SEL)绢慢。
????????1.2)灿渴、把key的首字母大寫;?前面加上set胰舆;?key就變成了setKey骚露。
????????1.3)、再用class_getInstanceMethod去獲得setKey:的實(shí)現(xiàn)(Method)缚窿,如果沒(méi)有棘幸,自然要拋出異常
?????*/
????SEL?setterSelector?=?NSSelectorFromString(setterForGetter(key));
????Method?setterMethod?=?class_getInstanceMethod([self?class],?setterSelector);
????if(!setterMethod)
????{
????????NSString?*reason?=?[NSString?stringWithFormat:
????????????????????????????@"Object?%@?does?not?have?a?setter?for?key?%@",?self,?key];
????????@throw[NSException?exceptionWithName:NSInvalidArgumentException
???????????????????????????????????????reason:reason
?????????????????????????????????????userInfo:nil];
????????return;
????}
????/*
?????二、檢查對(duì)象isa指向的類是不是一個(gè)KVO類倦零。如果不是误续,新建一個(gè)繼承原來(lái)類的子類,并把isa指向這個(gè)新建的子類
?????*/
????Class?clazz?=?object_getClass(self);
????NSString?*clazzName?=?NSStringFromClass(clazz);
????//?2.1)扫茅、先看類名有沒(méi)有我們定義的前綴蹋嵌。如果沒(méi)有,我們就去創(chuàng)建新的子類
????if(![clazzName?hasPrefix:kPGKVOClassPrefix])
????{
????????clazz?=?[self?makeKvoClassWithOriginalClassName:clazzName];
????????object_setClass(self,?clazz);
????}
????//?2.2)葫隙、到這里為止,?object的類已不是原類了,?而是KVO新建的類
????//?2.3)栽烂、例如官方的API:?Person?->?NSKVONotifying_Person()
????//?2.4)、kPGKVOClassPrefix是自己定義的一個(gè)宏恋脚,便于區(qū)分系統(tǒng)
????/*
?????三腺办、重寫setter方法。新的setter在調(diào)用原setter方法后糟描,通知每個(gè)觀察者(調(diào)用之前傳入的block)
?????*/
????if(![self?hasSelector:setterSelector])
????{
????????constchar?*types?=?method_getTypeEncoding(setterMethod);
????????class_addMethod(clazz,?setterSelector,?(IMP)kvo_setter,?types);
????}
????/*
?????四怀喉、把這個(gè)觀察的相關(guān)信息存在associatedObject里。
?????具體相關(guān):
????????觀察的相關(guān)信息(觀察者蚓挤,被觀察的?key,?和傳入的?block?)封裝在?PGObservationInfo?類里磺送。
?????*/
????PGObservationInfo?*info?=?[[PGObservationInfo?alloc]?initWithObserver:observer?Key:key?block:block];
????NSMutableArray?*observers?=?objc_getAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers));
????if(!observers)
????{
????????observers?=?[NSMutableArray?array];
????????objc_setAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers),?observers,?OBJC_ASSOCIATION_RETAIN_NONATOMIC);
????}
????[observers?addObject:info];
}
這就是自定義的核心代碼了驻子,內(nèi)容注視都整理在里面,有幾個(gè)方法需要具體介紹一下:
4.3估灿、核心方法解讀:
setterForGetter
getterForSetter
makeKvoClassWithOriginalClassName
kvo_class
PG_removeObserver
4.3.1崇呵、setterForGetter (name -> Name -> setName:)
/**?根據(jù)getter方法名獲得對(duì)應(yīng)的setter方法名?*/
staticNSString?*?setterForGetter(NSString?*getter)
{
????if(getter.length?<=?0)?{
????????returnnil;
????}
????//?upper?case?the?first?letter
????NSString?*firstLetter?=?[[getter?substringToIndex:1]?uppercaseString];
????NSString?*remainingLetters?=?[getter?substringFromIndex:1];
????//?add?'set'?at?the?begining?and?':'?at?the?end
????NSString?*setter?=?[NSString?stringWithFormat:@"set%@%@:",?firstLetter,?remainingLetters];
????returnsetter;
}
4.3.2、getterForSetter(setName: -> Name -> name)
/**?根據(jù)setter方法名獲得對(duì)應(yīng)的getter方法名?*/
staticNSString?*?getterForSetter(NSString?*setter)
{
????if(setter.length?<=0||?![setter?hasPrefix:@"set"]?||?![setter?hasSuffix:@":"])?{
????????returnnil;
????}
????//?remove?'set'?at?the?begining?and?':'?at?the?end
????NSRange?range?=?NSMakeRange(3,?setter.length?-?4);
????NSString?*key?=?[setter?substringWithRange:range];
????//?lower?case?the?first?letter
????NSString?*firstLetter?=?[[key?substringToIndex:1]?lowercaseString];
????key?=?[key?stringByReplacingCharactersInRange:NSMakeRange(0,?1)
???????????????????????????????????????withString:firstLetter];?
????returnkey;
}
4.3.3馅袁、makeKvoClassWithOriginalClassName
-?(Class)makeKvoClassWithOriginalClassName:(NSString?*)originalClazzName
{
????//?生成kPGKVOClassPrefix_class的類名
????NSString?*kvoClazzName?=?[kPGKVOClassPrefix?stringByAppendingString:originalClazzName];
????Class?clazz?=?NSClassFromString(kvoClazzName);
????//?如果kvo?class已經(jīng)被注冊(cè)過(guò)了,?則直接返回
????if(clazz)?{
????????returnclazz;
????}
????/*
?????*??如果kvo?class不存在,?則創(chuàng)建這個(gè)類
?????*??class?doesn't?exist?yet,?make?it
?????*/
????Class?originalClazz?=?object_getClass(self);
????Class?kvoClazz?=?objc_allocateClassPair(originalClazz,?kvoClazzName.UTF8String,?0);
????/*
?????*??修改kvo?class方法的實(shí)現(xiàn),?學(xué)習(xí)Apple的做法,?隱瞞這個(gè)kvo_class
?????*??grab?class?method's?signature?so?we?can?borrow?it
?????*/
????Method?clazzMethod?=?class_getInstanceMethod(originalClazz,?@selector(class));
????constchar?*types?=?method_getTypeEncoding(clazzMethod);
????class_addMethod(kvoClazz,?@selector(class),?(IMP)kvo_class,?types);
????//?注冊(cè)kvo_class
????objc_registerClassPair(kvoClazz);
????returnkvoClazz;
}
4.3.4域慷、kvo_class
1
2
3
4
staticClass?kvo_class(id?self,?SEL?_cmd)
{
????returnclass_getSuperclass(object_getClass(self));
}
4.3.5、kvo_setter
#pragma?mark?-?Overridden?Methods
/**?重寫setter方法,?新方法在調(diào)用原方法后,?通知每個(gè)觀察者(調(diào)用傳入的block)?*/
staticvoidkvo_setter(id?self,?SEL?_cmd,?id?newValue)
{
????NSString?*setterName?=?NSStringFromSelector(_cmd);
????NSString?*getterName?=?getterForSetter(setterName);
????//?如果不存在getter方法
????if(!getterName)
????{
????????NSString?*reason?=?[NSString?stringWithFormat:@"Object?%@?does?not?have?setter?%@",?self,?setterName];
????????@throw[NSException?exceptionWithName:NSInvalidArgumentException
???????????????????????????????????????reason:reason
?????????????????????????????????????userInfo:nil];
????????return;
????}
????//?獲取舊值
????id?oldValue?=?[self?valueForKey:getterName];
????//?調(diào)用原類的setter方法
????struct?objc_super?superclazz?=?{
????????.receiver?=?self,
????????.super_class?=?class_getSuperclass(object_getClass(self))
????};
????/*
?????*???這里需要做個(gè)類型強(qiáng)轉(zhuǎn),?否則會(huì)報(bào)too?many?argument的錯(cuò)誤
?????*???cast?our?pointer?so?the?compiler?won't?complain
?????*/
????void(*objc_msgSendSuperCasted)(void*,?SEL,?id)?=?(void*)objc_msgSendSuper;
????/*
?????*???call?super's?setter,?which?is?original?class's?setter?method
?????*/
????objc_msgSendSuperCasted(&superclazz,?_cmd,?newValue);
????/*
?????*??找出觀察者的數(shù)組,?調(diào)用對(duì)應(yīng)對(duì)象的callback
?????*??look?up?observers?and?call?the?blocks
?????*/
????NSMutableArray?*observers?=?objc_getAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers));
?????//?遍歷數(shù)組
????for(PGObservationInfo?*eachinobservers)
????{
????????if([each.key?isEqualToString:getterName])
????????{
????????????//?gcd異步調(diào)用callback
????????????dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0),?^{
????????????????each.block(self,?getterName,?oldValue,?newValue);
????????????});
????????}
????}
}
4.3.6汗销、PG_removeObserver
/**?移除觀察者?*/
-?(void)PG_removeObserver:(NSObject?*)observer?forKey:(NSString?*)key
{
????NSMutableArray*?observers?=?objc_getAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers));
????PGObservationInfo?*infoToRemove;
????for(PGObservationInfo*?info?inobservers)
????{
????????if(info.observer?==?observer?&&?[info.key?isEqual:key])
????????{
????????????infoToRemove?=?info;
????????????break;
????????}
????}
????[observers?removeObject:infoToRemove];
}
4.3.7犹褒、調(diào)用
[self.message?PG_addObserver:self?forKey:NSStringFromSelector(@selector(text))?withBlock:^(id?observedObject,?NSString?*observedKey,?id?oldValue,?id?newValue)?{
}];