1、什么是isa指針
概念:
Every object has an isa instance variable that identifies the object's class. The runtime uses this pointer to determine the actual class of the object when it needs to.
每個對象都有一個標識對象類的isa實例變量。運行時使用此指針來確定對象需要時的實際類。
這就好比把isa拆開成 is a(是什么類的實例)的意思。
代碼中的isa:
在objc.h文件中有這樣定義:
從圖中我們可以看出三點:
1、 id類型是一個objc_object結構體的指針。
2、objc_object結構體包含一個Class 類型的變量isa。
3、 Class是objc_class結構體的指針。
事實上在objc的runtime中,類是用 objc_class 結構體表示的,對象是用 objc_object 結構體表示的。這也就解釋了為什么id類型可以指向OC中任意對象類型了。
到了這里我們只需要再明白objc_class結構體的內容就可以了。在runtime.h文件中objc_class結構體定義如下:
struct objc_class {
Class isa //所屬類的指針
Class super_class//指向父類的指針
const char *name //類名
long version // 版本
long info //供運行期使用的一些位標識。
long instance_size //實例大小
struct objc_ivar_list *ivars //成員變量數組
struct objc_method_list **methodLists //方法列表
struct objc_cache *cache//指向最近使用的方法.用于方法調用的優化
struct objc_protocol_list *protocols//協議的數組
}
當看到objc_class結構體的第一個變量也是Class類型的指針時,是不是很崩潰。不必難過,其實這正好驗證了萬物皆對象的事實。類也是對象,他是meteClass(元類)的實例。
到這里我們來整理下思路:
- 實例對象在運行時被表示成objc_object類型結構體,結構體內部有個isa指針指向objc_class結構體。
- objc_class內部保存了類的變量和方法列表以及其他一些信息,并且還有一個isa指針。這個isa指針會指向meteClass(元類),元類里保存了這個類的類方法列表。
-
為了完整性,其實元類里也有一個isa指針,這個isa指針,指向的是根元類,根元類的isa指針指向自己
大致如下面邏輯:
實例對象--(runtime)-->objc_object--(isa)-->objc_class--(isa)-->元類--isa-->根元類--isa-->自己。
然而值得一提的是:當我們調用某個類的方法時,如果這個類的方法列表里沒有該方法,則會去找這個類的父類的方法列表。這種機制就是通過objc_class的第二個變量super_class指針實現的。并且這種繼承關系會擴展到元類。最終類似于下圖的一種關系:
2、KVO的實現原理
key-value observing is implemented using a technique called isa-swizzling.
The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.
從Apple文檔中我們大概可以了解到:
KVO是通過"isa-swizzling"技術來實現的,當一個對象注冊觀察者時,這個對象的isa指針被修改指向一個中間類。
KVO 的實現依賴于 Objective-C 強大的 runtime(這里不詳細講解,我準備開一篇結合實例講解下runtime,喜歡我文章的可以關注下O(∩_∩)O~~)。當觀察A類型的對象時,在運行時會創建了一個集成自A類的NSKVONotifying_A類,且為NSKVONotifying_A重寫觀察屬性的setter 方法,setter 方法會負責在調用原 setter 方法之前和之后,通知所有觀察者屬性值的更改情況。
假設A類有個name屬性,NSKVONotifying_A重寫setName方法:
- (void) setName:(NSString *)name
{
[self willChangeValueForKey:@"name"];
[super setName:name];
[self didChangeValueForKey:@"name"];
}
被觀察屬性發生改變之前,willChangeValueForKey:
被調用,通知系統該 keyPath 的屬性值即將變更,來保存舊值;當改變發生后,didChangeValueForKey:
被調用,通知系統該 keyPath 的屬性值已經變更;之后,observeValueForKey:ofObject:change:context:
就會被調用。
3、手動觸發KVO
以上我們說的都是自動觸發,只要我們注冊了觀察者。只要被觀察的屬性值一改變就會調用observeValueForKey:ofObject:change:context:
方法,而有時候我們在特定的情況下,才去通知觀察者被觀察的屬性改變了。這就需要我們手動觸發KVO。大致步驟:
1、取消自動觸發:重寫+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
2、重寫屬性的setter方法,根據需求判斷是否需要調用willChangeValueForKey:
和didChangeValueForKey:
方法。
代碼如下:
- (void)setName:(NSString *)name{
if ([name isEqualToString:@"小白"]) {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}else{
_name = name;
}
}
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
// 這里只是取消了name屬性的自動觸發
if ([key isEqualToString:@"name"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
這樣寫之后,只有當Dog類對象的name屬性等于"小白"時才會通知觀察者屬性改變了。
注意:取消自動觸發的方法如果直接返回NO,那么這個類的對象的所有屬性值都會取消自動觸發。所以最好根據需要自己判斷。
從上面的介紹中我們看到KVO的調用其實很繁瑣,如果有個帶有block的方法就好了,這篇文章手把手教你封裝一個帶有block的KVO方法。
-------------完--------------
最后:喜歡我文章的可以多多點贊和關注,您的鼓勵是我寫作的動力。O(∩_∩)O~