日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

聊聊 KVC 和 KVO 的高阶应用

發(fā)布時(shí)間:2024/1/17 c/c++ 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 聊聊 KVC 和 KVO 的高阶应用 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

KVC, KVO 作為一種魔法貫穿日常Cocoa開發(fā),筆者原先是準(zhǔn)備寫一篇對其的全面總結(jié),可網(wǎng)絡(luò)上對其的表面介紹已經(jīng)夠多了,除去基本層面的使用,筆者跟大家談下平常在網(wǎng)絡(luò)上沒有提及的KVC, KVO進(jìn)階知識(shí)。旨在分享交流。

?

KVC的消息傳遞

?

valueForKey:的使用并不僅僅用來取值那么簡單,還有很多特殊的用法,集合類也覆蓋了這個(gè)方法,通過調(diào)用valueForKey:給容器中每一個(gè)對象發(fā)送操作消息,并且結(jié)果會(huì)被保存在一個(gè)新的容器中返回,這樣我們能很方便地利用一個(gè)容器對象創(chuàng)建另一個(gè)容器對象。另外,valueForKeyPath:還能實(shí)現(xiàn)多個(gè)消息的傳遞。一個(gè)例子:

?

NSArray *array = [NSArray arrayWithObject:@"10.11",??

??????????????????????????????????????????@"20.22", nil];

NSArray *resultArray = [array valueForKeyPath:@"doubleValue.intValue"];??

NSLog(@"%@", resultArray);

?

//打印結(jié)果

(

10,??

20??

)

?

KVC容器操作

?

容器不僅僅能使用KVC方法實(shí)現(xiàn)對容器成員傳遞普通的操作消息,KVC還定義了特殊的一些常用操作,使用valueForKeyPath:結(jié)合操作符來使用,所定義的keyPath格式入下圖所示

?

?

Left key path:如果有,則代表需要操作的對象路徑(相對于調(diào)用者)

Collection operator:以”@”開頭的操作符

Right key path:指定被操作的屬性

?

常規(guī)操作符:

?

  • @avg、@count、@max、@min、@sum

?

對象操作符:

?

  • @distinctUnionOfObjects、@unionOfObjects

?

NSArray *values = [object valueForKeyPath:@"@unionOfObjects.value"];?

?

@distinctUnionOfObjects操作符返回被操作對象指定屬性的集合并做去重操作,而@unionOfObjects則允許重復(fù)。如果其中任何涉及的對象為nil,則拋出異常。

?

Array和Set操作符:

?

Array和Set操作符操作對象是嵌套型的集合對象

?

  • @distinctUnionOfArrays、@unionOfArrays

    ?

NSArray *values = [arrayOfobjectsArrays valueForKeyPath:@"@distinctUnionOfArrays.value"];

?

同樣的,返回被操作集合下的集合中的對象的指定屬性的集合,并且做去重操作,而@unionOfObjects則允許重復(fù)。如果其中任何涉及的對象為nil,則拋出異常。

?

  • @distinctUnionOfSets

?

NSSet *values = [setOfobjectsSets valueForKeyPath:@"@distinctUnionOfSets.value"];

?

返回結(jié)果同理于NSArray。

?

據(jù)官方文檔說明,目前還不支持自動(dòng)以操作符。

?

KVC與容器類(集合代理對象)

?

當(dāng)然對象的屬性可以是一對一的,也可以是一對多。屬性的一對多關(guān)系其實(shí)就是一種對容器類的映射。如果有一個(gè)名為numbers的數(shù)組屬性,我們可以使用valueForKey:@"numbers"來獲取,這個(gè)是沒問題的,但KVC還能使用更靈活的方式管理集合。——集合代理對象

?

ElfinsArray.h??

@interface ElfinsArray : NSObject

@property (assign ,nonatomic) NSUInteger count;

- (NSUInteger)countOfElfins;

- (id)objectInElfinsAtIndex:(NSUInteger)index;

@end

?

ElfinsArray.m??

#import "ElfinsArray.h"

@implementation ElfinsArray

- (NSUInteger)countOfElfins {

????return??self.count;

}

- (id)objectInElfinsAtIndex:(NSUInteger)index {

????return [NSString stringWithFormat:@"小精靈%lu", (unsigned long)index];

}

@end

?

Main.m??

- (void)work {

????ElfinsArray *elfinsArr = [ElfinsArray alloc] init];

????elfinsArr.count = 3;

