我有一個同事,他既不姓金,也不是司機,但咱們都叫他「金司機」。他跟倉鼠同樣是一個 iOS 工程師,至於叫司機的緣由就不難想到了…… 爲了防止博客被封,在此不舉例子。面試
總之,金司機在這週週會上給組裏同事展現了好幾道他出的「面試題」,成功淘汰了組裏全部同事、甚至包括咱們老大,給平淡的工做帶來了許多歡樂。之因此打引號,是由於這些題只是形式像面試題,其實並不能真的用來面試(並且咱們公司毫不會使用這些題來面試),否則恐怕一我的都招不到了。你們有興趣看看就好,不準噴我同事~swift
代碼是在 command line 環境下執行的,雖然代碼是 swift 寫的,不過 API 都是同樣的,寫 Objective-C 的朋友也能一看就懂。咱們開始吧~安全
在看這組題以前,先問本身一個問題:主線程和主隊列的關係是什麼?bash
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()
複製代碼
執行結果是什麼呢?app
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
呢?async
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 出來會有什麼不一樣呢?oop
這個題 Objective-C 和 swift 會有些不同,因此我提供了兩個版本的代碼:ui
Swift:spa
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 呢?
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 是沒問題的,英語也不是大問題。有興趣的朋友歡迎私信倉鼠,我能夠解答關於工做和麪試的各類問題~ 若是是由於這篇文章帶來的推薦獎,我也會所有轉給個人同事金司機,說到作到:)