iOS经典面试题之深入分析“内存平移”的原理
生活随笔
收集整理的這篇文章主要介紹了
iOS经典面试题之深入分析“内存平移”的原理
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、拋磚引玉
- 現在有一個 YDWPerson 類 ,其中有一個屬性 name 和一個實例方法 saySomething,如下:
- 通過以下代碼的方式,能否調用實例方法?為什么?
二、調試分析
① 不訪問類變量
- 在日常開發中,我們采取的是如下的調用方式:
- 不過,運行上面的代碼,可以看到兩種方式都可以調用成功,如下所示:
- [person saySomething] 的本質是對象發送消息,那么當前的 person 是什么呢?我們來一步一步分析。
- 首先,person 的 isa 指向類 YDWPerson,即 person 的首地址指向 YDWPerson 的首地址,我們可以通過 YDWPerson 的內存平移找到 cache,在 cache 中查找方法:
- [(__bridge id)boy saySomething] 中的 boy 是來自于 YDWPerson 這個類,有一個指針 boy,將其指向 YDWPerson 的首地址:
- 因此,person 是指向 YDWPerson 類的結構,boy 也是指向 YDWPerson 類的結構,然后都是在 YDWPerson 中的 methodList 中查找方法:
② 訪問打印類變量
- 現在,我們繼續修改 saySomething 方法實現,如下所示:
- 再次運行,查看打印結果:
- 可以看到:boy 調用打印的 name 是 <ViewController: 0x7fbee94051c0>,而 person 調用打印的 name 則是 (null),為什么打印不一致呢?
③ 原理分析
- 首先 person 調用 name,是由于 self 指向 person 的內存結構,然后通過內存平移8字節,去取值 name,即 self 指針首地址平移 8 字節獲得,如下:
- boy 是內存中一個 8 字節的指針,指向 cls,相當于 person 指針指向 YDWPerson 的一個實例,所以 self.name 的獲取,相當于 boy 首地址的指針也需要平移 8 字節尋找 name,那么此時的 boy 的指針地址是多少?平移 8 字節獲得的是什么呢?
- boy 是一個指針,是存在棧中的,棧是一個先進后出的結構,參數傳入就是一個不斷壓棧的過程:
- 其中隱藏參數會壓入棧,且每個函數都會有兩個隱藏參數(id self,sel _cmd),可以通過 clang 查看底層編譯;
- 隱藏參數壓棧的過程,其地址是遞減的,而棧是從高地址->低地址分配的,即在棧中,參數會從前往后一直壓棧(棧是一個先進后出的隊列,內存從高地址到低地址分配,所以先壓入棧的地址高);
- super 通過 clang 查看底層的編譯,是 objc_msgSendSuper,其第一個參數是一個結構體 __rw_objc_super(self,class_getSuperclass);
- 因此入棧的變量如下:self–>_cmd–> cls–> boy–> person,但是[super viewDidLoad];調用時會產生一個結構體傳入參數,因此這個結構體也會被壓入到當前棧中:self–>_cmd–>(id)class_getSuperclass(objc_getClass(“YDWTeacher”))–>self–> cls–> boy–> person;
- self 和 _cmd 是 viewDidLoad 方法的兩個隱藏參數,是高地址->低地址正向壓棧的;
- class_getSuperClass 和 self 為 objc_msgSendSuper2 中的結構體成員,是從最后一個成員變量,即低地址->高地址反向壓棧;
- 添加以下代碼:
- 運行結果如下所示:
- 其中,為什么 class_getSuperclass 是 ViewController,因為 objc_msgSendSuper2返回的是當前類,兩個self,并不是同一個self,而是棧的指針不同,但是指向同一片內存空間;
- [(__bridge id)boy saySomething]調用時,此時的 boy 是 YDWPerson: 0x7ffee77cd068,所以 saySomething 方法中傳入 self 的還是 YDWPerson,但并不是我們通常認為的 YDWPerson,而是我們當前傳入的消息接收者,即YDWPerson: 0x7ffee77cd068,是 YDWPerson 的實例對象,此時的操作與普通的 YDWPerson 是一致的,即 YDWPerson 的地址內存平移 8 字節;
- 普通person流程:person -> name - 內存平移8字節;
- boy 流程:0x7ffee77cd068 + 0x80 -> 0x7ffee77cd070,即為self,指向<ViewController: 0x7f7fbe2045f0>,如下圖所示:
- 其中 person 與 YDWPerson 的關系是 person 是以 YDWPerson 為模板的實例化對象,即 alloc 有一個指針地址,指向 isa,isa 指向 YDWPerson,它們之間關聯是有一個 isa 指向。
- 而 boy 也是指向 YDWPerson 的關系,編譯器會認為 boy 也是 YDWPerson 的一個實例化對象,即 boy 相當于 isa,即首地址,指向 YDWPerson,具有和 person 一樣的效果,簡單來說,我們已經完全將編譯器“騙”過了,即 boy 也有 name。由于person 查找 name 是通過內存平移 8 字節,所以 boy 也是通過內存平移 8 字節去查找 name;
④ 棧和堆分別存放了什么?
- alloc 的對象存放在堆中;
- 指針、對象存放于棧中,例如 person 指向的空間在堆中,person 所在的空間在棧中;
- 臨時變量存放棧中;
- 屬性值存放在堆中,屬性隨對象是存放在棧中;
三、總結
- 堆是從小到大,即低地址 -> 高地址;
- 棧是從大到小,即從高地址 -> 低地址分配;
- 函數隱藏參數會從前往后一直壓棧,即從高地址 -> 低地址 開始入棧,
- 結構體內部的成員是從低地址 -> 高地址;
- 一般情況下,內存地址有如下規則:
- 0x60 開頭表示在堆中;
- 0x70 開頭的地址表示在棧中;
- 0x10 開頭的地址表示在全局區域中。
總結
以上是生活随笔為你收集整理的iOS经典面试题之深入分析“内存平移”的原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS之深入解析数组遍历的底层原理和性能
- 下一篇: iOS经典面试题之“runtime是如何