我有一個同事,他既不姓金,也不是司機,但我們都叫他“金司機”。他跟倉鼠一樣是一個 iOS 工程師,至於叫司機的原因就不難想到了…… 為了防止部落格被封,在此不舉例子。
總之,金司機在這週週會上給組裡同事展示了好幾道他出的“面試題”,成功淘汰了組裡所有同事、甚至包括我們老大,給平淡的工作帶來了許多歡樂。之所以打引號,是因為這些題只是形式像面試題,其實並不能真的用來面試(而且我們公司絕不會使用這些題來面試),不然恐怕一個人都招不到了。大家有興趣看看就好,不許噴我同事~
程式碼是在 command line 環境下執行的,雖然程式碼是 swift 寫的,不過 API 都是一樣的,寫 Objective-C 的朋友也能一看就懂。我們開始吧~
主執行緒與主佇列
在看這組題之前,先問自己一個問題:主執行緒和主佇列的關係是什麼?
第一題
let key = DispatchSpecificKey<String>()
DispatchQueue.main.setSpecific(key: key, value: "main")
func log() {
debugPrint("main thread: \(Thread.isMainThread)")
let value = DispatchQueue.getSpecific(key: key)
debugPrint("main queue: \(value != nil)")
}
DispatchQueue.global().sync(execute: log)
RunLoop.current.run()
複製程式碼
執行結果是什麼呢?
第二題
let key = DispatchSpecificKey<String>()
DispatchQueue.main.setSpecific(key: key, value: "main")
func log() {
debugPrint("main thread: \(Thread.isMainThread)")
let value = DispatchQueue.getSpecific(key: key)
debugPrint("main queue: \(value != nil)")
}
DispatchQueue.global().async {
DispatchQueue.main.async(execute: log)
}
dispatchMain()
複製程式碼
什麼情況下輸出的結果並不是兩個 true
呢?
GCD 與 OperationQueue
第三題
let observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0) { _, activity in
if activity.contains(.entry) {
debugPrint("entry")
} else if activity.contains(.beforeTimers) {
debugPrint("beforeTimers")
} else if activity.contains(.beforeSources) {
debugPrint("beforeSources")
} else if activity.contains(.beforeWaiting) {
debugPrint("beforeWaiting")
} else if activity.contains(.afterWaiting) {
debugPrint("afterWaiting")
} else if activity.contains(.exit) {
debugPrint("exit")
}
}
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, CFRunLoopMode.commonModes)
// case 1
DispatchQueue.global().async {
(0...999).forEach { idx in
DispatchQueue.main.async {
debugPrint(idx)
}
}
}
// case 2
//DispatchQueue.global().async {
// let operations = (0...999).map { idx in BlockOperation { debugPrint(idx) } }
// OperationQueue.main.addOperations(operations, waitUntilFinished: false)
//}
RunLoop.current.run()
複製程式碼
上面 GCD 的寫法,和被註釋掉的 OperationQueue 的寫法,print 出來會有什麼不同呢?
執行緒安全
第四題
這個題 Objective-C 和 swift 會有些不一樣,所以我提供了兩個版本的程式碼:
Swift:
let queue1 = DispatchQueue(label: "queue1")
let queue2 = DispatchQueue(label: "queue2")
var list: [Int] = []
queue1.async {
while true {
if list.count < 10 {
list.append(list.count)
} else {
list.removeAll()
}
}
}
queue2.async {
while true {
// case 1
list.forEach { debugPrint($0) }
// case 2
// let value = list
// value.forEach { debugPrint($0) }
// case 3
// var value = list
// value.append(100)
}
}
RunLoop.current.run()
複製程式碼
使用 case 1 的程式碼會 crash 嗎?case 2 呢?case 3 呢?
Objective-C:
dispatch_queue_t queue1 = dispatch_queue_create("queue1", 0);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", 0);
NSMutableArray* array = [NSMutableArray array];
dispatch_async(queue1, ^{
while (true) {
if (array.count < 10) {
[array addObject:@(array.count)];
} else {
[array removeAllObjects];
}
}
});
dispatch_async(queue2, ^{
while (true) {
// case 1
// for (NSNumber* number in array) {
// NSLog(@"%@", number);
// }
// case 2
// NSArray* immutableArray = array;
// for (NSNumber* number in immutableArray) {
// NSLog(@"%@", number);
// }
// case 3
NSArray* immutableArray = [array copy];
for (NSNumber* number in immutableArray) {
NSLog(@"%@", number);
}
}
});
[[NSRunLoop currentRunLoop] run];
複製程式碼
使用 case 1 的程式碼會 crash 嗎?case 2 呢?case 3 呢?
Runloop
第五題
class Object: NSObject {
@objc
func fun() {
debugPrint("\(self) fun")
}
}
var runloop: CFRunLoop!
let sem = DispatchSemaphore(value: 0)
let thread = Thread {
RunLoop.current.add(NSMachPort(), forMode: .commonModes)
runloop = CFRunLoopGetCurrent()
sem.signal()
CFRunLoopRun()
}
thread.start()
sem.wait()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
CFRunLoopPerformBlock(runloop, CFRunLoopMode.commonModes.rawValue) {
debugPrint("2")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
debugPrint("1")
let object = Object()
object.fun()
// CFRunLoopWakeUp(runloop)
})
}
RunLoop.current.run()
複製程式碼
這樣會輸出什麼呢?
答案
第一題:
"main thread: true"
"main queue: false"
複製程式碼
看到主執行緒上也可以執行其他佇列。
第二題: 這道題要想出效果比較不容易。所以放一張截圖:
看,主佇列居然不在主執行緒上啦!
這裡用的這個 API dispatchMain()
如果改成 RunLoop.current.run()
,結果就會像我們一般預期的那樣是兩個 true
。而且在 command line 環境下才能出這效果,如果建工程是 iOS app 的話因為有 runloop,所以結果也是兩個 true
的。
第三題: GCD:
"entry"
"beforeTimers"
"beforeSources"
"beforeWaiting"
"afterWaiting"
"exit"
"entry"
"beforeTimers"
"beforeSources"
"beforeWaiting"
"afterWaiting"
0
1
2
3
4
...
996
997
998
999
"exit"
"entry"
"beforeTimers"
"beforeSources"
"beforeWaiting"
"afterWaiting"
"exit"
"entry"
"beforeTimers"
"beforeSources"
"beforeWaiting"
複製程式碼
OperationQueue
"entry"
"beforeTimers"
"beforeSources"
"beforeWaiting"
"afterWaiting"
0
"exit"
"entry"
"beforeTimers"
"beforeSources"
"beforeWaiting"
"afterWaiting"
1
"exit"
"entry"
"beforeTimers"
"beforeSources"
"beforeWaiting"
"afterWaiting"
2
"exit"
"entry"
"beforeTimers"
"beforeSources"
"beforeWaiting"
"afterWaiting"
...
複製程式碼
這個例子可以看出有大量任務派發時用 OperationQueue 比 GCD 要略微不容易造成卡頓一些。
第四題:
這個題其實還挺實用的,答案是兩種語言的每個 case 都會 >< [NSArray copy]
那個概率低一點兒,但是稍微跑一會兒還是很容易觸發的。
感謝樓下評論的朋友,補充一句:Objective-C 的第三個 case 跟前兩個 crash 的原因確實是不一樣的,error message 是 release 一個已經 release 的東西。至於為啥會這樣我也不知道,問題應該在 copy 方法的內部實現裡吧。
第五題: 上面的程式碼直接執行出來是
"1"
"<Runloop.Object: 0x102d05be0> fun"
複製程式碼
如果把 object.fun()
改成 object.perform(#selector(Object.fun), on: thread, with: nil, waitUntilDone: false)
的話就能 print 出來 2 了,就是說 runloop 在 sleep 狀態下,performSelector 是可以喚醒 runloop 的,而一次單純的呼叫不行。
有一個細節就是,如果用CFRunLoopWakeUp(runloop)
的話,輸出順序是1 fun 2
而用 performSelector
的話順序是 1 2 fun
。我的朋友騎神的解釋:
perform呼叫時新增的timer任務會喚醒runloop去處理任務。但因為CFRunLoopPerformBlock的任務更早加入佇列中,所以輸出優先於fun
題解
倉鼠本來想厚顏無恥地寫一篇付費文章,然後把題解部分作為付費部分,估計肯定賺一波小錢:)但是因為倉鼠比較菜,心虛怕會說錯,所以我就不提供題解啦~ 歡迎大家在評論區討論吧,我也會放出朋友們的解答連結~~
後記
倉鼠公司也在招人。因為以前寫部落格被噴過,至今心有餘悸;所以怕公司被噴,我不敢說是哪個公司了(有這麼招人的嗎?) 總之就是一個外企網際網路公司,座標北京。大部分 swift,很顯然我的同事和老大技術水平都非常強,倉鼠在這是最菜的。而且大家都特別 nice,公司福利待遇也是業內頂尖水平的。我們的面試題非常注重實操,主要都是現場寫程式碼實現小功能,100% 是平常工作最常使用的,絕不會使用上面這些奇奇怪怪的題,大家可以放心。要求的話,現階段只招比較 senior 的人,基本上要求真的有 4 年以上的經驗,有大廠的經歷或者學校背景好的話會比較好~ 不用太擔心對語言的要求,不會 swift 是沒問題的,英語也不是大問題。有興趣的朋友歡迎私信倉鼠,我可以解答關於工作和麵試的各種問題~ 如果是因為這篇文章帶來的推薦獎,我也會全部轉給我的同事金司機,說到做到:)