????NSArray *elfins = [ElfinsArray valueForKey:@"elfins"];

????//elfins為KVC代理數(shù)組

????NSLog(@"%@", elfins);

?

????//打印結(jié)果

????(

????????"小精靈0",

????????"小精靈1",

????????"小精靈2"

????)

}

?

問題來了,ElfinsArray中并沒有定義elfins屬性,那么elfins數(shù)組從何而來?valueForKey:有如下的搜索規(guī)則:

?

  • 按順序搜索getVal、val、isVal,第一個(gè)被找到的會(huì)用作返回。

  • countOfVal,或者objectInValAtIndex:與valAtIndexes其中之一,這個(gè)組合會(huì)使KVC返回一個(gè)代理數(shù)組。

  • countOfVal、enumeratorOfVal、memberOfVal。這個(gè)組合會(huì)使KVC返回一個(gè)代理集合。

  • 名為val、isVal、val、isVal的實(shí)例變量。到這一步時(shí),KVC會(huì)直接訪問實(shí)例變量,而這種訪問操作破壞了封裝性,我們應(yīng)該盡量避免,這可以通過重寫+accessInstanceVariablesDirectly返回NO來避免這種行為。

?

ok上例中我們實(shí)現(xiàn)了第二條中的特殊命名函數(shù)組合:

?

- (NSUInteger)countOfElfins;

- (id)objectInElfinsAtIndex:(NSUInteger)index;

?

這使得我們調(diào)用valueForKey:@"elfins"時(shí),KVC會(huì)為我們返回一個(gè)可以響應(yīng)NSArray所有方法的代理數(shù)組對象(NSKeyValueArray),這是NSArray的子類,- (NSUInteger)countOfElfins決定了這個(gè)代理數(shù)組的容量,- (id)objectInElfinsAtIndex:(NSUInteger)index決定了代理數(shù)組的內(nèi)容。本例中使用的key是elfins,同理的如果key叫human,KVC就會(huì)去尋找-countOfHuman:

?

可變?nèi)萜髂?/p>

?

當(dāng)然我們也可以在可變集合(NSMutableArray、NSMutableSet、NSMutableOrderedSet)中使用集合代理:

這個(gè)例子我們不再使用KVC給我們生成代理數(shù)組,因?yàn)槲覀兪峭ㄟ^KVC拿到的,而不能主動(dòng)去操作它(insert/remove),我們聲明一個(gè)可變數(shù)組屬性elfins。

?

ElfinsArray.h??

@interface ElfinsArray : NSObject

@property (strong ,nonatomic) NSMutableArray *elfins;

- (void)insertObject:(id)object inNumbersAtIndex:(NSUInteger)index;

- (void)removeObjectFromNumbersAtIndex:(NSUInteger)index;

@end

?

ElfinsArray.m??

#import "ElfinsArray.h"

@implementation ElfinsArray

- (void)insertObject:(id)object inElfinsAtIndex:(NSUInteger)index {

????[self.elfins insertObject:object atIndex:index];

????NSLog(@"insert %@n", object);

}

- (void)removeObjectFromElfinsAtIndex:(NSUInteger)index {

????[self.elfins removeObjectAtIndex:index];

????NSLog(@"removen");

}

@end

?

Main.m??

- (void)work {

????ElfinsArray *elfinsArr = [ElfinsArray alloc] init];

????elfinsArr.elfins = [NSMutableArray array];

????NSMutableArray *delegateElfins = [ElfinsArray mutableArrayValueForKey:@"elfins"];

????//delegateElfins為KVC代理可變數(shù)組,非指向elfinsArr.elfins

????[delegateElfins insertObject:@"小精靈10" atIndex:0];

????NSLog(@"first log n %@", elfinsArr.elfins);

????[delegateElfins removeObjectAtIndex:0];

????NSLog(@"second log n %@", elfinsArr.elfins);

?

?

????//打印結(jié)果

????insert 小精靈10

????first log

????(

????????"小精靈10"

????)

????remove

????second log

????(

????)

}

?

上例中,我們通過調(diào)用

?

- mutableArrayValueForKey:

- mutableSetValueForKey:

- mutableOrderedSetValueForKey:

?

KVC會(huì)給我們返回一個(gè)代理可變?nèi)萜鱠elegateElfins,通過對代理可變?nèi)萜鞯牟僮?#xff0c;KVC會(huì)自動(dòng)調(diào)用合適KVC方法(如下):

?

