iOS 面試題(2)

1.編譯過程做了哪些事情?

C++,Objective C 都是編譯語言,編譯語言在執行的時候,必須先通過編譯器生成機器碼,機器碼可以直接在CPU上執行,所以執行效率很高。OC 編譯依賴於Clang + LLVM

iOS 編譯

不管是OC 還是swift,都是採用Clang 作爲編譯器的前端,LLVM 作爲編譯器的後端。所以簡單的編譯過程 CLang->LLVM Optimizer -> LLVM Code Generator

編譯器前端

編譯器前端的任務是進行:語法分析,語義分析,生成中間代碼。在這個過程會進行類型檢查,如果發現錯誤或者警告會標註出來在哪一行。

Lexical Analysis  Driver Parser  Indexing Semantic Analysis  Code Completion Abstract Syntax Trees Rewriter Static Analyzer Tooling

編譯器後端

編譯器後端會進行機器無關的代碼化,生成機器語言,並進行及其相關的代碼優化。

LVVM 優化器會進行BitCode 的生成,鏈接期優化等等

Bitcode   Passes Analysis   Link-Time Optimization  Transforms

LVVM 會針對不同的架構生成不同的機器碼

執行一次Xcode build 的流程

當你在Xcode中,選擇build 的時候,會執行如下過程

編譯信息寫入輔助文件,創建編譯後的文件架構

處理文件打包信息,例如在debug 環境下

Entitilements: {

 "application-identifier" = "";

 "aps-enviroment" = development;

}

執行cocoaPods 編譯前的腳本

例如對於使用CocoaPod的工程會執行CheckPods Manifest.lock

編譯各個.m文件,使用CompileC和clang命令

1.CompileC ClassName.o ClassName.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler

2.export LANG=en_US.US-ASCII

3.export PATH="..."

4.clang-x objective-c -arch x86_64 -fmessage-length=0 -fobjc-arc...

-Wno-missing-field-initializers ... -DDEBUG=1 ... -isysroot

iPhoneSimulator10.1.sdk -fasm-blocks ... -I 上文提到的文件 -F 所需要的Framework-iquote 所需要的Framework ... -c ClassName.c -o ClassName.o




2、字典的大致實現原理

  NSDictionary(字典)是使用hash 表來實現key 和value 之間的映射和存儲的
oc NSDictionary 的底層其實是一個hash 表
哈希原理

散列表,是根據關鍵碼值(key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中的一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
 
給定表M,存在函數f(key),對任意給定的關鍵字值key,代入函數後若能得到包含該關鍵字的記錄在表中的地址,則稱表M爲hash表,函數爲hash 函數。

哈希概念:哈希表的本質是一個數組,數組中每一個元素成爲一個箱子,箱子中存放的是鍵值對。

哈希存儲的過程

1、根據key 計算出它的哈希值h
2、假設箱子的個數爲n,那麼這個鍵值對應該放在(h%n)個箱子中
3、如果該箱子中已經有了鍵值對,就使用開放尋址法或者拉鍊法解決衝突

在使用拉鍊法解決哈希衝突時,每個箱子其實是一個鏈表,屬於同一個箱子的所有鍵值對都會排列在鏈表中
哈希表還有一個重要的屬性:負載因子,它用來衡量哈希表的空/滿程度,一定程度上也可以提現查詢的效率,計算公式爲:

 負載因子 = 總鍵值對數 / 箱子個數

負載因子越大,意味着哈希表越滿,越容易導致衝突,性能也就越低。因此,一般來說,當負載因子大於某個常數(可能是1,或者0.75等)時,哈希表將自動擴容。

哈希表在自動擴容時,一般會創建兩倍於原來個數的箱子,因此即使 key 的哈希值不變,對箱子個數取餘的結果也會發生改變,因此所有鍵值對的存放位置都有可能發生改變,這個過程也稱爲重哈希(rehash)。

哈希表的擴容並不總是能夠有效解決負載因子過大的問題。假設所有 key 的哈希值都一樣,那麼即使擴容以後他們的位置也不會變化。雖然負載因子會降低,但實際存儲在每個箱子中的鏈表長度並不發生改變,因此也就不能提高哈希表的查詢性能。

基於以上總結,細心的朋友可能會發現哈希表的兩個問題:

1.如果哈希表中本來箱子就比較多,擴容時需要重新哈希並移動數據,性能影響較大。

2.如果哈希函數設計不合理,哈希表在極端情況下會變成線性表,性能極低。


3、block 和函數指針的理解

相似點:
函數指針和block 都可以實現回調的操作,聲明上也很相似,實現上都可以看成一個代碼片段。
函數指針類型和block 類型都可以作爲變量和函數參數的類型。(typedef 定義別名後,這個別名就是一個類型)

不同點:
函數指針只能指向預先定義好的函數代碼塊(可以是其他文件裏面定義,通過函數參數動態傳入的),函數地址是在編譯連接時就已經確定好的。
block 本質是Objective-C 對象,是NSObject 的子類,可以接收消息。

函數裏面只能訪問全局變量,而Block 代碼塊不光能訪問全局變量,還擁有當前棧內存和堆內存變量的可讀性(當然通過__block 訪問指示符修飾的局部變量還可以在block代碼塊裏面進行修改)。

從內存的角度看,函數指針只不過是指向代碼區的一段可執行代碼,而block 實際上是程序運行過程中在棧內存動態創建的對象。可以向其發送copy 消息將block 對象拷貝到堆內存,以延長其生命週期。

4、一般開始做一個項目,你的架構是如何思考的

https://casatwy.com/iosying-yong-jia-gou-tan-wang-luo-ceng-she-ji-fang-an.html
https://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de-zu-zhi-he-diao-yong-fang-an.html

5、UIKit


6、KVO 的實現原理

KVO 是基於運行時實現的

isa	Class	NSKVONotifying_Person	0x000001a12de2f545

上述例子中,當 p1.name 的值改變時,p1對象的 isa 指針會指向 NSKVONotifying_Person,意味着,在程序運行時,會動態生成一個 NSKVONotifying_Person 類,該類繼承於 Person,而且該類中也有個 -setName: 方法,方法中在設置 name 的同時實現了:


- (void)setName:(NSString *)name
{
    [super setName:name];
    
    // 這兩個方法底層會調用observer的- (void)observeValueForKeyPath: ofObject: change: context:這個方法
    [self willChangeValueForKey:@"age"];
    [self didChangeValueForKey:@"age"];
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章