目錄:
1.KVC用法;
2.KVC和對象的setter申窘、getter方法的區(qū)別弯蚜;
3.key和keyPath的區(qū)別;
4.KVC進(jìn)行求和剃法,求平均值等操作碎捺;
5.KVO的用法;
6.根據(jù)KVO底層原理自己實(shí)現(xiàn)KVO
一.KVC
1.KVC用法(很簡單贷洲,不詳細(xì)介紹)
KVC也就是key-value-coding(鍵值編碼)收厨,簡而言之就是通過key值去進(jìn)行賦值和取值。主要是是操作對象的屬性优构。以下是幾個常用的方法:
setValue:forKey:(為對象的屬性賦值)
setValue: forKeyPath:(為對象的屬性賦值(包含了setValue:forKey:的功能诵叁,并且還可以對對象內(nèi)的類的屬性進(jìn)行賦值))
valueForKey:(根據(jù)key取值)
valueForKeyPath:(根據(jù)keyPath取值)
setValuesForKeysWithDictionary:(對模型進(jìn)行一次性賦值)
2.KVC和對象的setter、getter方法的區(qū)別
一般情況下钦椭,KVC和setter拧额、getter應(yīng)該說都能達(dá)到對對象屬性的賦值碑诉,并且KVC操作也是去調(diào)用的setter方法和getter方法(針對一些已經(jīng)在.h中聲明的屬性而言)。但是對于一些私有屬性势腮,那么這個時候setter联贩、getter方法就沒有用了,這個時候KVC卻能發(fā)揮重要優(yōu)勢捎拯。
例如:在Person.m中
#import "Person.h"
@implementation Person
{
??????????? NSInteger _height;
}
@end
此時你會發(fā)現(xiàn)setter泪幌、getter已經(jīng)無能為力了,但是KVC去可以實(shí)現(xiàn)賦值署照、取值
[p setValue:@170 forKey:@"height"];
3.key和keyPath的區(qū)別
keyPath方法是集成了key的所有功能祸泪,也就是說對一個對象的一般屬性進(jìn)行賦值、取值建芙,兩個方法是通用的没隘,都可以實(shí)現(xiàn)。但是對對象中的對象進(jìn)的屬性行賦值禁荸,只有keyPath能夠?qū)崿F(xiàn)右蒲。
setValuesForKeysWithDictionary:的巧妙使用(字典轉(zhuǎn)模型)
-(instancetype)initWithDict:(NSDictionary *)dict{
if (self = [super init]) {
??????????? [self setValuesForKeysWithDictionary:dict];
}
???????? return self;
}
4.KVC進(jìn)行求和,求平均值等操作
Person.h
#import?"Father.h"
#import?"Book.h"
@interface?Person?:?NSObject?{
@public
?????????? NSString?*_fullName;
@private
????????? NSString?*_name;
????????? Father?*_father;
????????? NSArray?*_books;
}
@end
Father.h
@interface?Father?:?NSObject?{
@protected
??????? NSString?*_name;
}
@end
Book.h
#import?
@interface?Book?:?NSObject?{
@private
??????????? NSString?*_name;
??????????? float?_price;
}
@end
使用代碼:
#import?
#import?"Person.h"
int?main(int?argc,?const?char?*?argv[])
{
???????????? @autoreleasepool?{
???????????????????? Person?*person?=?[[Person?alloc]?init];
??????????????????? //直接訪問public變量
?????????????????? person->_fullName?=?@"ALI?TOM";
?????????????????? NSLog(@"_fullName?:%@",person->_fullName);
?????????????? ?? //KVC方式
?????????????? ? ? [person?setValue:@"TOM"?forKey:@"_name"];
???????????????? ? NSLog(@"_name?:%@",?[person?valueForKey:@"_name"]);
?????????????????? Father *father = [[Father alloc] init];
?? ? ? ? ? ? ? ? ? [father?setValue:@"JACK"?forKey:@"_name"];
?????????????????? [person?setValue:father?forKey:@"_father"];
????????????????? //KVC路徑訪問
????????????????? NSLog(@"father.name?:%@",?[person?valueForKeyPath:@"_father._name"]);
????????????????? [person?setValue:@"JERRY"?forKeyPath:@"_father._name"];
?????????????????? NSLog(@"father.name?:%@",?[person?valueForKeyPath:@"_father._name"]);
????????????????? NSMutableArray *bookArray = [NSMutableArray arrayWithCapacity:3];
????????????? ? ? for?(int?i=0;?i<3;?i++)?{
???????????????????????? Book?*book?=?[[Book?alloc]?init];
???????????????????????? NSString?*bookName?=?[NSString?stringWithFormat:@"book%d",?i];
???????????????????????? [book?setValue:bookName?forKey:@"_name"];
???????????????????????? [book?setValue:@((i?+?1)??*?10.2)?forKey:@"_price"];
???????????????????????? [bookArray?addObject:book];
????????????????????????? [book?release];
? ? ? ? ? ? ? ? ? }
???????????????? [person setValue:bookArray forKey:@"_books"];
? ? ? ? ? ? ? ? ? ? //KVC計算
?????????????????? //通過@count獲取集合book個數(shù)
?????????????????? NSNumber?*bookCount?=?[person?valueForKeyPath:@"_books.@count"];
?????????????????? NSLog(@"book?count?:%@",?bookCount);
?????????????????? //價格總和
?????????????????? NSNumber?*sum?=?[person?valueForKeyPath:@"_books.@sum._price"];
?????????????????? NSLog(@"sum?:%@",?sum);
?????????????????? //價格平均值
?????????????????? NSNumber?*avg?=?[person?valueForKeyPath:@"_books.@avg._price"];
?????????????????? NSLog(@"vag?:%@",?avg);
????????????????? //最低價格
???????????????? NSNumber?*min?=?[person?valueForKeyPath:@"_books.@min._price"];
???????????????? NSLog(@"min?:%@",?min);
???????????????? //最高價格
???????????????? NSNumber?*max?=?[person?valueForKeyPath:@"_books.@max._price"];
???????????????? NSLog(@"max?:%@",?max);
二.KVO
1.KVO的用法
KVO也就是key-value-observing(即鍵值觀察),利用一個key來找到某個屬性并監(jiān)聽其值得改變赶熟。用法如下:
添加觀察者
在觀察者中實(shí)現(xiàn)監(jiān)聽方法瑰妄,observeValueForKeyPath: ofObject: change: context:(通過查閱文檔可以知道,絕大多數(shù)對象都有這個方法映砖,因?yàn)檫@個方法屬于NSObject)
移除觀察者
//讓對象b監(jiān)聽對象a的name屬性
//options屬性可以選擇是哪個
/* NSKeyValueObservingOptionNew =0x01, 新值
* NSKeyValueObservingOptionOld =0x02, 舊值
*/
[a addObserver:b forKeyPath:@"name"options:kNilOptionscontext:nil];
a.name = @"zzz";
#pragma mark - 實(shí)現(xiàn)KVO回調(diào)方法
/* * 當(dāng)對象的屬性發(fā)生改變會調(diào)用該方法
* @param keyPath 監(jiān)聽的屬性
* @param object 監(jiān)聽的對象
* @param change 新值和舊值
* @param context 額外的數(shù)據(jù)
*/
- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary*)change context:(void *)context{
????????????? NSLog(@"%@的值改變了,",keyPath);
? ? ? ? ? ?? NSLog(@"change:%@", change);
}
//最后不要忘記了间坐,去移除observer
- (void)dealloc{
??????????? [a removeObserver:b forKeyPath:@"name"];
}
KVO底層(這部分涉及到了runtime,關(guān)于isa指針,會在隨后的簡述中介紹)
當(dāng)一個類的屬性被觀察的時候邑退,系統(tǒng)會通過runtime動態(tài)的創(chuàng)建一個該類的派生類竹宋,并且會在這個類中重寫基類被觀察的屬性的setter方法,而且系統(tǒng)將這個類的isa指針指向了派生類地技,從而實(shí)現(xiàn)了給監(jiān)聽的屬性賦值時調(diào)用的是派生類的setter方法蜈七。重寫的setter方法會在調(diào)用原setter方法前后,通知觀察對象值得改變莫矗。
具體實(shí)現(xiàn)圖如下
1.當(dāng)某個類的對象第一次被觀察時飒硅,系統(tǒng)就會在運(yùn)行期動態(tài)地創(chuàng)建該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的 setter 方法趣苏。
2.派生類在被重寫的 setter 方法中實(shí)現(xiàn)真正的通知機(jī)制,就如前面手動實(shí)現(xiàn)鍵值觀察那樣梯轻。這么做是基于設(shè)置屬性會調(diào)用 setter 方法食磕,而通過重寫就獲得了 KVO 需要的通知機(jī)制。當(dāng)然前提是要通過遵循 KVO 的屬性設(shè)置方式來變更屬性值喳挑,如果僅是直接修改屬性對應(yīng)的成員變量彬伦,是無法實(shí)現(xiàn) KVO 的滔悉。
3.同時派生類還重寫了 class 方法以“欺騙”外部調(diào)用者它就是起初的那個類。然后系統(tǒng)將這個對象的 isa 指針指向這個新誕生的派生類单绑,因此這個對象就成為該派生類的對象了回官,因而在該對象上對 setter 的調(diào)用就會調(diào)用重寫的 setter,從而激活鍵值通知機(jī)制搂橙。此外歉提,派生類還重寫了 dealloc 方法來釋放資源。
2.根據(jù)KVO底層原理自己實(shí)現(xiàn)KVO
#import "NSObject+HKKVO.h"
#import@implementation NSObject (QLKVO) //給NSObject增加分類
//自定義的KVO
-(void)QL_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
?????????? //1.動態(tài)生成一個類!!
????????? //1.1獲取類名
????????? NSString * oldClassName = NSStringFromClass([self class]);
????????? NSString * newClassName = [@"QLKVO_" stringByAppendingString:oldClassName];
????????? const char * name = [newClassName UTF8String];
???????? //動態(tài)創(chuàng)建一個子類
????????? Class MyClass = objc_allocateClassPair([self class], name, 0);
????????? //添加方法
???????? class_addMethod(MyClass, @selector(setName:), (IMP)setName, "v@:@");
????????? //注冊類
??????? objc_registerClassPair(MyClass);
? ? ? ? //NSLog(@"%@", [self class]);? 會輸出:Person
?????? ? //修改isa区转,修改完后self變成了子類
???????? object_setClass(self, MyClass);
? ? ?? //NSLog(@"%@", [self class]);? 會輸出:hkKVO_Person
??????? //保存觀察者對象,這里的self指的是子類
????? ? objc_setAssociatedObject(self, @"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC)苔巨;
}
void setName(id self,SEL _cmd,NSString * newName){
???????? NSLog(@"我監(jiān)聽到了!!");
? ? ? ?? id class = [self class];
??????? //拿到觀察者
??????? id objc = objc_getAssociatedObject(self, @"objc");
??????? //改變self的isa指針,指向父類
?????? object_setClass(self, class_getSuperclass(class));
?????? //調(diào)用父類的set方法!!
?????? objc_msgSend(self, @selector(setName:),newName);
???? ?? //? ? NSLog(@"修改完畢!!");
?
?????? //通知觀察者
????? ? objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:),self,@"name",nil,nil);
??????? //改回子類類型(如果不改废离,self就指向了父類侄泽,下次父類的name屬性更改的,就不會調(diào)用到這個函數(shù)里面去)
???????? object_setClass(self, class);
}
#import "NSObject+HKKVO.h"
@interface ViewController ()
/**? */@property(nonatomic,strong)Person * p;
@end@implementation ViewController
- (void)viewDidLoad {??
???????? [super viewDidLoad];? ?
?????? ? Person * p = [[Person alloc]init];
? ? ? ? ?? _p = p;??
?????????? //使用自定義的KVO來監(jiān)聽!Person 的 name 屬性?
? ? ? ? ? [p hk_addObserver:self forKeyPath:@"name" options:0 context:nil];??
?????????? NSLog(@"%@",[p class]);??
}
//監(jiān)聽到了就來了!!
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context{??
????????? NSLog(@"哥么來了!!!%@",_p.name);
}
//點(diǎn)擊就改變!!
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {
??????? static int i = 0;
??????? i++;
?????? _p.name = [NSString stringWithFormat:@"%d",i];
}