7月,iOS求職跳槽的相對較少,能在這個時間段求職的,不是被迫,就是對本身的技術很自信;
針對7月,特別總結了第三份iOS常見大廠面試題(下);前端iOS面試題分爲 上、中、下三部分,方便你們觀看;git
請先本身
答一答
!github
話很少說;直接上題
本文收錄:公衆號【iOS進階寶典《iOS底層面試題(下篇)》】面試13. 如何用Charles抓HTTPS的包?其中原理和流程是什麼?
流程:後端
首先在手機上安裝Charles證書緩存
在代理設置中開啓Enable SSL Proxying服務器
原理:markdown
Charles
做爲中間人,對客戶端假裝成服務端,對服務端假裝成客戶端。簡單來講:網絡
具體流程以下圖:扯一扯HTTPS單向認證、雙向認證、抓包原理、反抓包策略dom
中間人就是截獲到客戶端的請求以及服務器的響應,好比Charles
抓取HTTPS的包就屬於中間人。
避免的方式:客戶端能夠預埋證書在本地,而後進行證書的比較是不是匹配的
1:預編譯:主要處理以「#」開始的預編譯指令。
2:編譯:
詞法分析:將字符序列分割成一系列的記號。
語法分析:根據產生的記號進行語法分析生成語法樹。
語義分析:分析語法樹的語義,進行類型的匹配、轉換、標識等。
中間代碼生成:源碼級優化器將語法樹轉換成中間代碼,而後進行源碼級優化,好比把 1+2 優化爲 3。中間代碼使得編譯器被分爲前端和後端,不一樣的平臺能夠利用不一樣的編譯器後端將中間代碼轉換爲機器代碼,實現跨平臺。
- 目標代碼生成:此後的過程屬於編譯器後端,代碼生成器將中間代碼轉換成目標代碼(彙編代碼),其後目標代碼優化器對目標代碼進行優化,好比調整尋址方式、使用位移代替乘法、刪除多餘指令、調整指令順序等。
3:彙編:彙編器將彙編代碼轉變成機器指令。
靜態連接:連接器將各個已經編譯成機器指令的目標文件連接起來,通過重定位事後輸出一個可執行文件。
裝載:裝載可執行文件、裝載其依賴的共享對象。
- 動態連接:動態連接器將可執行文件和共享對象中須要重定位的位置進行修正。
最後,進程的控制權轉交給程序入口,程序終於運行起來了。
靜態連接是指將多個目標文件合併爲一個可執行文件,直觀感受就是將全部目標文件的段合併。須要注意的是可執行文件與目標文件的結構基本一致,不一樣的是是否「可執行」。
靜態庫:連接時完整地拷貝至可執行文件中,被屢次使用就有多份冗餘拷貝。
- 動態庫:連接時不復制,程序運行時由系統動態加載到內存,供程序調用,系統只加載一次,多個程序共用,節省內存。
優化DNS解析和緩存
對傳輸的數據進行壓縮,減小傳輸的數據
使用緩存手段減小請求的發起次數
使用策略來減小請求的發起次數,好比在上一個請求未着地以前,不進行新的請求
- 避免網絡抖動,提供重發機制
@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, 不管調用者是誰。
因此這個道題的答案就出來了: 兩個打印的都是當前的類。
下面代碼輸出什麼?
@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
和 NSObject
的meta class
是否相等,以前講到meta class
的時候放了一張很詳細的圖,從圖上咱們也能夠看出,NSObject
的meta class
與自己不等。
接着第二次循環判斷NSObject
與meta class
的superclass
是否相等。仍是從那張圖上面咱們能夠看到:Root class(meta)
的superclass
就是 Root class(class)
,也就是NSObject
自己。因此第二次循環相等,因而第一行res1
輸出應該爲YES
。
同理,[Sark class]
執行完以後調用isKindOfClass
,第一次for
循環,Sark
的Meta Class
與[Sark class]
不等,第二次for循環
,Sark
Meta Class的super class
指向的是 NSObject Meta Class
, 和 Sark Class
不相等。
第三次for循環,NSObject Meta Class
的super 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
指向Sark
的Meta Class
,和Sark Class
也不等,因此第二行res2
和第四行res4
都輸出NO。下面的代碼會?**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
, _cmd
, self.class
, self
, obj
。
第一個
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 **<ViewController: 0x7fb570e2ad00>**
。
至此,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
,_cmd
,self.class
,self
,myName
,obj
。obj
往上偏移32位,就是myName
字符串,因此輸出變成了輸出myName
了。
這裏簡單的說下幾種快速排序的不一樣之處,隨機快排,是爲了解決在近似有序的狀況下,時間複雜度會退化爲
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) } }
喜歡的小夥伴記得點贊喔~
收藏等於白嫖,點贊纔是真情ღ( ´・ᴗ・` )ღ