iOS 底層原理 自測(一)

iOS 底層原理 文章匯總

一、選擇題

有多選,有單選

1、在LP64下,一個指針的有多少個字節

  • A: 4
  • B: 8
  • C: 16
  • D: 64

解析:1個指針8字節

2、一個實例對象的內存結構存在哪些元素

  • A:成員變量
  • B: supClass
  • C: cache_t
  • D: bit

解析: 實例對象的大小由成員變量決定。其中BCD是類的結構

3、下面 sizeof(struct3)大小等于

struct LGStruct1 {
    char b;
    int c;
    double a; -- 逢8歸零
    short d;
}struct1;  -- 24

struct LGStruct2 {
    double a; 
    int b;
    char c;
    short d;
}struct2; -- 16


struct LGStruct3 {
    double a;
    int b;
    char c;
    struct LGStruct1 str1;
    short d;
    int e;
    struct LGStruct2 str2;
}struct3;
  • A: 48
  • B: 56
  • C: 64
  • D: 72

解析: 整體歸零法

  • LGStruct1 看最大a,意味著前面b+c占8字節,a占8字節,c占2字節,需要對齊,即滿足8的倍數 ==> 24字節

  • LGStruct2 看最大a,a占8字節,b+c+a 占8字節,對齊==> 16字節

  • LGStruct3 a占8字節,b+c占8字節,str1占24字節,d+e占8字節,str2占16自己, 對齊==>64字節

4、下列代碼: re1 re2 re3 re4 re5 re6 re7 re8輸出結果

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];     
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];   
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];     
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];  
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];      
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];    
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];     
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];   
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
  • A: 1011 1111
  • B: 1100 1011
  • C: 1000 1111
  • D: 1101 1111

解析:

  • +isKindOfClass:元類繼承鏈 vs 傳入類

  • -isKindOfClass:類繼承鏈 vs 傳入類

  • +isMemberOfClass:類的元類 vs 傳入類

  • -isMemberOfClass:對象父類 vs 傳入類

5、(x + 7) & ~7 這個算法是幾字節對齊

  • A: 7
  • B: 8
  • C: 14
  • D: 16

解析: 8自己對齊(抹零后三位)
帶入實際數據計算,例如(8+7)& ~7

  • 8+7 => 1111

  • ~7 => 1000

  • & => 1000

6、判斷下列數據結構大小

union kc_t {
    uintptr_t bits;
    struct {
        int a;
        char b;
    };
}
  • A: 8
  • B: 12
  • C: 13
  • D: 16

解析:聯合體共用內存 ,即互斥

7、元類的 isa 指向誰, 根元類的父類是誰

  • A: 自己 , 根元類
  • B: 自己 , NSObject
  • C: 根元類 , 根元類
  • D: 根元類 , NSObject

解析:經典的isa走位圖


isa走位圖

8、查找方法緩存的時候發現是亂序的, 為什么? 哈希沖突怎么解決的

  • A: 哈希函數原因 , 不解決
  • B: 哈希函數原因 , 再哈希
  • C: 他存他的我也布吉島 , 再哈希
  • D: 他亂由他亂,清風過山崗 , 不解決

解析:具體實現看objc源碼

9、消息的流程是

  • A: 先從緩存快速查找
  • B: 慢速遞歸查找 methodlist (自己的和父類的,直到父類為nil)
  • C: 動態方法決議
  • D: 消息轉發流程

解析:cache快速查找 - 慢速繼承鏈遞歸查找 - 動態方法決議 - 消息轉發(快速轉發 + 慢速轉發)

10、類方法動態方法決議為什么在后面還要實現 resolveInstanceMethod

  • A: 類方法存在元類(以對象方法形式存在), 元類的父類最終是 NSObject 所以我們可以通過resolveInstanceMethod 防止 NSObject 中實現了對象方法!
  • B: 因為在oc的底層最終還是對象方法存在
  • C: 類方法存在元類以對象方法形式存在.
  • D: 咸吃蘿卜,淡操心! 蘋果瞎寫的 不用管

解析:萬物皆對象 、isa走位圖

二、判斷題

1、光憑我們的對象地址,無法確認對象是否存在關聯對象

解析:可以通過isa判斷關聯對象的標識

2、int c[4] = {1,2,3,4}; int *d = c; c[2] = *(d+2)

解析:內存偏移

3、@interface LGPerson : NSObject{ UIButton *btn } 其中 btn 是實例變量

解析:

  • 屬性 = getter + setter + 成員變量

  • 成員變量 = 沒有下劃線的變量 + {}中定義

  • 實例變量 = 具備實例化的變量,是一種特殊的成員變量

4、NSObject 除外 元類的父類 = 父類的元類

解析:isa走位圖

5、對象的地址就是內存元素的首地址

6、類也是對象

解析:萬物皆對象

三、簡答題

1、怎么將上層OC代碼還原成 C++代碼

解析:

  • clang -rewrite-objc xxx.m -o xxx.cpp

  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m -o xxx.cpp

2、怎么打開匯編查看流程,有什么好處 ?

