OC底層知識點

1. OC對象的本質形式 (一個NSObject對象佔用多少內存)

  • OC本質底層都是C,C++代碼混合實現的--編譯彙編代碼--機器代碼前端

  • 對象和類結構是基於C和C++中的結構體struct實現的。探究NSObject的本質,OC代碼轉換爲C和C++混合代碼。xcode用的編譯器前端是clang。xcode

  • 由於1個NSObject對象對應1個結構體內只有1個isa指針,指針在iOS64位系統內佔8個字節,所以一個NSObject對象在內存裏是佔用1個指針的大小。類內部的方法和方法的實現存儲空間並不在對象內,obj指針就是isa地址其實就是結構體地址就是NSObject對象的地址。緩存

  • 只要繼承自NSObject對象,結構中確定會有一個isa指針。bash

2. 實例對象 class類對象 元類對象(類信息存放在什麼地方)

  • instance實例對象:alloc出來的。存放成員變量的值,isa。函數

  • class類對象 :class類型,[實例對像 class],object_getClass(object1),每個類在內存中有且只有一個class對象,存放對象方法信息,屬性信息,成員變量信息(名字等),協議信息,superClass指針,isa等。ui

  • metaclass元類對象 :也是class類型,每個類在內存中有且只有一個元類對象,在內存中和類對象結構同樣的,可是用途不同。object_getClass(類對象),object_getClass([NSObject class]);存放有用信息和class類對象不同,static類型成員變量,屬性多是空的,存放類方法,superClass指針,isa等。編碼

3. isa和superClass底層指針指向

  • instance實例對象:isa指向本身的class類對象
  • class類對象:isa指向本身的metaClass元類對象。class類對象中的superClass指針指向父類的類對象
  • metaClass元類對象:全部元類的isa最終都指向基類( 如NSObject)的metaClass。metaClass元類中的superClass指針指向父類的metaClass元類對象。
  • Tips: 基類的metaClass的superClass指針指向基類的class類對象. (這裏以後應該再說一下消息轉發機制的)

4. OC消息傳遞

  • 熟悉runtime的都知道,OC的方法調用其實應該叫消息傳遞,消息傳遞是動態綁定的機制來決定須要調用的方法;[person age];會被翻譯爲objc_msgSend(person, @selector(age));objc_msgSend查找方法時,會先從Person緩存中查找,找到直接返回 (緩存是存在類中的,每一個類都有一份方法緩存struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;)。
  • 找不到,再去 Class 的方法列表中找。在 objc-runtime-new.mm 文件中有一個函數 lookUpImpOrForward,這個函數的做用就是無緩存時去查找方法的實現。lookUpImpOrForward 並非objc_msgSend 直接調用的,而是經過 _class_lookupMethodAndLoadCache3 方法。
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj,
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
複製代碼

lookUpImpOrForward屬於源代碼層級的了,想要具體瞭解能夠直接 draveness.me/messagespa

具體查找過程

  • 在當前類中查找實現:先調用了cache_getImp從某個類的cache 屬性中獲取對應的實現,若是查找到實現,跳轉到done。
  • 若是沒找到緩存。經過當前類instance實例對象的isa找到類對象(class),接着在當前類的class對象內找到方法列表methodLists找到對應的Method,最後找到method中的IMP,執行具體實現並添加到緩存。
  • 若是在當前類方法列表中沒有找到,經過superClass指針找到當前類的父類,在父類中尋找實現。這一操做與上一步基本是同樣的,先查找緩存,沒找到接着搜索方法列表,添加到緩存。與當前類的區別是,在父類中找到了_objc_msgForward_impcache實現會交給當前類處理。

沒找到對應的selector?(方法決議+消息轉發)

當前類中和父類中都沒有找到對應方法處理,系統會提供三次補救機會翻譯

  • 方法決議(method resolve):動態加載,系統就會調用receiver的
    + (BOOL)resolveInstanceMethod:(SEL)sel {}(實例方法) 和
    + (BOOL)resolveClassMethod:(SEL)sel {}(類方法)