//至少實(shí)現(xiàn)一個(gè)insert方法和一個(gè)remove方法

- insertObject:inValAtIndex:

- removeObjectFromValAtIndex:

- insertVal:atIndexes:

- removeValAtIndexes:

?

間接地對被代理對象操作。

還有一組更強(qiáng)大的方法供參考

?

- replaceObjectInValAtIndex:withObject:

- replaceValAtIndexes:withVal:

?

我認(rèn)為這就是KVC結(jié)合KVO的結(jié)果。這里我嘗試研究下了文檔中的如下兩個(gè)方法,還沒有什么頭緒,知道的朋友可否告訴我下

?

- willChange:valuesAtIndexes:forKey:

- didChange:valuesAtIndexes:forKey:

?

KVO和容器類

?

要注意,對容器類的觀察與對非容器類的觀察并不一樣,不可變?nèi)萜鞯膬?nèi)容發(fā)生改變并不會(huì)影響他們所在的容器,可變?nèi)萜鞯膬?nèi)容改變&內(nèi)容增刪也都不會(huì)影響所在的容器,那么如果我們需要觀察某容器中的對象,首先我們得觀察容器內(nèi)容的變化,在容器內(nèi)容增加時(shí)添加對新內(nèi)容的觀察,在內(nèi)容移除同時(shí)移除對該內(nèi)容的觀察。

?

既然容器內(nèi)容數(shù)量改變和內(nèi)容自身改變都不會(huì)觸發(fā)容器改變,此時(shí)對容器屬性施加KVO并沒有效果,那么怎么實(shí)現(xiàn)對容器變化(非容器改變)的觀察呢?上面所介紹的代理容器能幫到我們:

?

//我們通過KVC拿到容器屬性的代理對象

NSMutableArray *delegateElfins = [ElfinsArray mutableArrayValueForKey:@"elfins"];??

[delegateElfins addObject:@"小精靈10"];

?

當(dāng)然這樣做的前提是要實(shí)現(xiàn)insertObject:inValAtIndex:和removeObjectFromValAtIndex:兩個(gè)方法。如此才能觸發(fā)observeValueForKeyPath:ofObject:change:context:的響應(yīng)。

?

而后,我們就可以輕而易舉地在那兩個(gè)方法實(shí)現(xiàn)內(nèi)對容器新成員添加觀察/對容器廢棄成員移除觀察。

?

KVO的實(shí)現(xiàn)原理

?

寫到這里有點(diǎn)犯困,估計(jì)廣州的春天真的來了。對于KVO的實(shí)現(xiàn)原理就不花筆墨再描述了,網(wǎng)絡(luò)上哪里都能找到,這里借網(wǎng)上一張圖來偷懶帶過。

?

?

在我們了解明白實(shí)現(xiàn)原理的前提下,我們可以自己來嘗試模仿,那么我們從哪里下手呢?先來準(zhǔn)備一個(gè)新子類的setter方法:

?

- (void)notifySetter:(id)newValue {

????NSLog(@"我是新的setter");

}

?

setter的實(shí)現(xiàn)先留空,下面再詳細(xì)說,緊接著,我們直接進(jìn)入主題,runtime注冊一個(gè)新類,并且讓被監(jiān)聽類的isa指針指向我們自己偽造的類,為了大家看得方便,筆者就不做封裝了,所有直接寫在一個(gè)方法內(nèi):

?

- (Class)configureKVOSubClassWithSourceClassName:(NSString *)className observeProperty:(NSString *)property {

????NSString *prefix = @"NSKVONotifying_";

????NSString *subClassName = [prefix stringByAppendingString:className];

?

????//1

????Class originClass = [KVOTargetClass class];

????Class dynaClass = objc_allocateClassPair(originClass, subClassName.UTF8String, 0);

?

????//重寫property對應(yīng)setter

????NSString *propertySetterString = [@"set" stringByAppendingString:[[property substringToIndex:1] uppercaseString]];

????propertySetterString = [propertySetterString stringByAppendingString:[property substringFromIndex:1]];

????propertySetterString = [propertySetterString stringByAppendingString:@":"];

????SEL setterSEL = NSSelectorFromString(propertySetterString);

?

????//2

????Method setterMethod = class_getInstanceMethod(originClass, setterSEL);

????const char types = method_getTypeEncoding(setterMethod);

????class_addMethod(dynaClass, setterSEL, class_getMethodImplementation([self class], @selector(notifySetter:)), types);

?

????objc_registerClassPair(dynaClass);

????return dynaClass;

}

