前言
開門見山,這篇文章,适合「中進階iOS開發」,如果你現在待業,或者想跳槽并且還在求職的話,可以看看本文,找一找靈感,希望對你們有幫助。
2020年注定是一個特殊且不平凡的一年。*
疫情之下,内憂外患,部分企業,倒下的倒下,扣薪的扣薪……,在這樣的大環境之下,即是危機也是機會,毅然決定踏上求職之路。
起初自信滿滿,在沒有做好充分準備情況之下,履歷寥寥草草,簡簡單單,以至于錯失不少好機會。切記切記!吃一塹長一智。
最後通過優化精簡排版履歷,接到不少網際網路大廠的邀約面試。事實證明,擁有一份好履歷,你已經成功一半了。最終,通過兩個月的艱苦奮戰,終于拿到自己比較滿意的offer。為了做個總結,特開此篇,僅供參考~
我的感受就是,自己一邊梳理知識點,一邊總結歸納,收獲可能更大,是以打算把我梳理的部分分享出來,篇幅有點長,大家見諒。
覆寫的點不是很全,分享給你們,希望你們在金九銀十的招聘季一切順利,offer收割機
1、面試經曆
坐标:深圳,面試公司數:約15家, offer:到手的有2兩個,還有2家也進入談薪階段,談完後就一直沒下文了,表示很郁悶。 面試方式:大部分采用遠端視訊面試,極少現場面試。面試特點:一輪iOS技術面(OC基礎+OC底層+算法), 二輪廣泛技術面(網絡工程+資料結構+算法)+HR面。總體感受,今年面試最大特點是,首先.機會比往年少很多,iOS招聘需求主要集中在3-5年工作經驗(換句話就是說崗位薪資20k普遍是上限,當然大廠除外),其次. 技術方面:OC底層已是必須掌握,Swift極少被提到。
作為一個開發者,有一個學習的氛圍跟一個交流圈子特别重要,這是一個我的iOS交流群:642363427,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿裡面試題、面試經驗,讨論技術, 大家一起交流學習成長!
2、iOS高頻(基礎+底層)面試題
1. 你在開發過程中常用到哪些定時器,定時器時間會有誤差嗎,如果有,為什麼會有誤差?
iOS中常NSTimer、CADisplayLink、GCD定時器,其中NSTimer、CADisplayLink基于NSRunLoop實作,故存在誤差,GCD定時器隻依賴系統核心,相對一前兩者是比較準時的。
誤差原因是:與NSRunLoop機制有關, 因為RunLoop每跑完一次圈再去檢查目前累計時間是否已經達到定時設定的間隔時間,如果未達到,RunLoop将進入下一輪任務,待任務結束之後再去檢查目前累計時間,而此時的累計時間可能已經超過了定時器的間隔時間,故會存在誤差。
參考《iOS常見三種定時器-NSTimer、CADisplayLink、GCD定時器》
2. NSTimer、CADisplayLink會産生循環引用嗎?如果會,你是如何解決的?
如果直接使用,會産生循環引用問題。可以增加一個中間類,給這個類添加一個用weak修飾的id 類型target屬性,并重寫中間類的消息轉發方法。實作如下代碼:
聲明檔案.h:
#import <Foundation/Foundation.h>
@interface LXProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@end
複制
實作檔案.m
#import "LXProxy.h"
@interface LXProxy ()
/** weak target*/
@property (nonatomic, weak) id target;
@end
@implementation LXProxy
+ (instancetype)proxyWithTarget:(id)target{
LXProxy *proxy = [LXProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:self.target];
}
@end
複制
調用代碼:
_timer = [NSTimer scheduledTimerWithTimeInterval:2 target:[LXProxy proxyWithTarget:self] selector:@selector(test) userInfo:nil repeats:YES];
複制
3. 對Runtime有了解嗎,Runtime的方法查找過程是什麼樣的?有哪些實際應用?
runtime是OC動态語言的運作時機制,OC的方法調用最後都轉成了runtime的objc_msgSend函數。
3.1 Runtime消息傳遞:
- 通過雜湊演算法,先從方法緩存中查找,如果命中,調用方法結束流程
- 如果緩存中沒有,則去目前類的方法清單中查找,如果命中,調用方法,加入目前方法緩存中,結束流程
-
如果目前類沒有對應方法,則去逐級父類方法清單中查找,如果命中,調用方法,加入目前方法緩存中,結束流程
4.如果方法都不存在,進入方法動态解析,轉入消息轉發流程。
注:對于已經排序好的方法清單,采用二分查算法查找對應的執行函數,對應沒有排序的清單,采用一般周遊方法查找對應執行函數。
3.2 消息轉發流程:
image.png
- 調用動态解析方法resolveClassMethod:(SEL)sel,如果動态添加方法(調用class_addMethod函數)并傳回YES,則結束流程
- 如果上一步沒有實作動态添加方法,無論傳回Yes還是No,都會調用消息接受者重定向forwardingTargetForSelector方法,如果傳回重定向接受者,則目前流程結束
- 如果傳回上一步nil,則會調用methodSignatureForSelector擷取函數的參數和傳回值類型,同時調用forwardInvocation消息通知目前對象。
- 如果上一步傳回nil,消息無法處理,App crash。
3.3 繼承關系:
e
- 執行個體對象(isntance)的isa指針指向類對象(class),類對象的存放執行個體方法(-方法)
- 類對象(class)的isa指針指向其元類對象(meta), 元類對象存放類方法(+方法)
- 根類對象(root class)的isa指針指向根元類對象(root meta),superclass指針指向nil.
- 根元類對象(root meta)的isa指針指向自己,superclass指針根類對象(root class)
由此可知, 執行個體方法(-方法)查找是沿着其superclass指針逐級父類查找,終于根類對象(root class)。而類方法(+方法)查找是沿着其superclass指針逐級父類(meta)查找,終于根類對象(root class),如果根類對象存在同名執行個體方法,則會調用同名執行個體方法
3.4 Runtime實際運用:
- 給NSTimer定時器聲明一個中間類Proxy(消息轉發)
- 通過rumtime動态擷取類的所有屬性(json轉model、可歸檔類對屬性的歸檔及解歸檔操作)
- 反射機制(NSClassFromString, CTMediator原理)
- 交換系統方法(比如交換viewController生命周期方法,進而進行統一埋點等操作)
- 給分類添加屬性(通過關聯對象,實作getter, setter方法)
4. +load和+initlize調用時機?現在有一個類,給其添加了多個分類,并且每個實作分類都實作了相同的類方法(比如+test),在調用這個方法時,會調用到哪個分類?
- +initialize 方法,會在第一次初始化這個類之前被調用,我們用它來初始化靜态變量。+load 方法會在加載類的時候就被調用,也就是 ios 應用啟動的時候,就會加載所有的類,就會調用每個類的 +load 方法。initialize 方法類似一個懶加載,如果沒有使用這個類,那麼系統預設不會去調用這個方法,且預設隻加載一次,且調用發生在 +init 方法之前。
- 調用最後參與編譯的分類的test方法。原因:Xcode在編譯時根據buildPhases->Compile Sources裡面的從上至下順序編譯的,通過壓棧的方式将多個分類壓棧,且根據後進先出的原則,後編譯的會被先調用(插入頂部添加,即[methodLists insertObject:category_method atIndex:0]。是以objc_msgSend周遊方法清單查找SEL 對應的IMP時,會先找到最後參與編譯的分類)當objc_msgSend找到方法并調用之後,結束傳遞消息,是以就形成了所謂的“覆寫”。
5. App冷啟動優化?
App冷啟動優化方案部落格非常之多,概括總結大緻如下:
- pre-main優化:減少動态靜态庫,合并動态庫,移除廢棄第三方庫及所依賴的系統庫,二進制重排(抖音優化方案)
- runtime對類的注冊,類對象的初始化,load方法加載階段:精簡類,合并分類,移除廢棄分類等等
- main函數之後,推遲對三方庫注冊及延時調用耗時操作函數。可以通過Instruments-->Time Profiler: 性能分析,定位耗時函數
6. UIView和CALayer有了解嗎,UI卡頓原因是什麼,什麼是離屏渲染,為什麼會産生離屏渲染,如何避免觸發離屏渲染?
- UIView和CALayer遵循單一職責原則,UIView負責事件處理,參與響應鍊,為CALayer提供顯示的内容,CALayer負責内容顯示。
- UI卡頓原因:參考
7. 事件響應響應鍊是什麼樣的?touchbegin,button touch,手勢的差別和聯系?
8. 實際開發過程當中,您使用到哪些設計模式?說說單例模式優缺點?蘋果設計的類對象是不是單例模式?
9. 實際開發過程當中,您使用到哪些多線程,GCD與NSOperationQueue有什麼聯系?
10. Runloop響應事件類型,常用幾種mode類型,與GCD有什麼聯系?
11. 說說你對Block的了解,有幾種類型的Block, Block在捕獲自變量,局部靜态變量,全局變量,全局靜态變量有什麼差別, 什麼情況下要注意Block循環引用問題?
12. NSString屬性,使用什麼關鍵字修飾,使用copy和strong修飾,有什麼差別?
13. 什麼是引用計數,說說你對自動釋放池的了解,它是什麼時候釋放的,為什麼用__weak修飾的變量所指向的對象在釋放時會自動把變量指針置為nil?
3、網絡工程面試題
1. HTTPS和HTTP有什麼差別,HTTPS加密過程是什麼樣的,對稱加密和非對稱解密各有什麼優缺點?
HTTPS協定是由SSL/TLS+HTTP協定建構的可進行加密傳輸、身份認證的網絡協定,要比http協定安全
HTTPS協定的主要作用可以分為兩種:一種是建立一個資訊安全通道,來保證資料傳輸的安全;另一種就是确認網站的真實性。
HTTP與HTTPS的差別,詳細介紹
2. TCP和UDP有什麼差別,TCP是可靠傳輸嗎,如果保證其可靠性?
2.1 TCP和UDP差別:
- TCP面向連接配接(如打電話要先撥号建立連接配接);UDP是無連接配接的,即發送資料之前不需要建立連接配接
- TCP提供可靠的服務。也就是說,通過TCP連接配接傳送的資料,無差錯,不丢失,不重複,且按序到達;UDP盡最大努力傳遞,即不保證可靠傳遞
- TCP面向位元組流,實際上是TCP把資料看成一連串無結構的位元組流;UDP是面向封包的UDP沒有擁塞控制,是以網絡出現擁塞不會使源主機的發送速率降低(對實時應用很有用,如IP電話,實時視訊會議等)
- 每一條TCP連接配接隻能是點到點的;UDP支援一對一,一對多,多對一和多對多的互動通信
- TCP首部開銷20位元組;UDP的首部開銷小,隻有8個位元組
-
TCP的邏輯通信信道是全雙工的可靠信道,UDP則是不可靠信道
參考1
2.2 TCP可靠性:
- 校驗和
- 确認應答與序列号
- 逾時重傳
- 連接配接管理
- 流量控制
-
擁塞控制(慢啟動,擁塞避免,快重傳,快恢複)
參考2
3. 如何針對App弱網情況優化
參考:淺談APP弱網優化
4、算法程式設計面試題
1. 判斷一個單向連結清單是否有環?
1.快慢雙指針法,快指針一次走兩步,慢指針一次走一步,如果有環必會相遇
public class ListNode {
public var val: Int
public var next: ListNode?
public init(_ val: Int) {
self.val = val
self.next = nil
}
}
func validedCycleNoded(_ node: ListNode?) -> Bool {
if node == nil {
return false
}
var fast = node, slow = node
while fast != nil {
fast = fast?.next?.next
slow = slow?.next
if fast?.val == slow?.val {
return true
}
}
return false
}
複制
- 可以使用集合(Set)來判斷,來一次周遊,把所有node添加到集合中,如果有重複,則肯定要有環
2. 如何計算二叉樹的高度?
遞歸算法
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init(_ val: Int) {
* self.val = val
* self.left = nil
* self.right = nil
* }
* }
*/
class Solution {
func maxDepth(_ root: TreeNode?) -> Int {
guard let root = root else {return 0;}
return max(maxDepth(root.left), maxDepth(root.right)) + 1
}
}
複制
3. 合并兩個有序數組,同時去重
func mergeSortedArray(_ a: [Int], b:[Int]) ->[Int] {
var i = 0
var j = 0
var ans = [Int]()
//合并數組
while i < a.count && j < b.count {
if a[i] > b[j] {
ans.append(b[j])
j += 1
}else if (a[i] == b[j]) {
ans.append(b[j])
j += 1
i += 1
}else {
ans.append(a[i])
i += 1
}
}
//數組a有未合并元素
while i < a.count {
ans.append(a[i])
i += 1
}
//數組b有未合并元素
while j < b.count {
ans.append(b[j])
j += 1
}
return ans
}
複制
4. 字元串編輯最短距離(LeeCode)
LeeCode-72.編輯距離
解法:動态規劃
5. 判斷括号的有效性(LeeCode)
LeeCode-20.有效的括号
class Solution {
func isValid(_ s: String) -> Bool {
if s.isEmpty {
return true;
}
let map = ["}":"{", ")":"(","]":"["]
var stack = [String]()
for c in s {
let key = String(c)
if key == "{" || key == "(" || key == "[" {
stack.append(key)
} else if !stack.isEmpty && map[key] == stack.last {
stack.removeLast()
} else {
return false
}
}
return stack.isEmpty
}
}
複制代碼
複制
6. 25匹馬,現有5條跑道,沒有計時器,要找出最快3匹馬,至少要跑幾場?
至少跑7場,
- 對25匹馬随機分成5個組(A,B,C,D,E,F),每組跑一場,記錄每一匹馬在目前組中名次(第1名,第2名,第3名)(跑了五場)
- 從各個組中選取名次為第一名的馬組成一組,跑一場,記錄名次(第六場),本組第1名則确定了25匹馬中最快的一匹馬
- 選取第六場中名次為第1名的所在原來組名次為第2、3名馬,選取第六場中名次為第2名的所在原來組名次第1、2名馬(它自己+第2名),選取第六場中名次為第3名所在原來組名次第1名的馬(它自己),組成一組,跑一場,記錄名次(第七場),本場的第1、2名就是25匹馬中最快的第2、3名
7. 8瓶液體,其中1瓶有毒藥,毒藥1小時後至死,請問最快找出毒藥,需要幾隻老鼠?
1隻老鼠可以斷定2瓶液體,2^3=8,是以需要3隻老鼠即可,
對液體進行編号,001,010,011,100,101,110,111
給1号老鼠喂編碼個位數上是1的液體(001,011,101,111),
給2号老鼠喂編碼十位數上是1的液體(010,011,110,111),
給3号老鼠喂編碼百位數上是1的液體(100,101,110,111),
1小時後,
如果老鼠全活, 8号液體有毒,
如果全都死,7号液體有毒,
如果1号死,2,3活, 1号液體有毒
如果2号死, 1,3活,2号液體有毒
如果3号死,1,2活, 4号液體有毒
如果1,2号死,3活, 3号液體有毒
如果1,3号死,2活, 5号液體有毒
如果2,3号死,1活, 6号液體有毒
5、其他面試題
1. 如下結構體,大小是多少?
struct Node {
char a;
int b;
} node;
複制
結構體大小是8,考察結構體特性,記憶體對齊原則。
2. 定義一個全局變量a = 0; 開辟兩條子線程通路 a = a + 1; 各for loop 10次,a的最終結果是多少?
<=20,線程安全問題。
3. 公司員工表(user)中有入職時間(t1)和離職時間(t2),請編寫sql語句,查詢18年3月(date1)-18年6月(date2)所有在職員勞工資訊
select * from user where 入職時間<201806 and (離職時間 is null or離職時間>201803)。
由于時間關系,後面慢慢完善面試題答案。最後借用《三十而已》電視劇的台詞作為結尾:“以上就是我面試遇到的故事,未完待續~~~”
作者:iOS面試總結
連結:https://www.jianshu.com/p/313706ef1f80
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。