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
指向的是IFPerson
的isa
,而person
的指標指向的也是isa
,這樣它們都是isa
從cache_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)
,我們執行程式碼結果
可以看出我們的分析是正確的。