kvo:key-value-observing:鍵值監聽,可以監聽某個對象屬性的值
#import "Person.h"
@interface ViewController()
@property (nonatomic, strong) Person *person;
@property (nonatomic, strong) Person *person2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.person = [[Person alloc] init];
self.person2 = [[Person alloc] init];
NSKeyValueObservingOptions optips = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:optips context:@"sdf"];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.person.age = @"12";
self.person2.age = @"12";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"監聽到%@的%@屬性發生了變化-%@ -%@",object, keyPath, change, context);
}
結果是:
2018-07-12 22:34:17.544 newxc[2119:242191] 監聽到<Person: 0x60000001d0c0>的age屬性發生了變化-{
kind = 1;
new = 12;
old = "<null>";
} -sdf
只會發生一次監聽,那爲什麼同樣一個person類,set方法都在Person類裏,那麼爲什麼只有被監聽的能發生監聽呢?
原因如下,請看證實性的:
可以看到打印出來的isa指針都不一樣,所以說,這裏內部是被修改了,那具體的內部實現是什麼樣的呢,下面模擬一下這個內部實現,
#import "NSKVONotifying_Person.h"
// 僞代碼、僞代碼、僞代碼、僞代碼、僞代碼
@implementation NSKVONotifying_Person
- (void)setAge:(NSString *)age
{
_NSSetIntValueAndNotify();
}
void _NSSetIntValueAndNotify()
{
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey@"age"];
}
- (void)didChangeValueForKey:(NSString *)key
{
// 運行時,調用此方法,子類沒有 就調用父類的此方法。
// 通知父類的調用父類的kvo,進行實現
[self performSelector:@selector(observeValueForKeyPath:ofObject:change:context:)];
}
@end
這個NSKVONotifying_Person,是自己模擬剛纔打印出來的那個類,集成自Person類,內部有set方法,繼而下面的實現,還會調用父類的[super setAget:age]方法,讓這個age值真的改變,然後再會把這個監聽發出去,達到監聽的目的,具體的邏輯看下面
沒有加kvo監聽的邏輯實現如下:
所以可以得知,加了kvo和沒有加kvo走的並不是一套,加了kvo的走的是子類NSKVONotifying_Person的set方法,並達到監聽的目的。
添加監聽後不是一個類,驗證一:
驗證二:(地址)
驗證三
上面這個證明didChangeValueForKey 裏面是調用了監聽方法的。
首先:
證明這個類中確實是這幾個方法,
- (void)printMethodNameOfClass:(Class)clas
{
unsigned int cout;
// 獲得方法數組
Method *methodlist = class_copyMethodList(clas, &cout);
// 儲存方法名
NSMutableString *muString = [NSMutableString string];
// 遍歷所有的方法
for (int i = 0; i < cout; i ++) {
// 獲得方法
Method methd = methodlist[i];
// 獲得方法名
NSString *methodStr = NSStringFromSelector(method_getName(methd));
// 拼接方法名
[muString appendString:methodStr];
[muString appendString:@", "];
}
// 釋放
free(methodlist);
// 打印一下類和方法名
NSLog(@"%@ %@",clas, muString);
}
調用
[self printMethodNameOfClass:[self.person class]];
[self printMethodNameOfClass:object_getClass(self.person)];
打印結果:
2018-07-17 23:18:24.911 newxc[4213:1394789] Person setAge:, age, .cxx_destruct,
2018-07-17 23:18:24.911 newxc[4213:1394789] NSKVONotifying_Person setAge:, class, dealloc, _isKVOA,
能不能監聽的 前提 是有沒有實現set方法。
面試題:
1:ios用什麼方式實現對一個對象的kvo?(kvo的本質是什麼?)
利用runtimeAPI動態生成一個子類,並讓instance對象的isa指向這個全新的類,當修改instance對象的屬性時,會調用Foundation的——NSSetXXXValueAndNotify函數:(willChangeValueForKey; 父類原來的setter方法;didChangeValueForKey(內部會觸發監聽器(observer)的監聽方法(observerValueForKeyPath:)),)
2:如何手動觸發kvo?
(不調用setage方法)
手動調用willChangeValueForKey 和didChangeValueForKey方法,即可直接觸發kvo。
3:直接修改成員變量會觸發KVO麼?
不會觸發KVO,(添加kvo的person實例,其實是NSKVONotyfing_person類,再調用setter方法,不是調用person的setter方法,而是NSKVONotyfing_person的setter方法,因爲修改成員變量不是setter方法賦值self.person->age=@"12", 所以就無所謂調用NSKVONotyfing_person類的setter方法,也就不會實現kvo。)
結尾:這裏說一下心得:
我們平時處理代碼,初級的情況下,都會以編譯期查詢方法和寫方法的方式來思考問題,但其實,oc是一個動態語言,所以很多黑魔法和方法做出來都可以在真正的調用時刻-runtime時刻進行處理,這也就是真正的調用,只有類和對象的本質,方法的本質,沒有所謂的.h 和.m的限制,只是我們習慣了oc給我們的和剛開始接觸的.h 和.m規範,並沒有瞭解到真正的內部原理是什麼。就像web是沒有編譯的,所以它們執行代碼的真正調用和流程,就跟我們runtime調用是一樣的,所以,不用把runtime想象的多麼複雜,是真正的實現過程而已,蘋果給我們一個途徑去了解它,修改它, 就是runtime。所以需要知道本質的方法、本質的類是什麼、運行時調用函數和方法這些 都需要我們進行探索,而不僅僅限制於表面的.h的編譯期的限制。這僅僅是我自己的想法,如果有錯誤,歡迎指認。
另外補充一下,
問題:如果在項目中對person進行了監聽,也創建了一個NSKVONotifying_Person類,那麼會編譯通過麼?
答案是可以的。
編譯不通過是因爲,在編譯時刻,兩個類名相同,所以報錯,解決方法,就是不讓兩個同名的都在編譯時刻進入,因爲kvo是運行時刻創建的,並不在編譯時刻,在編譯時刻只有一個NSKVONotifying_Person,所以不報錯,可以通過。而程序的初始化,是先編譯後運行。