# 面試答案 4.-Grand Central Dispatch(GCD) Serial vs Concurrent ? - DispatchQueue.global(qos: 有哪些等級 1. userInteractive - 類似於主線程,工作是瞬時的 2. userInitiated - 運作時間只有幾秒鐘或更短 3. default - 系統會自行推斷出預設類型 4. utility - 運作需要幾秒到幾分 5. background - 運作需要大量時間,幾分鐘到幾小時 - Qos 队列默认是Serial or Concurrent? 而DispatchQueue.global()預設是非同步 ``` 己實作一個DispatchQueue另外一個需要注意到的是,實做出來的DispatchQueue預設是同步執行的Serial 任務會跑在當前的執行緒,若當前的執行緒為主執行緒,則任務會執行在主執行緒上。 而DispatchQueue.global()預設是非同步 若要將自己實作出來的Queue改成非同步,讓任務在背景執行緒下執行,則必須要加上attributes: .concurrent。 ``` - 若在background Thread要顯示UI需要特別做什麼? DispatchQueue.main - 呼叫主執行緒 5. 說一下 OperationQueue 和 GCD 的區別,以及各自的優勢 [iOS多執行緒程式設計三:Operation和OperationQueue ](https://www.gushiciku.cn/pl/2noY/zh-tw) [Multi-Thread handling - Operation](https://ithelp.ithome.com.tw/articles/10197556) Operation 相比 GCD 而言,Operation 提供了更多對 thread 的操作,像是 dependency、observable、Pause、Cancel 及 Resume。 [How to use multithreaded operations with OperationQueue](https://www.hackingwithswift.com/example-code/system/how-to-use-multithreaded-operations-with-operationqueue) ------------ GCD遵循先進先出,OperationQueue沒有 OperationQueue可以設定依賴關係, GCD 沒有明確依賴關係 比較複雜的 block 嵌套 或是需要頻繁地提交任務 => GCD 任務間有依賴關係或是任務有可能取消 => OperationQueue ### Operation/OperationQueue GCD是一个底层的C API, OperationQueue是基于GCD和队列模型的一个抽象,负责Operation的调度.这意味着我们可以像GCD那样并行的执行任务, 但是以面向对象的方式. GCD和OperationQueue主要差别如下: OperationQueue不遵循FIFO: 相比于GCD, OperationQueue使用起来更加灵活. 在OperationQueue中,我们可以这是Operation之间的依赖, 例如Operation-B任务只有在Operation-A执行完成之后才能开始执行. 这也是OperationQueue不遵循FIFO的原因. OperationQueue并行运行: OperationQueue中的任务(Operation)并行执行, 你不能设置成串行Serial. 但是可以有个变通方法达到串行运行的效果,就是设置依赖, C依赖B, B依赖A; 所以他们运行的顺序就是 A -> B -> C 一个独立的Operation任务是同步运行的, 如果想让Operation异步运行, 你必须将Operation加入到一个OperationQueue. Operation queues 是OperationQueue 的一个实例, 实际上Operation queue是被封装在一个Operation中运行的. [iOS多執行緒之GCD、OperationQueue 對比和實踐記錄](https://www.gushiciku.cn/pl/ptUp/zh-tw) ```javascript! class ContentImportOperation: Operation { let itemProvider: NSItemProvider init(itemProvider: NSItemProvider) { self.itemProvider = itemProvider super.init() } override func main() { guard !isCancelled else { return } print("Importing content..") } } let fileURL = URL(fileURLWithPath: "..") let contentImportOperation = ContentImportOperation(itemProvider: NSItemProvider(contentsOf: fileURL)!) contentImportOperation.completionBlock = { print("Importing completed!") } queue.addOperation(contentImportOperation) // Prints: // Importing content.. // Importing completed! ``` 使用dependancy ```javascript! let fileURL = URL(fileURLWithPath: "..") let contentImportOperation = ContentImportOperation(itemProvider: NSItemProvider(contentsOf: fileURL)!) contentImportOperation.completionBlock = { print("Importing completed!") } let contentUploadOperation = UploadContentOperation() contentUploadOperation.addDependency(contentImportOperation) contentUploadOperation.completionBlock = { print("Uploading completed!") } queue.addOperations([contentImportOperation, contentUploadOperation], waitUntilFinished: true) // Prints: // Importing content.. // Importing completed! // Uploading content.. // Uploading completed! ``` ```javascript! let blockOperation = BlockOperation { print("Executing!") } blockOperation.addExecutionBlock { print("Executing more block!") } blockOperation.completionBlock = { print("BlockOperation completed") let queue = OperationQueue() queue.addOperation(blockOperation) ``` ```javascript! let op1 = BlockOperation.init { print("op1") } let op2 = BlockOperation.init { print("op2")} let op3 = BlockOperation.init { print("op3")} let op4 = BlockOperation.init { print("op4")} op0.addDependency(op1) op1.addDependency(op2) op0.queuePriority = .veryHigh op1.queuePriority = .normal op2.queuePriority = .veryLow op3.queuePriority = .low op4.queuePriority = .veryHigh gOpeQueue.addOperations([op0, op1, op2, op3, op4]) } ``` 6. dispatch_barrier_(a)sync使用? 等前面的block執行完才執行該block 7. 說一下semaphore DispatchSemaphore可以決定一次執行多少非同步執緒, 我們可以設定DispatchSemaphore.init(value: Int), 告訴DispatchSemaphore每次執行多少非同步執行緒, 透過在任務開始時執行DispatchSemaphore.wait(), 告訴其他執行緒自已正在執行任務中,可使用資源-1(value -1)。 再透過完成任務後執行DispatchSemaphore.signal(),告訴其他執行緒任務已完成, 釋出可使用資源(value +1)。 若value為負數,則該執行緒暫停,等待value大於、等於零後,才會繼續執行任務。 8. 說一下defer to be continued 9. Delegate, Notification,KVO (一对一)使用時機 Delegate (代理,委託,協議) 1對1的傳值 支援正向與反向傳值 (引數,返回值) 可以用require和optional來修飾的 Notification (通知,訊息) 1對N (多) 不關心返回值,單向的傳值 NSNotificationCenter單例統一處理髮通知 通過不同的唯一的通知標識名NotificationName區分不同通知 被觀察者主動發出通知 KVO 1對N (多) 只能監聽物件的屬性變化 (比較侷限) 只有通過getters和setters來改變值才會觸發KVO 被觀察者不用新增任何程式碼(比如傳送通知),與被觀察者完全解耦 註冊觀察者時,屬性名都是通過NSString來查詢,容易出錯 KVO的要求是物件必須能支援kvc機制 (NSObject的子類) 單向傳值 10. singleton 如何宣告 ```javascript public class IamSingleton { public static let shared: IamSingleton = IamSingleton() private init(){} } ``` 11. 什麼情況會造成retain cycle ``` javascript class Dog { var owner : Person? init() { print("Dog is built") } deinit { print("Dog deinit") } } class Person { var dog : Dog? init() { print("Person is built") } deinit { print("Person deinit") } } var mark : Person? = Person() var black : Dog? = Dog() mark?.dog = black black?.owner = mark mark = nil ``` 即使Mark變nil, black的owner還是存在 解決辦法 => 在Dog中owner屬加上weak變成弱引用(無前綴字時預設強引用) ```javascript! class Dog { weak var owner : Person? } ``` 12. @escaping 是什麼 ? Block? Weak self ? 如果closure的動作不會馬上執行時會, 又沒加@escaping會complie錯誤 When fetching data over a server, it takes a while for a network request to complete, and thus, the response to arrive. To perform actions on the response, you need to wait for it to arrive. * 範例使用延遲模擬API等待 ```javascript! func howAreYou(_ responseHandler: @escaping (String) -> Void) { print("Hey, how are you?") // you ask how are you // Simulating how it takes 2 seconds for your friend to answer: DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: { responseHandler("Hi, I'm doing really good.") }) print("Responding takes a while...") } howAreYou({ friendResponse in print(friendResponse) // print the response that arrives later }) ``` print結果 `Hey, how are you?` `Responding takes a while...` (兩秒後) `Hi, I'm doing really good.` [出處](https://www.codingem.com/escaping-in-swift/) 13. 如何處理 callback 地獄 => 使用 Promise Kit ### Promise kit ```javascript // 驗證帳號、密碼是否正確 func checkAccountAndPassword(_ account: String, password: String) -> Promise<String> { return Promise { fulfill, reject in Alamofire.request("http://data.taipei/opendata/datalist/apiAccess?scope=resourceAquire&rid=e6831708-02b4-4ef8-98fa-4b4ce53459d9").responseJSON { response in if response.result.isSuccess { // API 服務正常並成功接收到回傳結果 fulfill("userId") } else { // 呼叫API失敗拋出Alamofire的錯誤 // 當呼叫reject閉包則會返回至catch的block reject(response.result.error!) } } } } // 利用userId取得使用者的資料 func getUserDataWithUserName(userId: String) -> Promise<JSON> { return Promise { fulfill, reject in Alamofire.request("http://data.taipei/opendata/datalist/apiAccess?scope=resourceAquire&rid=e6831708-02b4-4ef8-98fa-4b4ce53459d9").responseJSON { response in if response.result.isSuccess { // API 服務正常並成功接收到回傳結果 fulfill(JSON(data: response.data!)) } else { // 呼叫API失敗拋出Alamofire的錯誤 // 當呼叫reject閉包則會返回至catch的block reject(response.result.error!) } } } } ``` ``` javascript firstly { // 先執行某些事再執行檢查帳號、密碼是否正確 UIApplication.shared.isNetworkActivityIndicatorVisible = true // 開啟電池旁邊的indicatior return self.checkAccountAndPassword(account, password: password) } .then { userId in self.getUserDataWithUserName(userId: userId) } .then { uesrData in self.userData = uesrData } .always { UIApplication.shared.isNetworkActivityIndicatorVisible = false // 關閉電池旁邊的indicatior if let userData = self.userData { print(userData) } } .catch { error in print(error) } ``` * firstly 的靈活度可算是最高的,firstly block 可撰寫在執行非同步任務前的一些其它邏輯。 * 第一個 then => 呼叫驗證帳號密碼並在該 func 內呼叫 fulfill 閉包函式後所會執行到的 block,而 userId 則是 fulfill 閉包回傳的值。 * 第二個 then => 呼叫利用使用者 ID 來取得使用者資料並在該 func 內呼叫 fulfill 閉包函式後所會執行到的 block,而 userData 則是 fulfill 閉包回傳的值,將其存放於全域變數。 * done() 告訴 promise 傳遞鏈在這裡結束,而且不存在回傳值;而 then() 總是會有一個回傳值。 * always => 總會被執行的 block,不管前面所有的 API 結果為何一定都會被執行到,就算前面的 API 有執行到 reject 閉包函式一樣會執行到該 block,該 block 為非必要的。 * catch => 有錯誤發生時,即 API 呼叫 reject 的閉包函式。error 可自行定義,後面會說明之。 [出處](https://franksios.medium.com/ios-%E4%BD%BF%E7%94%A8promisekit%E5%84%AA%E9%9B%85%E7%9A%84%E4%BE%86%E8%99%95%E7%90%86callback-hell-%E5%9B%9E%E5%91%BC%E5%9C%B0%E7%8D%84-2d65b574edba) 14. 知道swift enum 擁有儲存相關聯資料的 associated value嗎? ```javascript enum Status { case onTime case delay(min: Int , sec: Int) } let myStatus = Status.delay(min: 2, sec: 10) func checkStatus(status: Status) { switch status { case .onTime : print("Good job") case .delay(let min , let sec): // 等於case let .delayed(min, sec): if min < 10 { print("Be careful") } else { print("Be extra careful") } } } ``` 也可結合 where,直接將相關的比較用 where 串接 ```javascript func checkStatus2(status: Status) { switch status { case .onTime: print("Good job") case let .delayed(min, _) where minute > 1: print("東西收一收, 明天不用來了") default: print("扣薪一百萬") ``` 也可只顯示型態 ```javascript! enum Barcode { case upc(Int, Int, Int, Int) case qrCode(String) } ``` if 判斷 ```javascript if case .note(let note) = checkCellType(rowIndex: index) { note.notes = IdingOrderSession.shared.get(.orderNote) } ``` 15. UI kit collection view 常用嗎? Header view parallax effect 16. 用xib or code or storyboard ? frame 與bounds的差異 ? -widget by swiftUI 有用過嗎? Frame => 相對於 Super View 的位置和大小。 Bounds => 相對於自己的位置和大小 18.推播流程熟悉嗎? a.deviceToken 要怎麼取得? delegate通常放在哪個檔案內? b.arn(Amazon Resource Name)要怎麼取得? c.可以用模擬器測試嗎? 參考文章: [用模擬器](https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E5%95%8F%E9%A1%8C%E8%A7%A3%E7%AD%94%E9%9B%86/%E5%BE%9E%E6%A8%A1%E6%93%AC%E5%99%A8%E6%B8%AC%E8%A9%A6%E6%8E%A8%E6%92%AD-push-notification-c040f1c4d360) [AppCoda教學](https://www.appcoda.com.tw/push-notification/ ) [用FireBase](https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E5%95%8F%E9%A1%8C%E8%A7%A3%E7%AD%94%E9%9B%86/%E5%BE%9E-firebase-console-%E7%99%BC%E9%80%81%E6%8E%A8%E6%92%AD-adaf8b123901) 21.universal link? Deep link? 如何設定? >> 22.XCTest Unit Test 經驗 26. 白版題-生命週期 ![](https://i.imgur.com/Ao8KjLh.png) * Not running 本身有兩種模式:一是 App 未被點擊開啟;二是 App 被使用者或是系統關閉,兩種模式都會處在這種狀態。 * Inactive 當使用者點擊 App 後會進入到此模式。這時候雖然 App 已被啟動,但還是在初始化的階段,任何的點擊行為、或是發送請求等動作都無法使用。舉個例子:很多 App 在點開時會有一個過渡畫面、動畫、進度條等,這些都是 App 處在 Inactive 的階段,這個階段其實是非常短暫的。 * Active 顧名思義就是 App 已將所有 UI 準備就緒,可以開始使用 App 了。處在這個階段的 App 沒有其他的受限條件,且必須能正常的使用。例如:登入、按下按鈕送出、顯示資資料···等。 * Background 本身也有兩種模式:一是使用者直接將 App 關閉,這時會將 App 處在 Background 階段一陣子,之後就轉移到 Suspended 狀態。 第二則是使用者只是將 App 放到背景中;例如按下 Home 鍵、切換不同的 App。此時在這種階段的 App 仍可以被系統呼叫,進行一些像是下載、背景定位、或是通知等行為,但由於已在背景中,這時的 App 應盡最大化地降低它的工作量。 * Suspended 當 App 進入此狀態,就不能再執行任何的程式碼,但它仍然在記憶體中保有一塊位置。當下一次使用者重新打開 App 時,它就會重新回到 Background 狀態、再回到 Inactive、最後回到 Active ,讓它繼續被使用。 [出處](https://medium.com/在程式與旅行的路上/混淆系列-app-的生命週期-app-life-cycle-6ef9c88e9737) 27.access control ![](https://i.imgur.com/HopIqwG.jpg) * Open 存取: 擁有最高的存取權限,即最少的限制,可被同一個 module 的任何檔案存取,或一個不同的 module 存取。 * public 存取: 可被同一個 module 的任何檔案存取,或一個不同的 model 存取。Classes 擁有 public 存取或更多限制的存取層級,只能在其定義的 module 為 subclasses。Classes 的成員擁有 public 存取或更多限制存取層級,只能在其定義的 module 的 subclasses 覆載。 * Internal 存取: 可被其定義的 module 內的任何 sourcr file 所存取,但無法被外部 module 的任何檔案存取。當定義一個應用程式或 framework 的內部結構時,通常使用 internal 存取。 **default值** * File-private 存取: 存取限制為其自定義的 source file。當在整份文件中使用這些詳細內容時,使用 file-private 存取來隱藏特定功能的實現細節。 * Private 存取: 最低存取權限,即最多限制。宣告區塊和同一份檔案的擴展宣告才可存取。 [出處](https://ithelp.ithome.com.tw/articles/10220969)