1、KVO簡介
KVO是鍵值觀察Key-Value Observing的簡稱轨功,在iOS開發(fā)中趁舀,可使用KVO來監(jiān)聽一個對象屬性的變化。
2、KVO的使用
2.1對某個類添加監(jiān)聽
//content后面填NULL不要填nil,官方介紹的,OC是C的超集
[self.person addObserver:self forKeyPath:@"nick" options:(NSKeyValueObservingOptionNew) context:NULL];
1.self.person是LGPerson的實例對象
2.給LGPerson的實例對象self.person添加觀察者self,監(jiān)聽self.person的屬性nick的新值變化(NSKeyValueObservingOptionNew)
context的使用:
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
- (void)registerAsObserverForAccount:(Account*)account {
[account addObserver:self
forKeyPath:@"balance"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:PersonAccountBalanceContext];
[account addObserver:self
forKeyPath:@"interestRate"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:PersonAccountInterestRateContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == PersonAccountBalanceContext) {
// Do something with the balance…
} else if (context == PersonAccountInterestRateContext) {
// Do something with the interest rate…
} else {
// Any unrecognized context must belong to super
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
2.2觀察者的回調(diào)方法焚刚。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
}
當(dāng)觀察者的屬性變化時,會來到這個回調(diào)方法扇调,keyPath是被監(jiān)聽的屬性,object是被觀察的對象抢肛,change是數(shù)據(jù)的變化狼钮。
2.3移除觀察者
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"nick"];
}
2.4數(shù)組類型的屬性監(jiān)聽。
self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
// 5: 可變數(shù)組 KVO --
[self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL];
// KVO 建立在 KVC
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"2"];
對象的屬性-數(shù)組捡絮,需要使用KVC的形式賦值才能監(jiān)聽
2.5多個因素綜合影響
// 4: 多個因素影響 - 下載進(jìn)度 = 當(dāng)前下載量 / 總量
[self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];
#import "LGPerson.h"
@implementation LGPerson
// 下載進(jìn)度 -- writtenData/totalData
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"downloadProgress"]) {
NSArray *affectingKeys = @[@"totalData", @"writtenData"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
- (NSString *)downloadProgress{
if (self.writtenData == 0) {
self.writtenData = 10;
}
if (self.totalData == 0) {
self.totalData = 100;
}
return [[NSString alloc] initWithFormat:@"%f",1.0f*self.writtenData/self.totalData];
}
2.6KVO開關(guān)
// 自動開關(guān)
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
return YES;
}
//單個屬性設(shè)置關(guān)閉
+ (BOOL) automaticallyNotifiesObserversForNick:(NSString *)nick{
return YES;
}
- (void)setNick:(NSString *)nick{
[self willChangeValueForKey:@"nick"];
_nick = nick;
[self didChangeValueForKey:@"nick"];
}
當(dāng)上面的方法返回NO熬芜,整個類的監(jiān)聽都被關(guān)掉,反之福稳,返回YES涎拉,就被打開。
2.7使用集合類型字典等
/* Possible values in the NSKeyValueChangeKindKey entry in change dictionaries. See the comments for -observeValueForKeyPath:ofObject:change:context: for more information.
*/
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
NSKeyValueChangeSetting = 1,
NSKeyValueChangeInsertion = 2,
NSKeyValueChangeRemoval = 3,
NSKeyValueChangeReplacement = 4,
};
/* Possible kinds of set mutation for use with -willChangeValueForKey:withSetMutation:usingObjects: and -didChangeValueForKey:withSetMutation:usingObjects:. Their semantics correspond exactly to NSMutableSet's -unionSet:, -minusSet:, -intersectSet:, and -setSet: method, respectively.
*/
typedef NS_ENUM(NSUInteger, NSKeyValueSetMutationKind) {
NSKeyValueUnionSetMutation = 1,
NSKeyValueMinusSetMutation = 2,
NSKeyValueIntersectSetMutation = 3,
NSKeyValueSetSetMutation = 4
};
3的圆、KVO的底層原理
3.1添加監(jiān)聽的處理
實現(xiàn)以下方法打印類及其子類
#pragma mark - 遍歷類以及子類
- (void)printClasses:(Class)cls{
// 注冊類的總數(shù)
int count = objc_getClassList(NULL, 0);
// 創(chuàng)建一個數(shù)組鼓拧, 其中包含給定對象
NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
// 獲取所有已注冊的類
Class* classes = (Class*)malloc(sizeof(Class)*count);
objc_getClassList(classes, count);
for (int i = 0; i<count; i++) {
if (cls == class_getSuperclass(classes[i])) {
[mArray addObject:classes[I]];
}
}
free(classes);
NSLog(@"classes = %@", mArray);
}
實現(xiàn)以下方法打印類的方法列表
#pragma mark - 遍歷方法-ivar-property
- (void)printClassAllMethod:(Class)cls{
NSLog(@"*********************");
unsigned int count = 0;
Method *methodList = class_copyMethodList(cls, &count);
for (int i = 0; i<count; i++) {
Method method = methodList[I];
SEL sel = method_getName(method);
IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
}
free(methodList);
}
通過監(jiān)聽前后對LGPerson及其子類的列表的打印,我們發(fā)現(xiàn)越妈,當(dāng)對季俩,LGPerson的對象監(jiān)聽后,系統(tǒng)自動生成了一個LGPerson的子類NSKVONotifying_LGPerson梅掠。再來看看NSKVONotifying_LGPerson的方法列表酌住。
從上圖可以得知店归,NSKVONotifying_LGPerson重寫了父類的setNickName方法。
note:注意只有監(jiān)聽屬性才有生成set方法酪我,監(jiān)聽成員變量是不會生成的消痛,比如監(jiān)聽name.
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject{
@public
NSString *name;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
- (void)sayLove;
@end
再來看看self.person 的isa指向。
可以看到self.person的isa指向都哭,有LGPerson變成了NSKVONotifying_LGPerson.
通過上圖可以分析得出:當(dāng)對一個對象的屬性進(jìn)行監(jiān)聽的時候秩伞,會生成對前對象所屬類的子類NSKVONotifying_XXX,并將這個對象的isa指向這個派生類。在這個派生類中质涛,重寫當(dāng)前屬性的setter方法稠歉。還重寫了class方法,[self.person class];返回的是LGPerson汇陆。還實現(xiàn)了一個isKVO的標(biāo)記怒炸,標(biāo)記當(dāng)前類實現(xiàn)監(jiān)聽的派生類。
3.2監(jiān)聽的銷毀
1毡代、從上圖可知阅羹,銷毀監(jiān)聽后,派生類NSKVONotifying_LGPerson依然存在教寂,這樣下次監(jiān)聽不需要重復(fù)創(chuàng)建了捏鱼。
2、銷毀后酪耕,對象的isa指針又指回了LGPerson.
對象銷毀后导梆,isa指回來,保證不出現(xiàn)混亂迂烁。
謹(jǐn)記:一定要移除觀察看尼,在使用RAC的時候要手動接受(dispose)并在dealloc中釋放掉。
如果觀察沒有移除會出現(xiàn)野指針的情況盟步。