iOS底層面試題(下篇)

世界承認日羣衆辦事幹貨公衆號首圖 2.jpg

7月,iOS求職跳槽的相對較少,能在這個時間段求職的,不是被迫,就是對本身的技術很自信;
針對7月,特別總結了第三份iOS常見大廠面試題(下);前端

iOS面試題分爲 上、中、下三部分,方便你們觀看;git

請先本身答一答github

話很少說;直接上題本文收錄:公衆號【iOS進階寶典《iOS底層面試題(下篇)》】面試

13. 如何用Charles抓HTTPS的包?其中原理和流程是什麼?

流程:後端

  • 首先在手機上安裝Charles證書緩存

  • 在代理設置中開啓Enable SSL Proxying服務器

  • 以後添加須要抓取服務端的地址

原理:markdown

Charles做爲中間人,對客戶端假裝成服務端,對服務端假裝成客戶端。簡單來講:網絡

  • 截獲客戶端的HTTPS請求,假裝成中間人客戶端去向服務端發送HTTPS請求
  • 接受服務端返回,用本身的證書假裝成中間人服務端向客戶端發送數據內容。

具體流程以下圖:扯一扯HTTPS單向認證、雙向認證、抓包原理、反抓包策略dom

iOS底層面試題(下篇)

14. 什麼是中間人?如何避免?

中間人就是截獲到客戶端的請求以及服務器的響應,好比Charles抓取HTTPS的包就屬於中間人。

避免的方式:客戶端能夠預埋證書在本地,而後進行證書的比較是不是匹配的

15. 瞭解編譯的過程麼?分爲哪幾個步驟?

1:預編譯:主要處理以「#」開始的預編譯指令。

2:編譯:

  • 詞法分析:將字符序列分割成一系列的記號。

  • 語法分析:根據產生的記號進行語法分析生成語法樹。

  • 語義分析:分析語法樹的語義,進行類型的匹配、轉換、標識等。

  • 中間代碼生成:源碼級優化器將語法樹轉換成中間代碼,而後進行源碼級優化,好比把 1+2 優化爲 3。中間代碼使得編譯器被分爲前端和後端,不一樣的平臺能夠利用不一樣的編譯器後端將中間代碼轉換爲機器代碼,實現跨平臺。

  • 目標代碼生成:此後的過程屬於編譯器後端,代碼生成器將中間代碼轉換成目標代碼(彙編代碼),其後目標代碼優化器對目標代碼進行優化,好比調整尋址方式、使用位移代替乘法、刪除多餘指令、調整指令順序等。

3:彙編:彙編器將彙編代碼轉變成機器指令。

  • 靜態連接:連接器將各個已經編譯成機器指令的目標文件連接起來,通過重定位事後輸出一個可執行文件。

  • 裝載:裝載可執行文件、裝載其依賴的共享對象。

  • 動態連接:動態連接器將可執行文件和共享對象中須要重定位的位置進行修正。

最後,進程的控制權轉交給程序入口,程序終於運行起來了。

16. 靜態連接瞭解麼?靜態庫和動態庫的區別?

靜態連接是指將多個目標文件合併爲一個可執行文件,直觀感受就是將全部目標文件的段合併。須要注意的是可執行文件與目標文件的結構基本一致,不一樣的是是否「可執行」。

  • 靜態庫:連接時完整地拷貝至可執行文件中,被屢次使用就有多份冗餘拷貝。

  • 動態庫:連接時不復制,程序運行時由系統動態加載到內存,供程序調用,系統只加載一次,多個程序共用,節省內存。

17. App網絡層有哪些優化策略?

  • 優化DNS解析和緩存

  • 對傳輸的數據進行壓縮,減小傳輸的數據

  • 使用緩存手段減小請求的發起次數

  • 使用策略來減小請求的發起次數,好比在上一個請求未着地以前,不進行新的請求

  • 避免網絡抖動,提供重發機制

18:[self class] 與 [super class]

@implementation Son : Father

(id)init
    {
    self = [super init];
    if (self)
    {
    NSLog(@"%@", NSStringFromClass([self class]));
    NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
    }
    @end

self和super的區別:

  • self 是類的一個隱藏參數,每一個方法的實現的第一個參數即爲self
  • super並非隱藏參數,它實際上只是一個」編譯器標示符」,它負責告訴編譯器,當調用方法時,去調用父類的方法,而不是本類中的方法。

在調用[super class]的時候,runtime會去調用objc_msgSendSuper方法,而不是objc_msgSend

OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message. 
# if !defined(__cplusplus) &&  !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
# else
__unsafe_unretained Class super_class;
# endif
/* super_class is the first class to search */
}

