1. 程式人生 > 實用技巧 >iOS底層原理之部分面試題分析

iOS底層原理之部分面試題分析

Runtime Asssociate方法關聯的物件,是否需要在dealloc中釋放?

不需要釋放

分析

我們知道當一個物件銷燬的時候會呼叫dealloc方法,那麼我們先看下dealloc都進行了哪些操作。

  • dealloc函式呼叫了_objc_rootDealloc函式

  • _objc_rootDealloc函式呼叫rootDealloc函式
void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);

    obj->rootDealloc();
}
  • rootDealloc函式檢視
inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

rootDealloc函式中我們看到了判斷isa相關屬性的地方,實際上當一個物件存在會進入else中,即object_dispose函式

  • object_dispose函式檢視
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

通過objc_destructInstance函式找到物件,然後free,我們看objc_destructInstance函式

  • objc_destructInstance
    函式

重點檢視_object_remove_assocations函式

  • _object_remove_assocations函式分析

類、分類方法同名時呼叫順序是怎樣的?

非+load方法同名時,分類的方法在類的方法前面(注意不是覆蓋),因為分類的方法是在類realize之後 attach進去的,所以優先分類,其次類

+load方法同名時,優先類,其次分類

分類與類的擴充套件

分類

  • 專門用來給類新增新的方法
  • 不能新增屬性,但是可以通過runtime動態新增屬性(因為我們在前面的篇章中分析過,分類底層程式碼中有屬性列表)
  • 分類中@property定義的變數只會生成setter以及getter
    方法的宣告,但是不會生成對應的方法實現以及帶有下劃線的成員變數

作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這有個iOS交流群:642363427,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術!

類的擴充套件

@property

什麼是Runtime?

runtime是由C和C++彙編實現的一套API,為OC語言添加了面向物件和執行時功能。

  • 執行時:將資料型別的確定由編譯階段推遲到了執行階段。我們平時所寫的OC程式碼,最終轉換為runtime的C語言程式碼。

方法的本質是什麼?SEL、IMP是什麼?兩者之間的關係是什麼?

方法的本質

方法的本質是訊息的傳送,涉及到訊息傳送的流程有

objc_msgSend
lookUpImpOrForward
resolveInstanceMethod
forwardingTargetForSelector
mesthodSignatureForSelector & forwardInvocation

SEL、IMP

  • sel:方法編號,類比一本書的目錄
  • imp:方法函式指標地址,類比一本書的頁數
  • sel與imp關係:sel是方法編號,通過sel找到imp的函式指標地址,通過imp就能找到函式的實現

能否向編譯後的類中新增例項變數?能否向執行時建立的類新增例項變數?

編譯後例項變數儲存到 ro 中,一旦編譯完成,記憶體結構就完全確定了,無法再次修改

[self class] 與 [super class]的區別

我們先看以下如下程式碼列印結果,其中self是LGTeacher類,LGTeacher繼承於LGPerson,LGPerson繼承於NSObject

從列印結果中我們看到無論是[self class]還是[super class]的結果是一樣的,為什麼呢?

分析

  • 我們知道任何方法呼叫都會隱藏兩個引數,即(id self , sel _cmd),其中self是訊息接收者。對於[self class]來說,它的訊息接收者是自身LGTeacher沒什麼可說的,所以列印的是LGTeacher
  • 首先我們要知道super只是關鍵字,它意思是說從父類呼叫方法,因此[super class]就是直接呼叫的就是父類的class方法,它的本質是objc_msgSendSuper,只是objc_msgSendSuper速度更快,直接跳過self。但需要注意的是,[super class]的訊息接受者依然是LGTeacher,所以最終列印的是LGTeacher

記憶體偏移相關問題

我們先準備程式碼,定義IFPerson類,程式碼如下

我們再看ViewController程式碼

從上述程式碼中我們延伸出兩個問題:程式碼是否崩潰doSomething列印結果是什麼。先不回答這兩個問題,我們執行程式碼看結果如何,執行結果如下圖

從執行結果中我們可以看出程式碼不會崩潰且執行結果也出來了.

[(__bridge id)kc doSomething]為什麼不會崩潰?

首先我們知道對於一個物件,它的指標地址指向的是isa,同時isa地址指向當前的class,所以kc指向的是IFPersonisa,而person的指標指向的也是isa,這樣它們都是isacache_t中查詢doSomething方法,因此不會崩潰。

為什麼[(__bridge id)kc doSomething]列印的結果是ViewController

  • 從列印結果中[person doSomething]打印出出來shifx是沒有什麼問題的,畢竟給person.name賦值shifx,但是[(__bridge id)kc doSomething]列印的結果是ViewController呢?要解決這個問題首先我們需要知道person能夠找到name指標從isa記憶體平移了8個位元組移動到了name。那麼對於kc來說,它也需要指標平移,但是為什麼平移後的結果是viewController呢?這就需要明白棧地址是從高到低儲存的,且是先進後出,由於前面先呼叫了[super viewDidLoad]方法,且viewDidLoad的隱藏引數是(id self, IMP _cmd),所以self會先入棧,其次是cls->kc->person,出棧的順序剛好相反,由於[(__bridge id)kc doSomething]時需要指標平移,自然指向了self(即ViewController),所以列印的結果是ViewController
  • 為了驗證我們上面分析是否正確,我們修改程式碼位置,將宣告IFPerson *person = [[IFPerson alloc] init]放在[super viewDidLoad]之後,即

此時我們按照我們上面的分析self會先入棧,其次是person->cls->kc,猜測[(__bridge id)kc doSomething]列印結果應該是IFPerson (person的isa指向其Class),我們執行程式碼結果

可以看出我們的分析是正確的。