kvc
四壶笼、KVO ( ) KVO 是觀察者模式的另一實現(xiàn)覆劈。
從上面的輸出可以看到,如果使用對象的 -class 方面輸出類名始終為:Foo炮障,這是因為新誕生的派生類重寫了 -class 方法聲稱它就是起初的基類坤候,只有使用 runtime 函數(shù) object_getClass 才能一睹芳容:NSKVONotifying_Foo白筹。注意看:x,y 以及 xy 三個被觀察對象真正的類型都是 NSKVONotifying_Foo系馆,而且該類實現(xiàn)了:setY:, setX:, class, dealloc, _isKVOA 這些方法顽照。其中 setX:, setY:, class 和 dealloc 前面已經(jīng)講到過代兵,私有方法 _isKVOA 估計是用來標示該類是一個 KVO 機制聲稱的類。在這里 Objective C 做了一些優(yōu)化植影,它對所有被觀察對象只生成一個派生類何乎,該派生類實現(xiàn)所有被觀察對象的 setter 方法,這樣就減少了派生類的數(shù)量抢野,提供了效率各墨。所有 NSKVONotifying_Foo 這個派生類重寫了 setX,setY方法(留意:沒有必要重寫 setZ 方法)恃轩。
原理:
1:動態(tài)生成子類:NSKVONotifiy_A
1:防止實例變量的影響,我添加斷言異常排除
2;動態(tài)子類的過程: A:生成類 B:添加Class方法 C:注冊
3:同時也判斷類的存在性做了處理
2:給動態(tài)子類添加Setter方法
3:消息轉(zhuǎn)發(fā)給父類(runtime消息轉(zhuǎn)發(fā))
使用了 isa 混寫(isa-swizzling)來實現(xiàn) KVO
使用 setter 方法改變值 KVO 會生效叉跛,使用 setValue:forKey 即 KVC 改變值 KVO 也會生效蒸殿,因為 KVC 會去
調(diào)用 setter 方法
那么通過直接賦值成員變量會觸發(fā) KVO 嗎?
不會,因為不會調(diào)用 setter 方法酥艳,需要加上
willChangeValueForKey 和 didChangeValueForKey 方法來手動觸發(fā)才行
KVC(Key-value coding)
KVC 就是指 iOS 的開發(fā)中,可以允許開發(fā)者通過 Key 名直接訪問對象的屬性莫换,或者給對象的屬性賦值骤铃。而 不需要調(diào)用明確的存取方法。這樣就可以在運行時動態(tài)地訪問和修改對象的屬性。而不是在編譯時確定补鼻, 這也是 iOS 開發(fā)中的黑魔法之一雅任。很多高級的 iOS 開發(fā)技巧都是基于 KVC 實現(xiàn)的
當調(diào)用 setValue:屬性值 forKey:@”name“的代碼時,硼婿,底層的執(zhí)行機制如下:
? 程序優(yōu)先調(diào)用set<Key>:屬性值方法寇漫,代碼通過setter方法完成設(shè)置殉摔。注意,這里的<key>是指成員 變量名栓撞,首字母大小寫要符合 KVC 的命名規(guī)則瓤湘,下同
? 如果沒有找到setName:方法,KVC機制會檢查+(BOOL)accessInstanceVariablesDirectly方法有 沒有返回 YES弛说,默認該方法會返回 YES剃浇,如果你重寫了該方法讓其返回 NO 的話,那么在這一步 KVC 會執(zhí)行 setValue:forUndefinedKey:方法虎囚,不過一般開發(fā)者不會這么做淘讥。所以 KVC 機制會搜索該類 里面有沒有名為<key>的成員變量,無論該變量是在類接口處定義窒朋,還是在類實現(xiàn)處定義蝗岖,也無論用 了什么樣的訪問修飾符抵赢,只在存在以<key>命名的變量,KVC 都可以對該成員變量賦值划提。
? 如果該類即沒有set<key>:方法邢享,也沒有_<key>成員變量,KVC機制會搜索_is<Key>的成員變量伊履。
-
? 和上面一樣款违,如果該類即沒有set<Key>:方法奠货,也沒有_<key>和_is<Key>成員變量,KVC機制再會
繼續(xù)搜索<key>和 is<Key>的成員變量柔滔。再給它們賦值萍虽。
? 如果上面列出的方法或者成員變量都不存在,系統(tǒng)將會執(zhí)行該對象的setValue:forUndefinedKey: 方法超全,默認是拋出異常。
即如果沒有找到 Set<Key>方法的話蛾坯,會按照_key疏遏,_iskey财异,key,iskey 的順序搜索成員并進行賦值操作戳寸。
如果開發(fā)者想讓這個類禁用 KVC疫鹊,那么重寫+(BOOL)accessInstanceVariablesDirectly方法讓其返回No 即可,這樣的話如果 KVC 沒有找到 set<Key>:屬性名時,會直接用 setValue: forUndefinedKey方法蚌吸。
當調(diào)用 valueForKey:@”name“的代碼時羹唠,KVC 對 key 的搜索方式不同于 setValue:屬性值 forKey:@"name",其搜索方式如下:
- ? 首先按get<Key>,<key>,is<Key>的順序方法查找getter方法,找到的話會直接調(diào)用缝彬。如果是BOOL 或者 Int 等值類型谷浅, * 會將其包裝成一個 NSNumber對象奶卓。
- ? * 如果上面的getter沒有找到,KVC則會查找 countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes 格式的方法墩邀。如果 countOf<Key>方法和另外兩個方法中的一個被找到盏浙,那么就會返回一個可以響應(yīng)
NSArray 所有方法的代理集合(它是NSKeyValueArray,是NSArray的子類),調(diào)用這個代理集合的方法竹海,或者說給這個代理集合發(fā)送屬于的方法,就會以或<Key>AtIndexes 這幾個方法組合的形式調(diào)用坊萝。還有一個可選的get<Key>:range方法十偶,所以你想重新定義 KVC 的一些功能园细,你可以添加這些方法,需要注意的是你的方法名要符合 KVC 的標準命名方 法狮崩,包括方法簽名睦柴。
? 如果上面的方法沒有找到毡熏,那么會同時查找countOf<Key>痢法,enumerateOf<key>,memberOf<key> 格式的方法。如果這三個方法都找到蘸炸,那么就返回一個可以響應(yīng) NSSet 所的方法的代理集合尖奔,和上 面一樣提茁,給這個代理集合發(fā) NSSet 的消息建蹄,就會以 countOf<Key>,enumerateOf<key>,memberOf<key> 組合的形式調(diào)用丹弱。
? 如果還沒有找到铲咨,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回 YES(默認行為)纤勒,那么和先前的設(shè)值一樣,會按
_<key>, _iskey恐仑,key,is<Key>的順序搜索成員變量名裳仆,這里不推薦這么做,因為這樣直接訪問實例變量破 壞了封裝性纯丸,使代碼更脆弱静袖。如果重寫了類方法+ (BOOL)accessInstanceVariablesDirectly 返回 NO 的話队橙,那 么會直接調(diào)用 valueForUndefinedKey:方法,默認是拋出異常畅姊。
#import "NSObject+LGKVC.h"
#import <objc/runtime.h>
@implementation NSObject (LGKVC)
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key{
// KVC 自定義
// 1: 判斷什么 key
if (key == nil || key.length == 0) {
return;
}
// 2: setter set<Key>: or _set<Key>,
// key 要大寫
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
if ([self lg_performSelectorWithMethodName:setKey value:value]) {
NSLog(@"*********%@**********",setKey);
return;
}else if ([self lg_performSelectorWithMethodName:_setKey value:value]) {
NSLog(@"*********%@**********",_setKey);
return;
}else if ([self lg_performSelectorWithMethodName:setIsKey value:value]) {
NSLog(@"*********%@**********",setIsKey);
return;
}
// 3: 判斷是否響應(yīng) accessInstanceVariablesDirectly 返回YES NO 奔潰
// 3:判斷是否能夠直接賦值實例變量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4: 間接變量
// 獲取 ivar -> 遍歷 containsObjct -
// 4.1 定義一個收集實例變量的可變數(shù)組
NSMutableArray *mArray = [self getIvarListName];
// _<key> _is<Key> <key> is<Key>
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
// 4.2 獲取相應(yīng)的 ivar
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
// 4.3 對相應(yīng)的 ivar 設(shè)置值
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}
// 5:如果找不到相關(guān)實例
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}
- (nullable id)lg_valueForKey:(NSString *)key{
// 1:刷選key 判斷非空
if (key == nil || key.length == 0) {
return nil;
}
// 2:找到相關(guān)方法 get<Key> <key> countOf<Key> objectIn<Key>AtIndex
// key 要大寫
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}else if ([self respondsToSelector:NSSelectorFromString(key)]){
return [self performSelector:NSSelectorFromString(key)];
}else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
for (int i = 0; i<num-1; i++) {
num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
}
for (int j = 0; j<num; j++) {
id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
[mArray addObject:objc];
}
return mArray;
}
}
#pragma clang diagnostic pop
// 3:判斷是否能夠直接賦值實例變量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4.找相關(guān)實例變量進行賦值
// 4.1 定義一個收集實例變量的可變數(shù)組
NSMutableArray *mArray = [self getIvarListName];
// _<key> _is<Key> <key> is<Key>
// _name -> _isName -> name -> isName
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);;
}
return @"";
}
#pragma mark - 相關(guān)方法
- (BOOL)lg_performSelectorWithMethodName:(NSString *)methodName value:(id)value{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
return YES;
}
return NO;
}
- (id)performSelectorWithMethodName:(NSString *)methodName{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(methodName) ];
#pragma clang diagnostic pop
}
return nil;
}
- (NSMutableArray *)getIvarListName{
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char *ivarNameChar = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
NSLog(@"ivarName == %@",ivarName);
[mArray addObject:ivarName];
}
free(ivars);
return mArray;
}
@end
#import "NSObject+LGKVO.h"
#import <objc/message.h>
static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";
@interface LGInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, copy) LGKVOBlock handleBlock;
@end
@implementation LGInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block{
if (self=[super init]) {
_observer = observer;
_keyPath = keyPath;
_handleBlock = block;
}
return self;
}
@end
@implementation NSObject (LGKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block{
// 1: 驗證是否存在setter方法 : 不讓實例進來
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 動態(tài)生成子類
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa的指向 : LGKVONotifying_LGPerson
object_setClass(self, newClass);
// 4: 保存信息
LGInfo *info = [[LGInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (!mArray) {
mArray = [NSMutableArray arrayWithCapacity:1];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[mArray addObject:info];
}
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (observerArr.count<=0) {
return;
}
for (LGInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
[observerArr removeObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
}
if (observerArr.count<=0) {
// 指回給父類
Class superClass = [self class];
object_setClass(self, superClass);
}
}
#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];
}
}
#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 防止重復(fù)創(chuàng)建生成新類
if (newClass) return newClass;
/**
* 如果內(nèi)存不存在,創(chuàng)建生成
* 參數(shù)一: 父類
* 參數(shù)二: 新類的名字
* 參數(shù)三: 新類的開辟的額外空間
*/
// 2.1 : 申請類
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 : 注冊類
objc_registerClassPair(newClass);
// 2.3.1 : 添加class : class的指向是LGPerson
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
// 2.3.2 : 添加setter
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
return newClass;
}
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"來了:%@",newValue);
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
// 4: 消息轉(zhuǎn)發(fā) : 轉(zhuǎn)發(fā)給父類
// 改變父類的值 --- 可以強制類型轉(zhuǎn)換
void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
lg_msgSendSuper(&superStruct,_cmd,newValue);
// 5: 信息數(shù)據(jù)回調(diào)
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
for (LGInfo *info in mArray) {
if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
info.handleBlock(info.observer, keyPath, oldValue, newValue);
}
}
}
Class lg_class(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
#pragma mark - 從get方法獲取set方法的名稱 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter){
if (getter.length <= 0) { return nil;}
NSString *firstString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
#pragma mark - 從set方法獲取getter方法的名稱 set<Key>:===> key
static NSString *getterForSetter(NSString *setter){
if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
NSRange range = NSMakeRange(3, setter.length-4);
NSString *getter = [setter substringWithRange:range];
NSString *firstString = [[getter substringToIndex:1] lowercaseString];
return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}
@end