objc_msgSendSuper方法中,第一個參數是一個objc_super的結構體,這個結構體裏面有兩個變量,一個是接收消息的receiver,一個是當前類的父類super_class

objc_super結構體指向的superClass父類的方法列表開始查找selector,父類找到了,父類就執行這個方法。

class 方法的內部實現:

- (Class)class {
return object_getClass(self);
}

在class 方法內,默認傳入的是self, 不管調用者是誰。

因此這個道題的答案就出來了: 兩個打印的都是當前的類。

18.isKindOfClass 與 isMemberOfClass

下面代碼輸出什麼?

@interface Sark : NSObject
@end

@implementation Sark
@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];

NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}

先來分析一下源碼這兩個函數的對象實現

+ (Class)class {
return self;
}

(Class)class {
    return object_getClass(self);
    }

Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}

inline Class
objc_object::getIsa()
{
if (isTaggedPointer()) {
uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
return ISA();
}

inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
return (Class)(isa.bits & ISA_MASK);
}
(BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
    if (tcls == cls) return YES;
    }
    return NO;
    }

 (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
    if (tcls == cls) return YES;
    }
    return NO;
    }

(BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
    }

(BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
    }

首先題目中NSObject 和 Sark分別調用了class方法。

  • + (BOOL)isKindOfClass:(Class)cls方法內部,會先去得到object_getClass的類,而object_getClass的源碼實現是去調用當前類的obj->getIsa(),最後在ISA()方法中得到meta class的指針。
  • 接着在isKindOfClass中有一個循環,先判斷class是否等於meta class,不等就繼續循環判斷是否等於super class,不等再繼續取super class,如此循環下去。

  • [NSObject class]執行完以後調用isKindOfClass,第一次判斷先判斷NSObject 和 NSObjectmeta class是否相等,以前講到meta class的時候放了一張很詳細的圖,從圖上咱們也能夠看出,NSObjectmeta class與自己不等。

  • 接着第二次循環判斷NSObjectmeta classsuperclass是否相等。仍是從那張圖上面咱們能夠看到:Root class(meta) 的superclass 就是 Root class(class),也就是NSObject自己。因此第二次循環相等,因而第一行res1輸出應該爲YES

  • 同理,[Sark class]執行完以後調用isKindOfClass,第一次for循環,SarkMeta Class[Sark class]不等,第二次for循環Sark Meta Class的super class 指向的是 NSObject Meta Class, 和 Sark Class不相等。

  • 第三次for循環,NSObject Meta Classsuper class指向的是NSObject Class,和 Sark Class 不相等。第四次循環,NSObject Class的 super class 指向 nil, 和 Sark Class不相等。第四次循環以後,退出循環,因此第三行的res3輸出爲NO

  • 若是把這裏的Sark改爲它的實例對象,[sark isKindOfClass:[Sark class],那麼此時就應該輸出YES了。由於在isKindOfClass函數中,判斷sark的meta class是本身的元類Sark,第一次for循環就能輸出YES了。

  • isMemberOfClass的源碼實現是拿到本身的isa指針和本身比較,是否相等。

  • 第二行isa 指向 NSObject 的 Meta Class,因此和 NSObject Class不相等。第四行,isa指向SarkMeta Class,和Sark Class也不等,因此第二行res2和第四行res4都輸出NO。

19.Class與內存地址

下面的代碼會?**Compile Error / Runtime Crash / NSLog…?**

@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;

(void)speak;
    @end
    @implementation Sark
(void)speak {
    NSLog(@"my name's %@", [self.name](http://self.name/));
    }
    @end
    @implementation ViewController
(void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Sark class];
    void *obj = &cls;
    [(__bridge id)obj speak];
    }
    @end

這道題有兩個難點。

  • 難點一:obj調用speak方法,到底會不會崩潰。
  • 難點二:若是speak方法不崩潰,應該輸出什麼?

首先須要談談隱藏參數self和_cmd的問題。 當[receiver message]調用方法時,系統會在運行時偷偷地動態傳入兩個隱藏參數self_cmd,之因此稱它們爲隱藏參數,是由於在源代碼中沒有聲明和定義這兩個參數。self在已經明白了,接下來就來講說_cmd_cmd表示當前調用方法,其實它就是一個方法選擇器SEL

難點一,能不能調用speak方法?

id cls = [Sark class];
void *obj = &cls;

答案是能夠的。obj被轉換成了一個指向Sark Class的指針,而後使用id轉換成了objc_object類型。obj如今已是一個Sark類型的實例對象了。固然接下來能夠調用speak的方法。

難點二,若是能調用speak,會輸出什麼呢?

不少人可能會認爲會輸出sark相關的信息。這樣答案就錯誤了。

正確的答案會輸出

my name is <ViewController: 0x7ff6d9f31c50>

內存地址每次運行都不一樣,可是前面必定是ViewController。why?

咱們把代碼改變一下,打印更多的信息出來。

- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"ViewController = %@ , 地址 = %p", self, &self);
id cls = [Sark class];
NSLog(@"Sark class = %@ 地址 = %p", cls, &cls);
void *obj = &cls;
NSLog(@"Void *obj = %@ 地址 = %p", obj,&obj);
[(__bridge id)obj speak];
Sark *sark = [[Sark alloc]init];
NSLog(@"Sark instance = %@ 地址 = %p",sark,&sark);
[sark speak];
}

