一、選擇題
有多選,有單選
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的同名對象方法