鍵值編碼是由
NSKeyValueCoding
非正式協(xié)議啟用的一種機制,對象采用該協(xié)議來提供對其屬性的間接訪問。當對象符合鍵值編碼時砍聊,其屬性可以通過簡潔、統(tǒng)一的消息傳遞接口通過字符串參數(shù)進行尋址贰军。這種間接訪問機制補充了實例變量及其關聯(lián)訪問器方法提供的直接訪問玻蝌。
您通常使用訪問器方法來訪問對象的屬性。get訪問器(或getter)返回屬性的值词疼。集合訪問器(或設置器)設置屬性的值俯树。在Objective-C中,您還可以直接訪問屬性的基礎實例變量贰盗。以上述任何一種方式訪問對象屬性都很簡單许饿,但需要調(diào)用特定于屬性的方法或變量名稱。隨著屬性列表的增長或變化舵盈,訪問這些屬性的代碼也必須增長或變化陋率。相比之下球化,符合鍵值編碼的對象提供了一個簡單的消息傳遞界面,該界面在其所有屬性中都一致翘贮。KVC是一個基本概念赊窥,是許多其他可可技術的基礎,如KVC狸页、可可綁定锨能、核心數(shù)據(jù)和AppleScript可性。在某些情況下芍耘,鍵值編碼也有助于簡化代碼址遇。
實現(xiàn)機制(摘自蘋果文檔,下面有干貨非純文檔)
valueforkey
valueForKey:
的默認實現(xiàn),給定一個key
參數(shù)作為輸入斋竞,執(zhí)行以下過程倔约,從接收valueForKey:
調(diào)用的類實例中操作。
按順序在實例中搜索第一個訪問器方法坝初,名稱如
get<Key>
浸剩、<key>
、is<Key>
或_<key>
鳄袍。如果找到绢要,請調(diào)用它,然后繼續(xù)第5步并附上結果拗小。否則重罪,請繼續(xù)下一步。-
如果沒有找到簡單的訪問器方法哀九,請在實例中搜索名稱與模式
countOf<Key>
和objectIn<Key>AtIndex:
(對應NSArray
類定義的原始方法)和<key>AtIndexes:
(對應NSArray方法
objectsAtIndexes:)匹配的方法剿配。如果找到其中第一個和至少兩個中的一個,請創(chuàng)建一個響應所有
NSArray
方法的集合代理對象并返回該對象阅束。否則呼胚,請繼續(xù)第3步。代理對象隨后將其收到的任何
NSArray
消息轉換為countOf<Key>
息裸、objectIn<Key>AtIndex:
和<key>AtIndexes:
消息的某種組合砸讳,轉換為創(chuàng)建它的鍵值編碼兼容對象。如果原始對象還實現(xiàn)了名為get<Key>:range:
的可選方法界牡,則代理對象也會在適當時使用該方法簿寂。實際上,代理對象與鍵值編碼兼容對象一起工作宿亡,允許基礎屬性的行為就像NSArray
一樣常遂,即使它不是。 -
If no simple accessor method or group of array access methods is found, look for a triple of methods named
countOf<Key>
,enumeratorOf<Key>
, andmemberOf<Key>:
(corresponding to the primitive methods defined by the NSSet class).如果找到所有三種方法挽荠,請創(chuàng)建一個響應所有
NSSet
方法的集合代理對象并返回該對象克胳。否則平绩,請繼續(xù)第4步。此代理對象隨后將其接收的任何
NSSet
消息轉換為countOf<Key>
漠另、enumeratorOf<Key>
和memberOf<Key>:
消息的某種組合到創(chuàng)建它的對象捏雌。實際上,代理對象與鍵值編碼兼容對象一起工作笆搓,允許基礎屬性的行為就像NSSet
一樣性湿,即使它不是。 如果沒有找到簡單的訪問器方法或集合訪問方法組满败,并且接收器的類方法accessInstanceVariablesDirectly返回
YES
肤频,請按此順序搜索名為_<key>
、_is<Key>
算墨、<key>
或is<Key>
的實例變量宵荒。如果找到,請直接獲取實例變量的值净嘀,然后轉到第5步报咳。否則,請繼續(xù)第6步挖藏。如果檢索到的屬性值是對象指針暑刃,只需返回結果。 如果該值是
NSNumber
支持的標量類型熬苍,請將其存儲在NSNumber
實例中并返回稍走。
如果結果是NSNumber不支持的標量類型袁翁,請轉換為NSValue
對象并返回該對象柴底。如果所有其他方法都失敗了,請調(diào)用valueForUndefinedKey:粱胜。默認情況下柄驻,這會引發(fā)異常,但
NSObject
的子類可能會提供特定于密鑰的行為焙压。
setValueForKey
按順序查找第一個名為
set<Key>:
或_set<Key>
的訪問器鸿脓。如果找到,請使用輸入值(或根據(jù)需要打開的值)調(diào)用它涯曲,然后完成野哭。如果沒有找到簡單的訪問器,并且類方法
accessInstanceVariablesDirectly
返回YES
幻件,請按順序查找名稱為_<key>
拨黔、_is<Key>
、<key>
或is<Key>
的實例變量绰沥。如果找到篱蝇,請直接使用輸入值(或未包裝的值)設置變量并完成贺待。找不到訪問器或實例變量時,調(diào)用
setValue:forUndefinedKey:
零截。默認情況下麸塞,這會引發(fā)異常,但NSObject
的子類可能會提供特定于密鑰的行為涧衙。
Array/Set
mutableOrderedSetValueForKey/mutableSetValueForKey:的默認實現(xiàn):識別與valueForKey相同的簡單訪問器方法和有序集訪問器方法:哪工,并遵循相同的直接實例變量訪問策略,但總是返回可變集合代理對象绍撞,而不是valueForKey:返回的不可變集合正勒。此外,它還執(zhí)行以下操作:
- 搜索名稱如下的方法
insertObject:in<Key>AtIndex:
andremoveObjectFrom<Key>AtIndex:
(對應于 NSMutableOrderedSetclass), 而且
insert<Key>:atIndexes:和
remove<Key>AtIndexes:`(對應 insertObjects:atIndexes: 和 removeObjectsAtIndexes:).
如果找到至少一種插入方法和至少一種刪除方法傻铣,則返回的代理對象將發(fā)送以下方法的組合: insertObject:in<Key>AtIndex:
, removeObjectFrom<Key>AtIndex:
, insert<Key>:atIndexes:
, and remove<Key>AtIndexes:
發(fā)送給原始收件人的消息mutableOrderedSetValueForKey:
收到“NSMutableOrderedSet”消息時顯示消息章贞。
代理對象還使用名稱為replaceObjectIn<Key>AtIndex:withObject:
或replace<Key>AtIndexes:with<Key>:
等名稱的方法,當它們存在于原始對象中時非洲。
如果沒有找到可變集方法鸭限,搜索名稱為set<Key>:的訪問器方法。在這種情況下两踏,返回的代理對象每次收到NSMutableOrderedSet消息時都會向原始接收器發(fā)送set<Key>:消息败京。
如果既沒有找到可變集消息也沒有找到訪問器,并且接收器的accessInstanceVariablesDirectly類方法返回
YES
梦染,請按照此順序搜索名稱為_<key>
或<key>
的實例變量赡麦。如果找到此類實例變量,返回的代理對象會將其收到的任何NSMutableOrderedSet
消息轉發(fā)到實例變量的值帕识,該值通常是NSMutableOrderedSet
或其子類之一的實例泛粹。如果所有其他操作都失敗,則返回的代理對象將發(fā)送
setValue:forUndefinedKey:
發(fā)送給原始收件人的消息mutableOrderedSetValueForKey:
每當它收到可變集合消息時肮疗。
setValue:forUndefinedKey:
的默認實現(xiàn)會引發(fā)NSUndefinedKeyException晶姊,但對象可能會覆蓋此行為。
自定義實現(xiàn)KVC
因為蘋果內(nèi)部閉源所以根據(jù)原理猜測他的實現(xiàn)方式
- (BOOL)performSelectorWithMethodName:(NSString *)aSelector value:(id)anArgument {
SEL func = NSSelectorFromString(aSelector);
if ([self respondsToSelector:func]) {
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
[self performSelector:func withObject:anArgument];
_Pragma("clang diagnostic pop")
return YES;
}
return NO;
}
-(void)jl_setValue:(nullable id)value forKey:(NSString *)key {
//key非空判斷
if (!key || key.length == 0) {
return;
}
//找到相關方法 set<Kety> _set<Key> setIs<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 performSelectorWithMethodName:setKey value:value]) {
return;
}
if ([self performSelectorWithMethodName:_setKey value:value]) {
return;
}
if ([self performSelectorWithMethodName:setIsKey value:value]) {
return;
}
//判斷當前是否能否直接復制示例變量
if ([self.class accessInstanceVariablesDirectly]) {
@throw [NSException exceptionWithName:@"JLUnknownKeyException" reason:[NSString stringWithFormat:@"***[%@ valueForUndefineKey:]: this class is not key value codeing-comliant for the key name.****",self] userInfo:nil];
return;
}
//4. 找相關示例變量進行賦值
//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];
NSArray * keyarr = @[_Key,_isKey,key,isKey];
for (NSString * ikey in keyarr) {
if ([mArray containsObject:ikey]) {
Ivar ivar = class_getInstanceVariable([self class], ikey.UTF8String);
object_setIvar(self, ivar, value);
return;
}
}
@throw [NSException exceptionWithName:@"JLUnknownKeyException" reason:[NSString stringWithFormat:@"***[%@ valueForUndefineKey:]: this class is not key value codeing-comliant for the key name.****",self] userInfo:nil];
}
-(nullable id)jl_valueforKey:(NSString *)key {
// 1:刷選key 判斷非空
if (key == nil || key.length == 0) {
return nil;
}
// 2:找到相關方法 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
return nil;
}
上面代碼是根據(jù)kvc文檔簡單的猜測了他內(nèi)部實現(xiàn)了原理伪货。但是他還有個進階用法们衙,其實上面的原理上面也寫到了
請參考下列代碼
class Person: NSObject {
var penArr:[String] = []
}
Person *p = [Person new];
p.penArr = [NSMutableArray arrayWithObjects:@"pen0", @"pen1", @"pen2", @"pen3", nil];
NSArray *arr = [p valueForKey:@"pens"]; // 動態(tài)成員變量
NSLog(@"pens = %@", arr);
//NSLog(@"%@",arr[0]);
NSLog(@"%d",[arr containsObject:@"pen9"]);
正常情況下因為沒有pens這個屬性調(diào)用肯定會崩潰,但是如果進行一步騷操作就不會崩了這個操作就是實現(xiàn)對應的
// 個數(shù)
- (NSUInteger)countOfPens {
return [self.penArr count];
}
//// 獲取值
- (id) objectInPensAtIndex:(NSUInteger)index {
return [NSString stringWithFormat:@"pens %lu", index];
}
// 是否包含這個成員對象
- (id)memberOfPens:(id)object {
return [self.penArr containsObject:object] ? object : nil;
}
// 迭代器
- (id)enumeratorOfPens {
// objectEnumerator
return [self.penArr reverseObjectEnumerator];
}
將該key映射到原來的消息體中碱呼。這樣就實現(xiàn)了kvc的騷操作蒙挑。
不得不說看文檔還是牛逼啊,以后要多看文檔可以發(fā)現(xiàn)許多牛逼的東西
KVO
當我們對A類添加監(jiān)聽的時候愚臀,系統(tǒng)會自動生成一個NSKVONotifying_A的子類忆蚀,這個類重寫了A的class、superclass、deealloc方法和該屬性的Set方法蜓谋,同時A類的對象的isa指針指向了該虛擬子類梦皮。當監(jiān)聽屬性改變的時候系統(tǒng)調(diào)用NSSetobjectValueandNotify,這個方法的執(zhí)行流程是(willchangeValueforkey->改變父類的值->didchangeValueforkey->observeValueForKey:ofObject:change:context:),如果設置*automaticallyNotifiesObserversForKey:(NSString )key為NO的時候則需要手動觸發(fā)KVO即手動調(diào)用willchangeValueforkey和didchangeValueforkey.
func _NSSetObjectValueAndNotify {
...
willchangeValueforkey
...
"
objc_msgSendSupper '改變父類的值(猜測這樣實現(xiàn))
"
...
didchangeValueforkey
...
observeValueForKey:ofObject:change:context:
}
Q1: 為什么重寫系統(tǒng)的class/superclass桃焕、deealloc方法
因為該子類為系統(tǒng)自動生成蘋果想偽裝成并沒有這個類 所以重寫class/superclass 剑肯,但是調(diào)用 objc_getClass()這個方法時候依然會暴露,因為這個方法是調(diào)用調(diào)用對象的isa指針指向观堂。dealloc則是系統(tǒng)還有一些其他的事情處理
自定義KVO
自定義KVO需要遵守KVO的幾個基本原理让网,下面方法只是簡單實現(xiàn)了一個kvo,存在context判斷以及多個observer都存在的時候處理师痕。這個都需要加判斷
1.重寫他的set方法所以當調(diào)用
addObserver
的時候需要判斷當前類有沒有實現(xiàn)set<KeyPath>
方法
2.生成NSKVONotifying_XX的派生類
3. 重寫父類的class方法溃睹,setKeyPath,dealloc方法
#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];
}
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString * oldName = NSStringFromClass([self class]);
//動態(tài)生成子類
NSString * newClassName = [NSString stringWithFormat:@"%@%@",kJLKVOPrefix,oldName];
Class newClass = NSClassFromString(newClassName);
if (newClass) {///判斷類是否已經(jīng)被注冊
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的指向是父類
SEL classSel = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSel);
const char * classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSel, (IMP)jl_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)jl_setter, setterTypes);
return newClass;
}
- (void)jl_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
//1.驗證是否存在示例方法不讓示例進來
[self judgeSetterMethodFromKeyPath:keyPath];
// 2.動態(tài)生成子類
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3. isa的指向 : KVONotifying_Person
object_setClass(self, newClass);
// 4: 保存觀察者
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kJLKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)jl_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
// 指回給父類
Class superClass = [self class];
object_setClass(self, superClass);
}
static void jl_setter(id self,SEL _cmd,id newValue){
NSLog(@"來了:%@",newValue);
// 4: 消息轉發(fā) : 轉發(fā)給父類
// 改變父類的值 --- 可以強制類型轉換
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);
// 既然觀察到了,下一步不就是回調(diào) -- 讓我們的觀察者調(diào)用
// - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
// 1: 拿到觀察者
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kJLKVOAssiociateKey));
// 2: 消息發(fā)送給觀察者
SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
// objc_msgSend(observer,observerSEL);
objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
// [self observeValueForKeyPath:keyPath ofObject:nil change:@{keyPath:newValue} context:nil];
// objc_msgSend(self);
}