void myMethod(id self, SEL _cmd,NSString *nub) {
NSLog(@"ifelseagexx%@",nub);
};

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"Method Resolution");
    if (sel == @selector(age)) { // 方法沒有被實現
        class_addMethod([self class], sel, imp_implementationWithBlock(^() {
            // 實現方法的代碼寫在這裏
            
        }), "v@:");
        
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
複製代碼

咱們只須要在resolveInstanceMethod:方法中,利用class_addMethod 方法,將未實現的 age綁定到(IMP)myMethod 上。這樣就能完成轉發,最後返回YES。若是實現了這個方法,系統就會從新啓動一次消息發送。指針

  • 在緩存、當前類、父類以及 resolveInstanceMethod: 都沒有解決實現查找的問題時,執行第二次:
- (id)forwardingTargetForSelector:(SEL)aSelector {}
複製代碼

肯定是哪一個對象處理(找到該對象的方法名與消息中的選擇器的方法名一致的方法並調用)這個消息。使用場景通常是將 A 類的某個方法,轉發到 B 類的實現中去。

  • forwardingTargetForSelector:若是實現這個方法時,返回值爲nil或者self即表明不處理消息。執行第三次:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {};
- (void)forwardInvocation:(NSInvocation *)anInvocation {};
複製代碼

第一個要求返回一個方法簽名,第二個方法轉發具體的實現。兩者相互依賴,只有返回了正確的方法簽名,纔會執行第二個方法。
此次的轉發做用和第二次的比較相似,都是將 A 類的某個方法,轉發到 B 類的實現中去。不一樣的是,第三次的轉發相對於第二次更加靈活,forwardingTargetForSelector: 只能固定的轉發到一個對象;forwardInvocation: 可讓咱們轉發到多個對象中去。

tips:若是傳遞走到最後都沒有處理,系統就會崩潰並報錯:unrecognized selector sent to instance 0x7fea0ac2b0a0

5. KVO的本質

  • 當一個對象使用了KVO監聽,系統會修改這個對象的isa指針,改成指向一個全新的經過runtime動態建立的子類NSKVONotifying_class
  • 子類擁有本身的set方法實現:

willChangeValueForKey:
原來的seter方法setClass:
didChangeValueForkey:,(這個方法內部又會調用監聽器方法observeValueForKeyPath:ofObject:change:context:)

  • 手動觸發KVO

addObserver:selector:name:object:後手動調用willChangeValueForKey:didChangeValueForkey:,能夠實現不改變屬性值手動觸發監聽KVO方法。注意:必須will和did一塊兒成對調用,猜想可能didChangeValueForkey在觸發監聽方法的時候會檢測will方法有沒有被調用,進而成功觸發observeValueForKeyPath:ofObject:change:context:

  • 動態建立的子類NSKVONotifying_class中其實除了會重寫原類的setClass:方法(不會重寫get方法)外,經過class_copyMethodList()能夠發現,class() dealloc _isKVOA會新出如今NSKVONotifying_class類對象的方法裏。由於子類重寫了KVO的class方法,[object class]獲取的類對象仍是原類對象,object_getClass(object)獲取到的是NSKVONotifying_class類對象。

6. KVC的本質

KVC的全稱是Key-Value Coding,俗稱「鍵值編碼」,能夠經過一個Key來訪問某個屬性

  • 常見的API:
    - (void)setValue:(nullable id)value forKey:(NSString *)key;
    - (nullable id)valueForKey:(NSString *)key;
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    - (nullable id)valueForKeyPath:(NSString *)keyPath;
  • kvc賦值屬於runtime層級的直接賦值,經過KVC修改屬性也會觸發KVO。
    • 首先會按照,setKey:``,_setKey:順序尋找方法。若是有找到方法存在會傳遞參數直接調用這個方法。
    • 若是沒有找到方法,會查看accessInstanceVariablesDirectly方法的返回值。若是返回值爲NO,不容許直接返回成員變量,調用setValue:forUndefineKey:並拋出異常。若是容許會去訪問成員變量,若是找到了成員變量會直接賦值(依然會觸發KVO,內部作了willChangeValueForKey:didChangeValueForkey:),若是沒找到依然會拋出異常錯誤。
相關文章
相關標籤/搜索