《iOS底层原理文章汇总》
1.观察者中的context上下文参数可以防止重名(多个对象观察的同名属性区分),性能,代码可读性,安全
2.观察者在dealloc方法中要移除,若不移除,程序将会奔溃。
[self.student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
- (void)dealloc{
[self.student removeObserver:self forKeyPath:@"name" context:NULL];
}
3.单例对象的属性观察者,在两个Controller中都对同一个属性name进行观察,若没有remove掉,会引起野指针,无法判定是哪一个观察者而崩溃
[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
中并不会产生循环引用,在底层添加的属性observer是weak保存的self
4.手动和自动观察,默认为自动观察
手动观察:自动开关关闭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 *)change context:(void *)context{
// fastpath
// 性能 + 代码可读性
NSLog(@"%@",change);
}
// 自动开关
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
return NO;
}
- (void)setNick:(NSString *)nick{
[self willChangeValueForKey:@"nick"];
_nick = nick;
[self didChangeValueForKey:@"nick"];
}
5.下载的进度:已下载/总下载
[self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];
self.person.writtenData += 10;
self.person.totalData += 1;
+ (NSSet *)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 *)change context:(void *)context{
NSLog(@"%@",change);
}
// 自动开关
+ (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.数组的观察:对于集合类型,属于键值观察,基于KVC,不能直接添加元素,需要将数组mutableArrayValueForKey保存
// 5: 数组观察
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 *)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底层原理,怎么实现观察,KVO观察的是setter方法
- 1.KVO只能观察属性,属性有setter方法,不能观察成员变量
观察LGPerson中的成员变量name,点击屏幕发现没有打印变化,没有观察到
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 *)touches withEvent:(UIEvent *)event{
NSLog(@"实际情况:%@-%@",self.person.nickName,self.person->name);
self.person.nickName = @"KC";
self.person->name = @"Cooci";
}
#pragma mark - KVO回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"%@",change);
}
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"name"];
[self.person removeObserver:self forKeyPath:@"nickName"];
}
-
2.KVO会形成中间类:原来的person对象的isa指向了LGPerson类,生成中间类后,isa不再指向LGPerson类,isa指向发生了变化,添加观察者后,person对象的isa指向派生中间类NSKVONotifying_LGPerson,object_getClassName(self.person)获取当前isa的情况
查看添加观察者前后的子类情况
中间类NSKVONotifying_LGPerson中有什么东西呢,方法,属性
NSKVONotifying_LGPerson中的所有方法:setNickName:,class,dealloc(释放监听),_isKVOA(是否是KVO)
setNickName:方法属于继承自LGPerson方法还是重写呢?新建LGPerson的子类LGStudent,查看LGStudent中的方法,若LGStudent也有上述NSKVONotifying_LGPerson中的所有方法(setNickName:,class,dealloc(释放监听),_isKVOA(是否是KVO)),则表明NSKVONotifying_LGPerson中的所有方法来自于继承,打印发现LGStudent中没有任何方法,故NSKVONotifying_LGPerson中的所有方法都来自重写
这几个方法都来自于重写,在LGStudent中重写setName:可以验证,打印出LGStudent中的setName:方法
添加观察者后isa指向中间类NSKVONotifying_LGPerson,什么时候isa指回来LGPerson呢?是在移除观察者的时候吗?查看dealloc析构函数中移除观察者前后指针指向。
移除观察者后发现NSKVONotifying_LGPerson注册到内存中后会一直存在,防止之后程序继续添加观察者,再重复开辟NSKVONotifying_LGPerson内存空间,浪费性能
setter 子类 - 父类改变 nickName 传值
设置观察点watchpoint set variable self->_person->_nickName
通过设置观察点发现setNickName:在willChange和didChange之间,进入了父类的setNickName:方法,从中间类NSKVONotifying_LGPerson中调用了[super setNickName:]方法,所以是父类改变了nickName的值
7.自定义KVO
步骤如下:
// 1: 模拟系统
// 2: 移除观察者 - 自动移除
// 3: 响应式+函数式
// 1: 验证是否存在setter方法 : 不让实例进来
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 2.1 申请类
// 2.2 注册
// 2.3 添加方法
// 3: isa 指向
object_setClass(self, newClass);
// 4: 父类 setter
// 5: 观察者去响应
1.验证是否存在setter方法 : 不让实例成员变量进来,若观察成员变量name,则会报错
#pragma mark - 验证是否存在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:@"老铁没有当前%@的setter",keyPath] userInfo:nil];
}
}
2.动态生成子类:
I.申请类
II.注册
III.添加方法:不能添加动态成员变量,成员变量存在于ro中,ivar在read_images中就初始化和分配好了ivar空间,存在于ro,clean memory,不能再进行添加了
但是方法和属性添加在rwe中,dirty memory,可以进行添加
方法要是最原始的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 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 注册
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);