?

我們來看

?

//1處,我們要?jiǎng)?chuàng)建一個(gè)新的類,可以通過objc_allocateClassPair來創(chuàng)建這個(gè)新類和他的元類,第一個(gè)參數(shù)需提供superClass的類對象,第二個(gè)參數(shù)接受新類的類名,類型為const char *,通過返回值我們得到dynaClass類對象。

?

//2處,我們希望為我們的偽造的類添加跟被觀察類一樣只能的setter方法,我們可以借助被觀察類,拿到類型編碼信息,通過class_addMethod,注入我們自己的setter方法實(shí)現(xiàn):class_getMethodImplementation([self class], @selector(notifySetter:)),最后通過objc_registerClassPair完成新類的注冊!。

?

可能有朋友會(huì)問class_getMethodImplementation中獲取IMP的來源[self class]的self是指代什么?其實(shí)就是指代我們自己的setter(notifySetter:)IMP實(shí)現(xiàn)所在的類,指代從哪個(gè)類可以找到這個(gè)IMP,筆者這里是直接開一個(gè)新工程,在ViewController里就開干的,notifySetter:和這個(gè)手術(shù)方法configureKVOSubClassWithSourceClassName: observeProperty:所在的地方就是VC,因此self指向的就是這個(gè)VC實(shí)例,也就是這個(gè)手術(shù)方法的調(diào)用者。

?

不用懷疑,經(jīng)過手術(shù)后對KVOTargetClass對應(yīng)屬性的修改,就會(huì)進(jìn)入到我們偽裝的setter,下面我們來完成先前留空的setter實(shí)現(xiàn):

?

- (void)notifySetter:(id)newValue {

????NSLog(@"我是新的setter");

?

????struct objc_super originClass = {

????????.receiver = self,

????????.super_class = class_getSuperclass(object_getClass(self))

????};

?

????NSString *setterName = NSStringFromSelector(_cmd);

????NSString *propertyName = [setterName substringFromIndex:3];

????propertyName = [[propertyName substringToIndex:propertyName.length - 1] lowercaseString];

?

????[self willChangeValueForKey:propertyName];

????//調(diào)用super的setter

????//1

????void (*objc_msgSendSuperKVO)(void * class, SEL _cmd, id value) = (void *)objc_msgSendSuper;

????//2

????objc_msgSendSuperKVO(&originClass, _cmd, newValue);

????[self didChangeValueForKey:propertyName];

}

?

我們輕而易舉地讓willChangeValueForKey:和didChangeValueForKey:包裹了對newValue的修改。

?

這里需要提的是:

?

//1處,在IOS8后,我們不能直接使用objc_msgSend()或者objc_msgSendSuper()來發(fā)送消息,我們必須自定義一個(gè)msg_Send函數(shù)并提供具體類型來使用。

?

//2處,至于objc_msgSendSuper(struct objc_super *, SEL, ...),第一個(gè)參數(shù)我們需要提供一個(gè)objc_super結(jié)構(gòu)體,我們command跳進(jìn)去來看看這個(gè)結(jié)構(gòu)體:

?

/// Specifies the superclass of an instance.

struct objc_super {??

????/// Specifies an instance of a class.

????__unsafe_unretained id receiver;

?

????/// Specifies the particular superclass of the instance to message.

#if !defined(__cplusplus)??&??!__OBJC2__

????/* For compatibility with old objc-runtime.h header */

????__unsafe_unretained Class class;

#else

????__unsafe_unretained Class super_class;

#endif

????/* super_class is the first class to search */

};

#endif

?

第一個(gè)成員receiver表示某個(gè)類的實(shí)例,第二個(gè)成員super_class代表當(dāng)前類的父類,也就是這里接受消息目標(biāo)的類。

?

工作已經(jīng)完成了,可以隨便玩了:

?

- (void)main {

????KVOTargetClass *kvoObject = [[KVOTargetClass alloc] init];

????NSString *targetClassName = NSStringFromClass([KVOTargetClass class]);

????Class subClass = [self configureKVOSubClassWithSourceClassName:targetClassName observeProperty:@"name"];

????object_setClass(kvoObject, subClass);

?

????[kvoObject setName:@"haha"];

????NSLog(@"property -- %@", kvoObject.name);

}

?

總結(jié)

以上是生活随笔為你收集整理的聊聊 KVC 和 KVO 的高阶应用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。