前言
怎么看待勵(lì)志的書籍?
看再多糖荒,那都是別人的人生
一捶朵、KVO介紹
KVO(鍵值監(jiān)聽 Key-Value Observing),是OC觀察者設(shè)計(jì)模式的一種具體實(shí)現(xiàn)硼端。
作用:當(dāng)指定的對(duì)象的屬性被修改后,觀察者就會(huì)接受到監(jiān)聽通知的消息寓搬,開發(fā)者可以根據(jù)收到消息來進(jìn)行自定義處理句喷。
二、KVO使用
KVO的使用步驟大概分為三步:
- 1.添加觀察者兄春,實(shí)施監(jiān)聽
- 2.監(jiān)聽方法中處理屬性發(fā)生的變化
- 3.移除觀察者
具體例子如下:
#pragma mark - ------------runtime在KVO中的使用--------------
- (void)objc_KVO{
self.num = 0;
self.p = [[Person alloc] init];
self.p.name = @"firstName";
//添加觀察者
[self.p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionInitial context:NULL];
// [self.p test_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
}
//監(jiān)聽方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@-%@-%@", keyPath, change,self.p);
}
//移除監(jiān)聽
- (void)dealloc{
[self.p removeObserver:self forKeyPath:@"name"];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.num++;
self.p.name = [NSString stringWithFormat:@"name%ld",self.num];
}
三赶舆、KVO的底層原理
Apple官方文檔
Apple 的文檔有簡單提到過 KVO 的實(shí)現(xiàn):
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 ...
上面大概的意思就是:被觀察對(duì)象的 isa 指針會(huì)指向一個(gè)中間類芜茵,而不是原來真正的類九串。Apple并沒有說明具體的實(shí)現(xiàn)細(xì)節(jié)寺鸥。那我們就來嘗試猜測KVO的底層實(shí)現(xiàn)原理胆建。
驗(yàn)證原理
1.生成中間類NSKVONotifying_XXX
對(duì)上面的例子加4個(gè)斷點(diǎn):分別在添加觀察者之前、之后扑馁、以及監(jiān)聽方法的地方。
根據(jù)上面的斷點(diǎn)處沿侈,每個(gè)斷點(diǎn)打印出被觀察的類以及類的isa指針市栗,結(jié)果如下:
可以看出在我們添加觀察者之后,被觀察的類的isa變成了NSKVONotifying_Person
蛛淋,也就是說我們的類person變成了另外一個(gè)類褐荷。其實(shí)這個(gè)NSKVONotifying_XXX
類是我們被觀察者的子類,是動(dòng)態(tài)創(chuàng)建的层宫。
那什么時(shí)候我們的類會(huì)變成原始的類呢萌腿?那肯定是我們調(diào)用removeObserver
之后抖苦,就會(huì)變成原始的類。
這也驗(yàn)證了Apple官方文檔中贮庞,生成了一個(gè)中間類:NSKVONotifying_XXX
2.KVO只監(jiān)聽setter方法
我們正常定義一個(gè)屬性如下贸伐,系統(tǒng)會(huì)自動(dòng)生成一個(gè)setName
和getName
的方法怔揩,
@property (nonatomic, strong) NSString *name;
現(xiàn)在我們自定義set方法
@property (nonatomic, strong, setter=setname:) NSString *name;
//實(shí)現(xiàn)
- (void)setname:(NSString *)name{
_name = name;
}
注意這里的方法setname
不是標(biāo)準(zhǔn)命名的set方法商膊,當(dāng)我們點(diǎn)擊屏幕的時(shí)候修改name的時(shí)候,監(jiān)聽方法并沒有執(zhí)行藐翎。而當(dāng)我們是正常的setName
方法吝镣,監(jiān)聽方法就會(huì)執(zhí)行昆庇。同樣的方法驗(yàn)證getter方法,發(fā)現(xiàn)跟getter方法沒有關(guān)系拱撵。
也就是說KVO監(jiān)聽過程是通過setter方法來操作的。也就是說如果的setter方法命名不標(biāo)準(zhǔn)(set首字母大寫
)或者沒有實(shí)現(xiàn)乓旗,那么KVO監(jiān)聽則無效集索。
3.KVO重寫了class方法
上面的斷點(diǎn)中抄谐,添加觀察者之前是person類,添加觀察者之后是NSKVONotifying_Person
類毅厚。但是我們打印self.p卻發(fā)現(xiàn)還是打印出來person
而不是NSKVONotifying_Person
吸耿,其中的isa是中間類酷窥。這個(gè)是蘋果沒法沒有隱藏的,才是真正的類妆棒。name只是蘋果重寫了class方法糕珊,把class中結(jié)構(gòu)體的name指定為先前的類名而已毅糟,為了隱藏中間的類,返回還是真正的中間類喇肋。當(dāng)我們r(jià)emoveobserve后蝶防,isa指針又會(huì)指向我們原始的類明吩。把中間類銷毀了。
KVO的原理說明
當(dāng)我們添加KVO監(jiān)聽對(duì)象A時(shí)菱鸥,KVO機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象A所屬類的子類(NSKVONotifying_A)氮采,并且為這個(gè)子類動(dòng)態(tài)添加一個(gè)被觀察屬性keyPath的setter方法染苛,然后把A所屬類的isa指針指向新建子類茶行,所以當(dāng)我們調(diào)用A所屬類的setter方法其實(shí)調(diào)用的是新建子類的setter方法,setter方法隨后負(fù)責(zé)通知觀察對(duì)象屬性的改變狀況以及調(diào)用A所屬類的setter方法娶靡。
自定義模擬KVO底層實(shí)現(xiàn)
不關(guān)注KVO方法中后兩個(gè)參數(shù)姿锭,只是一些if和else填充的回調(diào)參數(shù)伯铣,這些就是一些細(xì)節(jié)考慮,這里只是關(guān)注和模擬kvo的流程實(shí)現(xiàn)焚鲜,不考慮一些容錯(cuò)處理忿磅,只關(guān)注原理實(shí)現(xiàn)犀斋,學(xué)習(xí)這種思想叽粹,知道kvo底層是如何運(yùn)轉(zhuǎn)起來的就可以了,我們沒必要重寫一個(gè)kvo機(jī)制锤灿,因?yàn)樘O果把這個(gè)封裝的很好了但校。當(dāng)然有興趣也可以嘗試實(shí)現(xiàn)一個(gè)完整的kvo啡氢。下面代碼基本上可以把整個(gè)流程寫清楚了术裸,看懂下面就差不多了袭艺。偽代碼我就不寫了猾编。
#import "NSObject+KVO.h"
#import <objc/message.h>
NSString *const kJYKVOClassPrefix = @"JYKVOClassPrefix_";//自定義類前綴
NSString *const kJYKVOAssociatedParameDict = @"JYKVOAssociatedParameDict";//參數(shù)key
NSString *const kJYKVOObservers = @"JYKVOObservers"; //觀察者key
NSString *const kJYKVOOldValue = @"JYKVOOldValue"; //舊值key
NSString *const kJYKVOKeyPath = @"JYKVOKeyPath"; //KeyPathkey
NSString *const kJYKVOSetter = @"JYKVOSetter"; //setter方法key
@implementation NSObject (KVO)
//添加觀察者
//下面采用面向過程編程方式答倡,所以下面這個(gè)函數(shù)有點(diǎn)長驴党,只是為了讓大家看清整個(gè)流程,
- (void)test_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
//獲取self所屬的類
Class originalClass = object_getClass(self);
NSString *originalClassStr = NSStringFromClass(originalClass);
//獲取setter方法字符串
NSString *setter = [NSString stringWithFormat:@"set%@:",[keyPath capitalizedString]];
//1.判斷被觀察者(self)對(duì)應(yīng)的keyPath有沒有setter方法,沒有則返回,添加觀察者失敗
BOOL haveSetter = NO;
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList(originalClass, &methodCount);
unsigned int i;
for(i = 0; i < methodCount; i++) {
NSString *methodName = NSStringFromSelector(method_getName(methodList[i]));
if ([methodName isEqualToString:setter]) {
haveSetter = YES;
break;
}
}
free(methodList);
if (!haveSetter) {
return;
}
//2.動(dòng)態(tài)生成一個(gè)前綴為JYKVOClassPrefix_的類,這個(gè)新類是self的子類
NSString *newClassStr = [NSString stringWithFormat:@"%@%@",kJYKVOClassPrefix,originalClassStr];
Class newClass = objc_allocateClassPair(originalClass, newClassStr.UTF8String, 0);
//3.為新類添加setter方法
class_addMethod(newClass, NSSelectorFromString(setter), (IMP)newClassSetterMethod, "v@:@");
//方法屬性添加完成后注冊這個(gè)類才算創(chuàng)建成功可以使用
objc_registerClassPair(newClass);
//4.修改被觀察者(self)的isa指針攘轩,讓isa指針指向新建的子類度帮。也就是說被觀察者(self)現(xiàn)在所屬于的類是新建的子類,
object_setClass(self, newClass);
//5.使用關(guān)聯(lián)值保存信息
NSMutableDictionary *parameDict = objc_getAssociatedObject(self, (__bridge const void *)(kJYKVOAssociatedParameDict));
if (!parameDict) {
parameDict = [NSMutableDictionary dictionary];
}
if (keyPath) {
[parameDict setValue:setter forKey:kJYKVOSetter];
[parameDict setValue:keyPath forKey:kJYKVOKeyPath];
}
//kvc獲取舊值
id oldValue = [self valueForKeyPath:keyPath];
if (oldValue) {
[parameDict setValue:oldValue forKey:kJYKVOOldValue];
}
//觀察者數(shù)組
if ([parameDict objectForKey:kJYKVOObservers] != nil) {
NSMutableArray *observers = [parameDict objectForKey:kJYKVOObservers];
[observers addObject:observer];
[parameDict setValue:observers forKey:kJYKVOObservers];
}else{
NSMutableArray *observers = [NSMutableArray array];
[observers addObject:observer];
[parameDict setValue:observers forKey:kJYKVOObservers];
}
//關(guān)聯(lián)
objc_setAssociatedObject(self, (__bridge const void *)kJYKVOAssociatedParameDict, parameDict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//新類setter方法的實(shí)現(xiàn)
static void newClassSetterMethod(id self, SEL _cmd, id newValue){
//1.設(shè)置當(dāng)前子類指向父類(原始的類)
//獲取當(dāng)前類,這個(gè)class就是我們新建的子類
Class newClass = object_getClass(self);
//獲取當(dāng)前類的父類,也就是我們最原始的類
Class superClass = class_getSuperclass(newClass);
//把當(dāng)前子類設(shè)置為父類率翅,也就是設(shè)置成我們原始的類,讓我們原始的類來調(diào)用setter方法腺晾,這樣就正常的調(diào)用我們原始的setter方法
object_setClass(self, superClass);
//2.調(diào)用父類的setter方法
//獲取關(guān)聯(lián)的參數(shù)
NSDictionary *parameDict = objc_getAssociatedObject(self, (__bridge const void *)(kJYKVOAssociatedParameDict));
NSString *setter = [parameDict objectForKey:kJYKVOSetter];
//消息發(fā)送調(diào)用setter方法
objc_msgSend(self, NSSelectorFromString(setter), newValue);
NSMutableArray *observers = [parameDict objectForKey:kJYKVOObservers];
NSString *keyPath = [parameDict objectForKey:kJYKVOKeyPath];
NSMutableDictionary<NSKeyValueChangeKey, id> *change = [NSMutableDictionary dictionary];
change[NSKeyValueChangeNewKey] = newValue;
change[NSKeyValueChangeOldKey] = [parameDict objectForKey:kJYKVOOldValue] != nil?[parameDict objectForKey:kJYKVOOldValue]:NULL;
for (NSObject *observer in observers) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),keyPath, self, change, NULL);
});
}
object_setClass(self, newClass);
[parameDict setValue:newValue forKey:kJYKVOOldValue];
objc_setAssociatedObject(self, (__bridge const void *)kJYKVOAssociatedParameDict, parameDict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//+ (Class)class{
//
// Class cls = object_getClass(self);
//
// cls->name = "截取原始類名";//這里蘋果不允許我們修改悯蝉,runtime源碼中可以查看和修改
// return self;
//}
@end
四鼻由、KVO的總結(jié)
通過上面我們知道KVO的底層原理,同時(shí)這種設(shè)計(jì)思想也值得我們學(xué)習(xí)蔼紧,通過中間類來偽裝很是巧妙歉井。希望根據(jù)這種思想來解決我們實(shí)際項(xiàng)目中的問題哈误。后面要講解的AOP編程的開源庫Aspect也是使用這種思想蜜自。期待你們能模仿一些思想寫出優(yōu)秀的開源庫卢佣。。只能說Runtime太強(qiáng)大啦