2018.4.26提問:請描述一下你使用KVC,KVO的具體案例,以及你認為使用的時候需要注意的地方?
KVC:
具體案例:屬性指派,添加私有成員變量,字典轉模型
注意的地方:
-
KVC,使用setValuesForKeysWithDictionary:方法,該方法預設根據字典中每個鍵值對,調用setValue:forKey方法
缺點:
-1. 字典中的鍵值對必須與模型中的鍵值對完全對應,否則程式會崩潰
- 2.字段比較多的話,手寫字典轉模型就很累
解決方案:
- 1.使用一個第三方控件MJExtension;可以做到字典轉模型,模型裡面還可以套結模型,也可以套接模型數組,功能比較完善和強大。
- 2.處理
- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
- 封裝一個BaseModel 實作下面兩個方法,建立如下轉模型方法
// 将所有的資料轉換為字元串
-(void)setValue:(id)value forKey:(NSString *)key{
if([value isKindOfClass:[NSNull class]]){
value=nil;
}else if([value isKindOfClass:[NSArray class]]){
}else{
value = [NSString stringWithFormat:@"%@",value];
}
[super setValue:value forKey:key];
}
// 對特殊字元 id 進行處理
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"Undefined Key: %@", key);
}
// 字典轉模型
-(id)initWithDic:(NSDictionary *)modelDic{
self = [super init];
if(self){
[self setValuesForKeysWithDictionary:modelDic];
}
return self;
}
使用:
底層實作:
當一個對象調用setValue:forKey: 方法時,方法内部會做以下操作:
- 1.判斷有沒有指定key的set方法,如果有set方法,就會調用set方法,給該屬性指派
- 2.如果沒有set方法,判斷有沒有跟key值相同且帶有下劃線的成員屬性(_key).如果有,直接給該成員屬性進行指派
- 3.如果沒有成員屬性_key,判斷有沒有跟key相同名稱的屬性.如果有,直接給該屬性進行指派
- 4.如果都沒有,就會調用 valueforUndefinedKey 和setValue:forUndefinedKey:方法
KVO:
具體案例:
- KVO有顯著的使用場景,當你希望監視一個屬性的時候,當處理屬性層的消息的事件時候,使用KVO,其他的盡量使用delegate。
優點:
- 1.能夠提供一種簡單的方法實作兩個對象間的同步。例如:model和view之間同步;
- 2.能夠對非我們建立的對象,即内部對象的狀态改變作出響應,而且不需要改變内部對象(SDK對象)的實作;
- 3.能夠提供觀察的屬性的最新值以及先前值;
- 4.用key paths來觀察屬性,是以也可以觀察嵌套對象;
- 5.完成了對觀察對象的抽象,因為不需要額外的代碼來允許觀察值能夠被觀察
缺點:
- 1.我們觀察的屬性必須使用strings來定義。是以在編譯器不會出現警告以及檢查;
- 2.對屬性重構将導緻我們的觀察代碼不再可用;
- 3.複雜的“IF”語句要求對象正在觀察多個值。這是因為所有的觀察代碼通過一個方法來指向;
- 4.當釋放觀察者時不需要移除觀察者。
注意事項:
-
潛在的問題有可能出現在dealloc中對KVO的登出上。KVO的一種缺陷(其實不能稱為缺陷,應該稱為特性)是,當對同一個keypath進行兩次removeObserver時會導緻程式crash,這種情況常常出現在父類有一個kvo,父類在dealloc中remove了一次,子類又remove了一次的情況下。不要以為這種情況很少出現!當你封裝framework開源給别人用或者多人協作開發時是有可能出現的,而且這種crash很難發現。不知道你發現沒,目前的代碼中context字段都是nil,那能否利用該字段來辨別出到底kvo是superClass注冊的,還是self注冊的?
回答是可以的。我們可以分别在父類以及本類中定義各自的context字元串,比如在本類中定義context為@”ThisIsMyKVOContextNotSuper”;然後在dealloc中remove
observer時指定移除的自身添加的observer。這樣iOS就能知道移除的是自己的kvo,而不是父類中的kvo,避免二次remove造成crash
KVC:鍵值編碼
主要作用是:
(1)通過鍵值路徑為對象的屬性指派。主要是可以為私有的屬性指派。
AppleViewController *appleVC = [[AppleViewController alloc]init];
[appleVC setValue:@"橘子" forKey:@"name"];
如果對象A的屬性是一個對象B,要設定對象B的屬性
[person setValue:@"旺财" forKeyPath:@"dog.name"];
(2)通過鍵值路徑擷取屬性的值。主要是可以通過key獲得私有屬性的值。
NSString *nameStr = [appleVC valueForKey:@"name"];
也可以通過keypath獲得值
NSString *dName = [person valueForKeyPath:@"dog.name"];
(3)将字典轉型成Model,方法:setValuesForKeysWithDictionary:
// 定義一個字典
NSDictionary *dict = @{
@"name" : @"jack",
@"money" : @"20.7",
};
// 建立模型
Person *p = [[Person alloc] init];
// 字典轉模型
[p setValuesForKeysWithDictionary:dict];
NSLog(@"person's name is the %@",p.name);
注意:字典的key和Model的屬性一定要一一對應。否則會出現錯誤。比如person裡沒有name的屬性,系統報錯如下:
‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.’
二. KVO
1.KVO:鍵值觀察(key-value-observing)
KVO提供了一種觀察者的機制,通過對某個對象的某個屬性添加觀察者,當該屬性改變,就會調用”observeValueForKeyPath:”方法,為我們提供一個“對象值改變了!”的時機進行一些操作。
2.KVO原理
當某個類的對象第一次被觀察時,系統在運作時會建立該類的派生類,改派生類中重寫了該對象的setter方法,并且在setter方法中實作了通知的機制。派生類重寫了class方法,以“欺騙”外部調用者他就是原先那個類。系統将這個類的isa指針指向新的派生類,是以改對象也就是新的派生類的對象了。因而改對象調用setter就會調用重寫的setter,進而激活鍵值通知機制。此外派生類還重寫了delloc方法來釋放資源。
3.KVO的使用
(1)給對象的屬性添加觀察者
[appleVC addObserver:self forKeyPath:@"name" options: NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
注: options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld 傳回未改變之前的值和改變之後的值 context可以為空
(2)若該屬性發生改變,系統自動調用下面的方法:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
//拿到新值/舊值,進行操作
NSLog(@"newValue----%@",change[@"new"]);
NSLog(@"oldValue----%@",change[@"old"]);
}
(3)取消監聽
-(void)dealloc
{
[person removeObserver:self forKeyPath:@"test"];
}
4.KVO的使用場景
KVO用于監聽對象屬性的改變。
(1)下拉重新整理、下拉加載監聽UIScrollView的contentoffsize;
(2)webview混排監聽contentsize;
(3)監聽模型屬性實時更新UI;
(4)監聽控制器frame改變,實作抽屜效果。
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property(nonatomic,strong)Person * p;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_p =[[Person alloc]init];
//添加觀察者
[_p addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"觀察到了");
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
_p.name [email protected]"hank";
}
成員變量是對成員屬性的一個封裝,每個成員變量都包含_name + getter + setter 方法
如果我們自己一個成員屬性,那麼使用observeValueForKeyPath就觀察不到值的變化,是以我們可以知道,觀察者觀察的是setter方法。
#import <Foundation/Foundation.h>
@interface Person : NSObject{
@public
NSString * _name;
}
//_name + getter + setter 方法!
//@property(nonatomic,copy)NSString * name;
@end
ViewController.h
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// _p.name [email protected]"hank";
_p->_[email protected]"hank";
}
KVO的底層實作原理:1.動态建立一個類NSKVONotyfing_Person,繼承自被繼承的類Person,重寫setter方法
2.改變P對象的類型!(子類類型)
#import "NSKVONotyfing_Person.h"
@implementation NSKVONotyfing_Person
-(void)setName:(NSString *)name{
[self willChangeValueForKey:@"name"];
[super setName:name];
[self didChangeValueForKey:@"name"];
}
@end