咱們把對象的指針地址都打印出來。輸出結果:

ViewController = <ViewController: 0x7fb570e2ad00> , 地址 = 0x7fff543f5aa8
Sark class = Sark 地址 = 0x7fff543f5a88
Void *obj = <Sark: 0x7fff543f5a88> 地址 = 0x7fff543f5a80

my name is <ViewController: 0x7fb570e2ad00>

Sark instance = <Sark: 0x7fb570d20b10> 地址 = 0x7fff543f5a78
my name is (null)

objc_msgSendSuper2 解讀

// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id objc_msgSendSuper2(struct objc_super *super, SEL op, ...)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);

objc_msgSendSuper2方法入參是一個objc_super *super。

/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message. 
# if !defined(__cplusplus) && !**OBJC2**
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
# else
__unsafe_unretained Class super_class;
# endi
/* super_class is the first class to search */
};
# endif

因此按viewDidLoad執行時各個變量入棧順序從高到底爲self_cmdself.classselfobj

  • 第一個self和第二個_cmd是隱藏參數。

  • 第三個self.class和第四個self[super viewDidLoad]方法執行時候的參數。

  • 在調用self.name的時候,本質上是self指針在內存向高位地址偏移一個指針。在32位下面,一個指針是4字節=4*8bit=32bit(64位不同可是思路是同樣的)

  • 從打印結果咱們能夠看到,obj就是cls的地址。在obj向上偏移32bit就到了0x7fff543f5aa8,這正好是ViewController的地址。

因此輸出爲my name is **&lt;ViewController: 0x7fb570e2ad00&gt;**

至此,Objc中的對象究竟是什麼呢?

實質:Objc中的對象是一個指向ClassObject地址的變量,即 id obj = &ClassObject , 而對象的實例變量 void *ivar = &obj + offset(N)

加深一下對上面這句話的理解,下面這段代碼會輸出什麼?

- (void)viewDidLoad {
[super viewDidLoad];

NSLog(@"ViewController = %@ , 地址 = %p", self, &self);

NSString *myName = @"halfrost";

id cls = [Sark class];
NSLog(@"Sark class = %@ 地址 = %p", cls, &cls);

void *obj = &cls;
NSLog(@"Void *obj = %@ 地址 = %p", obj,&obj);

[(__bridge id)obj speak];

Sark *sark = [[Sark alloc]init];
NSLog(@"Sark instance = %@ 地址 = %p",sark,&sark);

[sark speak];

}
ViewController = <ViewController: 0x7fff44404ab0> , 地址 = 0x7fff56a48a78
Sark class = Sark 地址 = 0x7fff56a48a50
Void *obj = <Sark: 0x7fff56a48a50> 地址 = 0x7fff56a48a48

my name is halfrost

Sark instance = <Sark: 0x6080000233e0> 地址 = 0x7fff56a48a40
my name is (null)

因爲加了一個字符串,結果輸出就徹底變了,[(__bridge id)obj speak];這句話會輸出「my name is halfrost」

緣由仍是和上面的相似。按viewDidLoad執行時各個變量入棧順序從高到底爲self_cmdself.classselfmyNameobjobj往上偏移32位,就是myName字符串,因此輸出變成了輸出myName了。