解析:

  • Xcode - Debug - workflow - 勾選always show disassembly

  • 好處:了解當前函數更深層的匯編執行,以及函數的底層實現,方便跟蹤內部代碼,并找到代碼來源

3、x/4gx 和 p/x 以及 p *$0 代表什么意思

解析:

  • x/4gx 輸出一段內存地址,以8字節的形式輸出8段

  • p/x 輸出一個數據結構的地址

  • p *$0 *$0為指向某一個數據空間的指針,輸出該數據的數據結構

4、類方法存在哪里? 為什么要這么設計?

解析:

  • 對象方法存儲在類中,類方法存儲在元類中

  • 好處:

    • 底層并未區分對象和類,其本質都是對象,即萬物皆對象

    • 方法調用的本質是消息發送,只是消息的接受者(即方法的查找對象)有所區別

    • 設計更加基于對象,符合面向對象的特性

5、方法慢速查找過程中的二分查找流程,請用偽代碼實現

解析:

  • 不斷的找起始位置base、有效數據量count

  • 當前目標位置 = base + 有效數據量/2

  • 查找到對應位置之后,不斷向前偏移,目的是為了找到第一個符合條件的數據

first = 0
probe
base = first
for(probe = 0;count != 0;count = count/2){
    probe = base + count/2
    if key == probe{
        while(probe > first && key == probe-1)
            probe--
        return probe
    }
    if key > value{
        base = probe+1
        count--
    }
}

6、ISA_MASK = 0x00007ffffffffff8ULL 那么這個 ISA_MASK 的算法意義是什么?

解析: 目的是為了得到isa中存儲的class信息。

  • 大部分isa都是不純的isa,即nonpointIsa,是一個64位的聯合體位域數據,而存儲class信息的部分只有其中的部分位,剩余的位置存儲了其他信息

  • 讀取class信息時,需要將其他位的信息清零,此時就需要用到掩碼

  • 任何數據與isa_mask進行按位與操作,都只保留isa_mask對應位的信息。其目的就是遮蓋不需要的位

7、類的結構里面為什么會有 rw 和 ro 以及 rwe ?

解析:

  • ro屬于clean memory,即在編譯時期確定的內存空間,只讀,加載后不會再改變的內存

  • rw屬于dirty memory,是運行時產生的內存,可讀可寫,可以向類中添加屬性、方法等,即在運行時可以改變的內存

  • rwe相當于類的額外信息,因為在使用過程中,只有很少的類會真正改變其內容,所以為了避免資源浪費就有了rwe

  • 運行時如果需要動態向勒種添加方法、協議等,會創建rwe,并將ro的數據優先attach到rwe中。在讀取時會優先返回rwe的數據,如果rwe中沒有被初始化,則返回ro

    • 有擴展,從rwe獲取

    • 沒有擴展,從ro獲取

  • rw中包含ro、rwe,其目的是為了讓dirty memory占用更少的空間,將rw中可變的部分抽取出來作為rwe

  • clean memory越多越好,dirty memory越少越好。因為iOS 系統底層是虛擬內存機制,在內存不足時,會將一部分內容回收掉,后面使用時需要再次從磁盤中加載的。

    • clean memory是可以從磁盤中重新加載的內存,例如mach-o文件、動態庫。

    • dirty memory是運行時產生的數據,是不能從磁盤中重新加載的,所以必須一直占用內存

    • 當系統物理內存緊張時會回收clean memory,如果dirty memory過大則會直接回收掉

  • 設計ro、rwe、rw的目的是為了更好更細致的區分clean memory和dirty memory

8、cache 在什么時候開始擴容 , 為什么?

解析:

  • 一般情況下:如果當前方法cache后,緩存的使用容量超過總容量的3/4,需要先進行擴容,擴容為原來的2倍,然后再插入本次的方法
  • 某些特殊預處理宏定義編譯命令下,首次會存儲滿之后在進行擴容
  • 擴展選擇3/4作為負載因子,是和hash表中使用的鏈表和紅黑樹數據結構有關,0.75是最符合泊松分布概率計算得出的數值,此時的hash表的空間和時間效率是最高的

9、objc_msgSend 為什么用匯編寫 , objc_msgSend 是如何遞歸找到imp?

解析:

  • 使用匯編響應速度快

  • 使用了兩個循環

    • 循環1:通過獲取的mask與要查找的_cmd進行hash運行,獲取下表,從而獲取_cmd對應的bucket;然后通過向前平移查找,每次平移16位,如果找到對應的sel,則cacheHit;當平移到bucket的首地址時,如果還沒有找到,則進入循環2

    • 循環2:首先獲取末尾bucket地址,同樣采用向前查找方式,向_cmd對應的地址進行平移查找

10、一個類的類方法沒有實現為什么可以調用 NSObject 同名對象方法

解析: isa走位圖 + 方法查找邏輯

  • 類的isa指向元類,在方法快速查找過程中,會根據類的isa找到元類,

  • 如果元類中沒有該方法,怎會走到lookUpImpOrForward慢速查找流程中,會根據元類的繼承鏈進行遞歸查找。其中元類的父類是根元類,根元類的父類指向NSObject,所以會找到NSObject的同名對象方法

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容