1.觀察者中的context上下文參數(shù)可以防止重名(多個(gè)對(duì)象觀察的同名屬性區(qū)分)捐顷,性能,代碼可讀性膜眠,安全
2.觀察者在dealloc方法中要移除拗踢,若不移除脚牍,程序?qū)?huì)奔潰。
[self.student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
- (void)dealloc{
[self.student removeObserver:self forKeyPath:@"name" context:NULL];
}
3.單例對(duì)象的屬性觀察者巢墅,在兩個(gè)Controller中都對(duì)同一個(gè)屬性name進(jìn)行觀察诸狭,若沒有remove掉券膀,會(huì)引起野指針,無法判定是哪一個(gè)觀察者而崩潰
[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
中并不會(huì)產(chǎn)生循環(huán)引用驯遇,在底層添加的屬性observer是weak保存的self
4.手動(dòng)和自動(dòng)觀察芹彬,默認(rèn)為自動(dòng)觀察
手動(dòng)觀察:自動(dòng)開關(guān)關(guān)閉automaticallyNotifiesObserversForKey返回NO,增加[self willChangeValueForKey:@"nick"]和[self didChangeValueForKey:@"nick"]
[self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:NULL];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
// fastpath
// 性能 + 代碼可讀性
NSLog(@"%@",change);
}
// 自動(dòng)開關(guān)
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
return NO;
}
- (void)setNick:(NSString *)nick{
[self willChangeValueForKey:@"nick"];
_nick = nick;
[self didChangeValueForKey:@"nick"];
}
5.下載的進(jìn)度:已下載/總下載
[self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];
self.person.writtenData += 10;
self.person.totalData += 1;
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"downloadProgress"]) {
NSArray *affectingKeys = @[@"totalData", @"writtenData"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
// 自動(dòng)開關(guān)
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
return YES;
}
- (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];
}
6.數(shù)組的觀察:對(duì)于集合類型叉庐,屬于鍵值觀察舒帮,基于KVC,不能直接添加元素陡叠,需要將數(shù)組mutableArrayValueForKey保存
// 5: 數(shù)組觀察
self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
[self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL];
// KVC 集合 array
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"dateArray"];
}
NSKeyValueChangeSetting,表示觀察的屬性為非集合類型玩郊,如nick,kind為1;
NSKeyValueChangeInsertion,表示觀察的屬性為集合類型枉阵,如dataArray译红,kind為2
/* 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,
};
6.KVO底層原理,怎么實(shí)現(xiàn)觀察兴溜,KVO觀察的是setter方法
- 1.KVO只能觀察屬性侦厚,屬性有setter方法,不能觀察成員變量
觀察LGPerson中的成員變量name拙徽,點(diǎn)擊屏幕發(fā)現(xiàn)沒有打印變化刨沦,沒有觀察到
self.person = [[LGPerson alloc] init];
[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"實(shí)際情況:%@-%@",self.person.nickName,self.person->name);
self.person.nickName = @"KC";
self.person->name = @"Cooci";
}
#pragma mark - KVO回調(diào)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"name"];
[self.person removeObserver:self forKeyPath:@"nickName"];
}
-
2.KVO會(huì)形成中間類:原來的person對(duì)象的isa指向了LGPerson類,生成中間類后膘怕,isa不再指向LGPerson類已卷,isa指向發(fā)生了變化,添加觀察者后淳蔼,person對(duì)象的isa指向派生中間類NSKVONotifying_LGPerson,object_getClassName(self.person)獲取當(dāng)前isa的情況
查看添加觀察者前后的子類情況
中間類NSKVONotifying_LGPerson中有什么東西呢,方法裁眯,屬性
NSKVONotifying_LGPerson中的所有方法:setNickName:,class,dealloc(釋放監(jiān)聽),_isKVOA(是否是KVO)
setNickName:方法屬于繼承自LGPerson方法還是重寫呢鹉梨?新建LGPerson的子類LGStudent,查看LGStudent中的方法,若LGStudent也有上述NSKVONotifying_LGPerson中的所有方法(setNickName:,class,dealloc(釋放監(jiān)聽),_isKVOA(是否是KVO))穿稳,則表明NSKVONotifying_LGPerson中的所有方法來自于繼承,打印發(fā)現(xiàn)LGStudent中沒有任何方法存皂,故NSKVONotifying_LGPerson中的所有方法都來自重寫
這幾個(gè)方法都來自于重寫,在LGStudent中重寫setName:可以驗(yàn)證逢艘,打印出LGStudent中的setName:方法
添加觀察者后isa指向中間類NSKVONotifying_LGPerson旦袋,什么時(shí)候isa指回來LGPerson呢?是在移除觀察者的時(shí)候嗎?查看dealloc析構(gòu)函數(shù)中移除觀察者前后指針指向。
移除觀察者后發(fā)現(xiàn)NSKVONotifying_LGPerson注冊(cè)到內(nèi)存中后會(huì)一直存在它改,防止之后程序繼續(xù)添加觀察者疤孕,再重復(fù)開辟NSKVONotifying_LGPerson內(nèi)存空間,浪費(fèi)性能
setter 子類 - 父類改變 nickName 傳值
設(shè)置觀察點(diǎn)watchpoint set variable self->_person->_nickName
通過設(shè)置觀察點(diǎn)發(fā)現(xiàn)setNickName:在willChange和didChange之間,進(jìn)入了父類的setNickName:方法央拖,從中間類NSKVONotifying_LGPerson中調(diào)用了[super setNickName:]方法祭阀,所以是父類改變了nickName的值
7.自定義KVO
步驟如下:
// 1: 模擬系統(tǒng)
// 2: 移除觀察者 - 自動(dòng)移除
// 3: 響應(yīng)式+函數(shù)式
// 1: 驗(yàn)證是否存在setter方法 : 不讓實(shí)例進(jìn)來
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 動(dòng)態(tài)生成子類
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 2.1 申請(qǐng)類
// 2.2 注冊(cè)
// 2.3 添加方法
// 3: isa 指向
object_setClass(self, newClass);
// 4: 父類 setter
// 5: 觀察者去響應(yīng)
1.驗(yàn)證是否存在setter方法 : 不讓實(shí)例成員變量進(jìn)來,若觀察成員變量name鹉戚,則會(huì)報(bào)錯(cuò)
#pragma mark - 驗(yàn)證是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
Class superClass = object_getClass(self);
SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老鐵沒有當(dāng)前%@的setter",keyPath] userInfo:nil];
}
}
2.動(dòng)態(tài)生成子類:
I.申請(qǐng)類
II.注冊(cè)
III.添加方法:不能添加動(dòng)態(tài)成員變量,成員變量存在于ro中专控,ivar在read_images中就初始化和分配好了ivar空間抹凳,存在于ro,clean memory伦腐,不能再進(jìn)行添加了
但是方法和屬性添加在rwe中赢底,dirty memory,可以進(jìn)行添加
方法要是最原始的LGPerson中的setNickName方法,傳入[self class],
Method method = class_getInstanceMethod([self class], setterSel);
#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
if (newClass) return newClass;
// kLGKVOPrefix
// 2.1 申請(qǐng)類
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 注冊(cè)
objc_registerClassPair(newClass);
// 2.3 添加方法 - 屬性 - ivar - ro
SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
Method method = class_getInstanceMethod([self class], setterSel);
const char *type = method_getTypeEncoding(method);
class_addMethod(newClass, setterSel, (IMP)lg_setter, type);
return newClass;
}
3.指向isa
object_setClass(self, newClass);