20. 排序題:冒泡排序,選擇排序,插入排序,快速排序(二路,三路)能寫出那些?

這裏簡單的說下幾種快速排序的不一樣之處,隨機快排,是爲了解決在近似有序的狀況下,時間複雜度會退化爲O(n^2),雙路快排是爲了解決快速排序在大量數據重複的狀況下,時間複雜度會退化爲O(n^2),三路快排是在大量數據重複的狀況下,對雙路快排的一種優化。

  • 冒泡排序
extension Array where Element : Comparable{
public mutating func bubbleSort() {
let count = self.count
for i in 0..<count {
for j in 0..<(count - 1 - i) {
if self[j] > self[j + 1] {
(self[j], self[j + 1]) = (self[j + 1], self[j])
}
}
}
}
}
  • 選擇排序
extension Array where Element : Comparable{
public mutating func selectionSort() {
let count = self.count
for i in 0..<count {
var minIndex = i
for j in (i+1)..<count {
if self[j] < self[minIndex] {
minIndex = j
}
}
(self[i], self[minIndex]) = (self[minIndex], self[i])
}
}
}
  • 插入排序
extension Array where Element : Comparable{
public mutating func insertionSort() {
let count = self.count
guard count > 1 else { return }
for i in 1..<count {
var preIndex = i - 1
let currentValue = self[i]
while preIndex >= 0 && currentValue < self[preIndex] {
self[preIndex + 1] = self[preIndex]
preIndex -= 1
}
self[preIndex + 1] = currentValue
}
}
}
  • 快速排序
extension Array where Element : Comparable{
public mutating func quickSort() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
var i = left + 1,j = left
let key = self[left]
while i <= right {
if self[i] < key {
j += 1
(self[i], self[j]) = (self[j], self[i])
}
i += 1
}
(self[left], self[j]) = (self[j], self[left])
quickSort(left: j + 1, right: right)
quickSort(left: left, right: j - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
  • 隨機快排
extension Array where Element : Comparable{
public mutating func quickSort1() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
let randomIndex = Int.random(in: left...right)
(self[left], self[randomIndex]) = (self[randomIndex], self[left])
var i = left + 1,j = left
let key = self[left]
while i <= right {
if self[i] < key {
j += 1
(self[i], self[j]) = (self[j], self[i])
}
i += 1
}
(self[left], self[j]) = (self[j], self[left])
quickSort(left: j + 1, right: right)
quickSort(left: left, right: j - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
  • 雙路快排
extension Array where Element : Comparable{
public mutating func quickSort2() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
let randomIndex = Int.random(in: left...right)
(self[left], self[randomIndex]) = (self[randomIndex], self[left])
var l = left + 1, r = right
let key = self[left]
while true {
while l <= r && self[l] < key {
l += 1
}
while l < r && key < self[r]{
r -= 1
}
if l > r { break }
(self[l], self[r]) = (self[r], self[l])
l += 1
r -= 1
}
(self[r], self[left]) = (self[left], self[r])
quickSort(left: r + 1, right: right)
quickSort(left: left, right: r - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
  • 三路快排
// 三路快排
extension Array where Element : Comparable{
    public mutating func quickSort3() {
        func quickSort(left:Int, right:Int) {
            guard left < right else { return }
            let randomIndex = Int.random(in: left...right)
            (self[left], self[randomIndex]) = (self[randomIndex], self[left])
            var lt = left, gt = right
            var i = left + 1
            let key = self[left]
            while i <= gt {
                if self[i] == key {
                    i += 1
                }else if self[i] < key{
                    (self[i], self[lt + 1]) = (self[lt + 1], self[i])
                    lt += 1
                    i += 1
                }else {
                    (self[i], self[gt]) = (self[gt], self[i])
                    gt -= 1
                }

            }
            (self[left], self[lt]) = (self[lt], self[left])
            quickSort(left: gt + 1, right: right)
            quickSort(left: left, right: lt - 1)
        }
        quickSort(left: 0, right: self.count - 1)
    }
}

文末推薦:iOS熱門文集&視頻解析

① Swift

② iOS底層技術

③ iOS逆向防禦

④ iOS面試合集

⑤ 大廠面試題+底層技術+逆向安防+Swift

喜歡的小夥伴記得點贊喔~

收藏等於白嫖,點贊纔是真情ღ( ´・ᴗ・` )ღ

相關文章
相關標籤/搜索