複習資料

下載&後臺下載相關

首先了解NSURLSession

NSURLSession中負責下載策略的URLSessionConfiguration

SessionDelegate中負責下載的DownloadSessionDelegate

extension DownloadSessionDelegate: URLSessionDownloadDelegate {
    
	//響應來自遠程服務器的會話級身份驗證請求
	// 如下兩種狀況時會調用該方法
	// 1. 遠程服務器請求客戶端證書
	// 2. 當 session 與使用 SSL 或 TLS 的遠程服務器首次創建鏈接時,使用該方法驗證服務器的證書鏈。
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        let method = challenge.protectionSpace.authenticationMethod
        
        if method == NSURLAuthenticationMethodServerTrust {
            let credential = URLCredential.init(trust: challenge.protectionSpace.serverTrust!)
            completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential)
        }
    }
    
  //urlSession(_:didBecomeInvalidWithError:)方法用以通知 URL session 該 session 已失效。若是經過調用finishTasksAndInvalidate()方法使會話無效,會話會在最後一個 task 完成或失敗後調用該方法;若是經過調用invalidateAndCancel()方法使會話無效,會話當即調用該方法。
    public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
        manager?.didBecomeInvalidation(withError: error)
    }
    
    //當全部事件都已傳遞時,系統會調用URLSessionDelegate協議的urlSessionDidFinishEvents(forBackgroundURLSession:)方法。在該方法內,獲取在上一步保存的 backgroundCompletionHandler 並執行。
    public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        manager?.didFinishEvents(forBackgroundURLSession: session)
    }
    
    //接收進度更新
    public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        guard let manager = manager,
            let currentURL = downloadTask.currentRequest?.url,
            let task = manager.fetchTask(currentURL: currentURL)
            else { return }
        task.didWriteData(bytesWritten: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
    }
    
    //表明一個下載任務已完成下載
    public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        guard let manager = manager,
            let currentURL = downloadTask.currentRequest?.url,
            let task = manager.fetchTask(currentURL: currentURL)
            else { return }
        task.didFinishDownloadingTo(location: location)
    }
    
    //用戶手動結束app,則全部正在下載、已計劃的任務均會取消
    //或則調用了task.cancel()等結束任務。
    public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        guard let manager = manager,
            let currentURL = task.currentRequest?.url,
            let downloadTask = manager.fetchTask(currentURL: currentURL)
            else { return }
        downloadTask.didComplete(task: task, error: error)
    }
}
複製代碼

NSURLSessionTask介紹,以及下載專用的NSURLSessionDownloadTask

NSURLSessionDownloadTask暫停生成用於斷點續傳的resumedata:

後臺下載

  • iOS 原生級別後臺下載詳解
  • 重點:completionHandler的做用: 處理完事件後,執行參數中的塊,以便應用程序能夠拍攝用戶界面的新快照。通常在urlSessionDidFinishEvents函數中調用completionHandler。
  • 準備
    • 在 App 啓動的時候AppDelegate的application(_:didFinishLaunchingWithOptions:)建立Background Sessions,後面會說明緣由。
  • 下載過程當中
    • 當建立了Background Sessions,系統會把它的identifier記錄起來,只要 App 從新啓動後,建立對應的Background Sessions,它的代理方法也會繼續被調用
  • 下載完成
    • 在前臺
      • 跟普通的 downloadTask 同樣,調用相關的 session 代理方法
      • Background Session,若是是任務被session管理,則下載中的 tmp 格式緩存文件會在沙盒的 caches 文件夾裏;
        • 截屏2021-06-15 上午10.25.30.png
      • 若是不被session管理,且能夠恢復,則緩存文件會被移動到 Tmp 文件夾裏;
      • 若是不被Background Session管理,且不能夠恢復,則緩存文件會被刪除。
      • 手動 Kill App 會調用了cancel(byProducingResumeData:)或者cancel,最後會調用urlSession(_:task:didCompleteWithError:)代理方法,能夠在這裏作集中處理,管理 downloadTask,把resumeData保存起來。
        • 截屏2021-06-15 上午10.28.00.png
      • 進入後臺、crash 或者被系統關閉,系統會有另一個進程對下載任務進行管理,沒有開啓的任務會自動開啓,已經開啓的會保持原來的狀態(繼續運行或者暫停),當 App 從新啓動後,建立對應的Background Sessions,可使用session.getTasksWithCompletionHandler(_:)方法來獲取任務,session 的代理方法也會繼續被調用
      • 最使人意外的是,只要沒有手動 Kill App,就算重啓手機,重啓完成後原來在運行的下載任務仍是會繼續下載,實在牛逼
    • 在後臺
      • 當Background Sessions裏面全部的任務(注意是全部任務,不僅僅是下載任務)都完成後,會調用AppDelegate的application(_:handleEventsForBackgroundURLSession:completionHandler:)方法,激活 App
      • 而後跟在前臺時同樣,調用相關的 session 代理方法,didFinishDownloadingTo & didCompleteWithError
      • 最後再調用urlSessionDidFinishEvents(forBackgroundURLSession:)方法
    • crash 或者 App 被系統關閉
      • 當Background Sessions裏面全部的任務(注意是全部任務,不僅僅是下載任務)都完成後,會自動啓動 App,調用AppDelegate的application(_:didFinishLaunchingWithOptions:)方法(這就是爲何要在這個方法中建立Background Sessions)
      • 而後調用application(_:handleEventsForBackgroundURLSession:completionHandler:)方法
      • 當建立了對應的Background Sessions後,纔會跟在前臺時同樣,調用相關的 session 代理方法,最後再調用urlSessionDidFinishEvents(forBackgroundURLSession:)方法
    • 總結
      • 只要不在前臺,當全部任務完成後會調用AppDelegate的application(_:handleEventsForBackgroundURLSession:completionHandler:)方法
      • 只有建立了對應Background Sessions,纔會調用對應的 session 代理方法,若是不在前臺,還會調用urlSessionDidFinishEvents(forBackgroundURLSession:)
  • 下載錯誤
    • 支持後臺下載的 downloadTask 失敗的時候,在urlSession(_:task:didCompleteWithError:)方法裏面的(error as NSError).userInfo可能會出現一個 key 爲NSURLErrorBackgroundTaskCancelledReasonKey的鍵值對,由此能夠得到只有後臺下載任務失敗時纔有相關的信息
  • 最大併發數
    • 支持後臺下載的URLSession的特性,系統會限制併發任務的數量,以減小資源的開銷。同時對於不一樣的 host,就算httpMaximumConnectionsPerHost設置爲 1,也會有多個任務併發下載,因此不能使用httpMaximumConnectionsPerHost來控制下載任務的併發數。
  • 先後臺切換
    • 在 downloadTask 運行中,App進行先後臺切換,會致使urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)方法不調用
    • 解決辦法:使用通知監聽UIApplication.didBecomeActiveNotification,延遲 0.1 秒調用suspend方法,再調用resume方法
  • 緩存文件
    • 前面說了恢復下載依靠的是resumeData,其實還須要對應的緩存文件,在resumeData裏能夠獲得緩存文件的文件名(在 iOS 8 得到的是緩存文件路徑),由於以前推薦使用cancel(byProducingResumeData:)方法暫停任務,那麼緩存文件會被移動到沙盒的 Tmp 文件夾,這個文件夾的數據在某些時候會被系統自動清理掉,因此爲了以防萬一,最好是額外保存一份。

上傳

經過multipart form data上傳的機制和原理

//請求頭
NSString *headerString = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
[request setValue:headerString forHTTPHeaderField:@"Content-Type"];

//請求體
bodyStr = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\" \r\n", name, fileName];
[data appendData:[bodyStr dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:[@"Content-Type: image/png\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:[NSData dataWithContentsOfURL:fileURL]];
[data appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
複製代碼

在multipart基礎上實現分片上傳

headers = ["Content-Range": "bytes=\(UploadManager.share().offset*model.uploadFileChunk!)-\(model.uploadSecretFileSize!)",
                "wnd-token": Defaults.shared.get(for: wndToken) ?? ""]
複製代碼

Tiercel源碼解析

雲盤上傳

  1. 將添加的全部上傳任務建立爲DownloadTask,並保存self.sessionManager.cache.storeUploadTasks(self.waittingUploadArray)
  2. 檢查正在上傳的任務數,將可上傳任務數補充至3個。uploadArray&waittingUploadArray
    • 被暫停的任務從新開始(舊任務)
      • 獲取上傳狀態
        • 1.若是未完成,則執行上傳。
        • 2.已完成上傳的任務,將任務從等待數組中移除,繼續添加任務。
  3. 若是是照片或視頻,則文件拷貝到沙盒。
    • 檢查硬盤空間
    • 上傳以前,將待上傳文件複製到解密區。
  4. 經過密碼中間件計算哈希
    • 網絡請求拿到公鑰
  5. 密鑰轉保護、文件加密
  6. 文件預上傳
    • 秒傳
    • 非秒傳
  7. 將文件分片,讀取第一片上傳
  8. 分片上傳完成後,判斷是否還有分片未上傳
    • 未上傳完,分片加一,從新調用上傳
    • 所有上傳完,繼續添加上傳任務

後臺上傳

iOS項目技術還債之路《一》後臺下載趟坑html

上傳加密流程

下載解密流程

預覽流程

文件存儲流程

iOS底層-第一章 oc對象本質

  • 一個OC對象在內存中是如何佈局的?
    • 結構體、isa指針
  • 一個NSObject對象佔用多少內存?
    • 分配16個字節,使用8個字節(isa指針)
    • 實際上class_getInstanceSize獲取到的大小, 是內存對齊以後的大小, 即全部成員變量中, 佔用內存最大的那個成員變量的倍數, Person內的NSObject_IMPL有一個isa佔用8個字節, _age佔用4個字節, 因此class_getInstanceSize獲取到的是8的倍數, 即16個字節
  • OC中給實例對象分配空間是按照16的倍數遞增的。
    • OC中給實例對象分配空間時, 是按照16, 32, 48, 64, 80, 96...按照16的倍數遞增的, 因此malloc_size函數獲取到的Student實例內存是32

iOS底層-第二章 OC對象的分類

  • 如何獲取類對象?
    • -class
    • +class
    • object_getClass(實例對象)
    • 每一個類在內存中有且只有一個class對象
  • 如何獲取元類對象?
    • object_getClass(類對象)
    • 每一個類在內存中有且只有一個meta-class對象
  • objc_getClass、object_getClass、-class、+class的區別?
    • objc_getClass傳入字符串類名,返回對應的類對象
    • object_getClass傳入instance對象、class對象、meta-class對象,返回class對象、meta-class對象、NSObject(基類)meta-class對象
    • -class、+class返回類對象
  • -class、+class底層實現?
    • return self->isa
    • return self
  • object_getClass(obj)與[obj class]的區別

iOS底層-第三章 isa和superclass

  • OC三種對象的區別?ios

    • instance對象:isa、其餘成員變量
    • class對象和meta-class對象底層結構相同,class的屬性、對象方法、協議、成員變量不爲空,meta-class的類方法不爲空。
  • OC調用方法是發送消息機制c++

    • objc_msgSend(person, sel_registerName("personInstanceMethod"));
  • isa指針的指向是什麼樣的?git

    • instance的isa指向class
      • 當調用對象方法時, 經過instance的isa找到class, 最後找到對象方法的實現進行調用
    • class的isa指向meta-class
      • 當調用類方法時, 經過class的isa找到meta-class, 最後找到類方法的實現進行調用
    • meta-class的isa指向基類的meta-class
  • superclass指針的指向是什麼樣的?github

    • class的superclass會指向父類的class對象, 最後指向的是NSObject的class對象, 而NSObject的class對象中的superclass指針, 會指向nil
    • 若是在發現NSObject的class中也沒有找到要調用的方法時, 就會報錯unrecognized selector sent to instance
    • 基類NSObject的meta-class對象的superclass最終指向的是NSObject的class對象, 而不是指向nil

image.png

iOS底層-第四章 kvo

  • kvo的本質是什麼?
    • 利用runtime動態生成一個子類,而且讓instance對象的isa指向這個全新的子類。
    • 子類命名爲NSKVONotifying_xxx,重寫set、class、superclass、dealloc方法,新增_isKVOA方法。
    • class方法返回原instance對象的類對象。
    • superclass方法指向原instance的類對象。
    • isKVOA返回true。
    • dealloc會增長一些監聽的釋放。
    • set方法新增willChangeValueForKey和didChangeValueForKey,而且在didChangeValueForKey內部會出發監聽器Observer的監聽方法。
  • kvo動態生成的對象結構是什麼樣的?
    • NSKVONotifying_Person的類對象中, 一共有兩個指針isa和superclass, 四個方法setAge:, class, dealloc和_isKVOA
  • 若是直接修改對象的成員變量,是否會出發監聽器?
    • 直接修改對象,不會調用set方法,將不會出發觀察者。
  • 如何手動觸發kvo?
    • 經過調用willChangeValueForKey和didChangeValueForKey方法,能夠手動調用kvo,兩個方法必須同時出現。
    截屏2021-03-26 下午3.49.51.png

iOS底層-第五章 kvc

  • setValue:forKey的原理?
    • 按照setKey、_setkey順序查找方法
    • 沒有找到方法,則查看accessInstanceVariablesDirectly方法返回值,默認爲true。
    • 若爲true,按照_key、_isKey、key、isKey順序查找成員變量,找到了直接賦值。
    • 若爲false或沒有找到成員變量,調用valueForUndefineKey:並跑出異常NSUnknownKeyException

截屏2021-03-26 下午4.08.48.png

  • valueForKey的原理?
    • 按照getKey、key、isKey、_key順序查找方法
    • 沒有找到方法,則查看accessInstanceVariablesDirectly方法返回值,默認爲true。
    • 若爲true,按照_key、isKey、key、isKey順序查找成員變量,找到了直接取值。
    • 若爲false或沒有找到成員變量,調用valueForUndefineKey:並跑出異常NSUnknownKeyException

截屏2021-03-26 下午4.09.37.png

  • kvc賦值時,會觸發kvo嗎?
    • 使用KVO給屬性或成員變量賦值時, 都會觸發KVO, 系統會自動調用willChangeValueForKey:和didChangeValueForKey:兩個方法

iOS底層-第六章 category

  • category的實現原理?面試

    • category編譯以後的底層結構是struct category_t,裏面存儲着分類的類名, 實例方法列表, 類方法列表, 協議列表和屬性列表。
    • 在程序運行的時候,runtime會將Category的數據,合併到類信息中(類對象、元類對象中)
      • 首先從新分配內存,能夠足夠放下類和Category中左右的方法數據。
      • 接着將類中原有的方法地用到方法列表的最右邊。
      • 最後將Category中的方法copy到方法列表的最前面。
      • 屬性和協議也是一樣的方法。
      • 此時,Category的方法就放在了方法列表中的前面,而類中的原有方法則存也在於方法列表的最後面。
      • 若是調用類的方法,則會從方法列表中從前日後查找,而若是Category中有相同的方法,那麼會直接使用Category中的方法。
  • Category和Class Extension的區別?正則表達式

    • class Extension在編譯的時候,他的數據就已經包含在類信息中。
    • Category在運行時,纔會將數據合併到類信息中。

iOS底層-第七章 load & initialize

  • Category中有load方法, load方法會在runtime加載類的時候調用
    • 類的load方法調用早於Category中的load方法, 調用子類的load方法以前, 會先調用父類的load方法
    • 沒有關係的類會根據編譯順序調用load方法, Category會根據編譯順序調用load方法
    • 全部的類和分類, load方法只會調用一次
  • 當一個類在查找方法的時候, 會先判斷當前類是否初始化, 若是沒有初始化就會去掉用initialize方法
    • 若是這個類的父類沒有初始化, 就會先調用父類的initialize方法, 再調用本身的initialize方法
    • 若是該類沒有實現initialize方法,會執行父類的initialize方法。
    • 類在調用initialize時, 使用的是objc_msgSend消息機制調用
  • load和initialize的區別是什麼?
    • load & initialize 調用時機
      • load 加載類、分類時調用
      • initialize 類第一次接收到消息時調用
    • load & initialize 調用方式
      • load 根據函數地址直接調用
      • initialize 經過objc_msgsend調用
    • load & initialize 調用順序
      • load 父類load->子類load->分類load,先編譯的類優先調用load
      • initialize 父類init->子類init(子類未實現則調用其父類的方法,因此父類的initialize方法可能會調用屢次)
  • 經過代碼調用load會怎樣?
    • 會根據消息傳遞機制經過該實例的類對象查找load函數。可是通常狀況下不會主動調用load方法, 都是讓系統自動調用。

iOS底層-第八章 關聯對象

  • Category爲何不能存放成員變量?
    • 由於category_t的底層結構中,只有存放實例方法、類方法、協議、實例屬性、類屬性的list,沒有存放成員變量的list。
  • 如何在category中存放成員變量?
    • 能夠經過關聯對象,關聯對象並非存儲在被關聯的對象內存中,而是存儲在全局的統一的一個AssociationsManager中。

iOS底層-第九章 block的底層結構

  • block的三種類型?
    • GlobalBlock
      • 存在於內存的數據區域(.data區)
      • 內部沒有使用auto類型變量的block, 就是__NSGlobalBlock__類型
      • __NSGlobalBlock__類型的block調用copy後類型不變, 仍是__NSGlobalBlock__類型(還在數據區)
    • StackBlock
      • 存在於內存的棧區
      • 內部使用了auto類型變量的block, 就是__NSStackBlock__類型
    • MallocBlock
      • 存在於內存的堆區
      • __NSStackBlock__類型的block調用copy後就是__NSMallocBlock__類型, 經過copy, 將block從棧區複製到了堆區
      • __NSMallocBlock__類型的block調用copy後類型不變, 仍是__NSMallocBlock__類型(不會生成新的block, 原有引用計數+1)
  • block對auto、static、全局變量捕獲方式

  • 全局靜態變量全局變量的區別, 全局靜態變量是有做用域的, 只能夠被聲明的.h .m / .c 中訪問到, 全局變量是沒有做用域的,不管在任何地方,引入一下 Test.h,即可以得到並使用全局變量。

內存分佈瞭解一下

  • 棧區:內存管理由系統控制,存儲的爲非靜態的局部變量,例如:函數參數,在函數中生命的對象的指針等。當系統的棧區大小不夠分配時,系統會提示棧溢出。算法

  • 堆區:內存管理由程序控制,存儲的爲malloc , new ,alloc出來的對象。數據庫

    • 若是程序沒有控制釋放,那麼在程序結束時,由系統釋放。但在程序運行過程當中,會出現內存泄露、內存溢出問題。分配方式相似於鏈表
  • 全局存儲區(靜態存儲區):全局變量、靜態變量會存儲在此區域。事實上全局變量也是靜態的,所以,也叫全局靜態存儲區。編程

    • 存儲方式: 初始化的全局變量跟靜態變量放在一片區域,未初始化的全局變量與靜態變量放在相鄰的另外一片區域。
    • 程序結束後由系統釋放。
  • 文字常量區:在程序中使用的常量存儲在此區域。程序結束後,由系統釋放。在程序中使用的常量,都會到文字常量區獲取。

  • 程序代碼區:存放函數體的二進制代碼。

    • 運行程序就是執行代碼,代碼要執行就要加載進內存。
  • ARC環境下, block的類型問題

    • __NSStackBlock__類型的block作爲函數返回值時, 會將返回的block複製到堆區
    • 將__NSStackBlock__類型的block賦值給__strong指針時, 會將block複製到堆區
    • 若是沒有__strong指針引用__NSStackBlock__類型的block, 那麼block的類型依然是__NSStackBlock__
    • block做爲Cocoa API中方法名含有usingBlock的方法參數時, block在堆區
    • block做爲GCD API的方法參數時, block在堆區
  • 對象類型的auto變量捕獲

    • 棧中的block不會將對象類型的auto變量進行retain處理, 只有在將block複製到堆上時, 纔會將對象類型的auto變量進行retain處理(引用計數+1)
    • 當堆中的block釋放時, 會對其中的對象類型的auto變量進行release處理(引用計數-1), 若是此時對象類型的auto變量的引用計數爲零, 就會被釋放
    • 當block被複制到堆上時,會調用__main_block_copy_0函數, 來對捕獲的對象類型的auto變量進行強引用
    • 當block從堆上移除時, 又會被調用__main_block_dispose_0函數, 對捕獲的對象類型的auto變量解除強
  • __weak修飾符

    • 當block捕獲到的對象類型的auto變量被__weak修飾時, 即使block被複制到了堆上, __main_block_copy_0方法也不會對被捕獲的對象類型的auto變量進行強引用

iOS底層-第十章 __block和block內存管理

  • block內部修改外部變量的值

    • static修飾的變量, 在block內能夠修改變量的值,由於在底層block捕獲的是地址
    • 全局變量能夠直接在block中修改值,block不會捕獲全局變量, 而是直接使用, 因此能夠直接改值
    • __block修飾的auto變量
  • __block修飾auto變量

    • auto變量被包裝爲一個結構體對象
    • 結構體中包含auto變量的地址(__forwarding)和值
    • 當block調用時,會經過age->__forwarding->age找到__Block_byref_age_0中的成員變量, 並修改值
  • block變量的內存管理

    • 基本數據類型的auto變量,當棧上block複製到堆上時, 會直接將捕獲的基本數據類型變量複製到堆中
    • 對象類型的auto變量,當變量被強引用修飾時, block複製到堆上的過程當中會調用copy函數, copy函數內部會調用裏面的_Block_object_assign函數, 對被捕獲的對象變量進行強引用
    • 對象類型的auto變量,當對象變量被__weak修飾時, block從棧中複製到堆中, 依然會調用copy函數, copy函數內部會調用裏面的_Block_object_assign函數,只不過不會再對被__weak修飾的變量進行強引用
    • __block修飾的auto變量,在底層會被包裝成一個__Block_byref_age_0對象,當block從棧上覆制到堆上時, 就會調用__main_block_desc_0中的copy函數, 對__Block_byref_age_0對象進行強引用,移除時會調用dispose函數,移除強引用。
  • __block的__forwarding指針

    • 當block在棧上時, 經過__forwarding指針拿到的是棧中的__block結構體
    • 當block在堆上時, 經過__forwarding指針拿到的是堆中的__block結構體
  • 被__block修飾的對象類型

    • 在block成員變量__main_block_desc_0結構體中的copy和dispose是用來處理person到struct __Block_byref_person_0之間連線的
    • 而__block對象中的copy和dispose, 是用來處理struct __Block_byref_person_0中__strong person到[[Person alloc] alloc]對象之間連線的
  • __block + __strong/__weak

    • __block修飾__strong類型的對象類型變量, 會對對象類型變量進行強引用
    • __block修飾__weak類型的對象類型變量, 會對對象類型變量進行弱引用
    • MRC下, __block修飾的__strong類型的對象類型變量, 在block複製到堆上時,不會進行retain處理
    • 總結:
      • 當__block變量在棧上時,不會對指向的對象產生強引用
      • 當__block變量被copy到堆時,會調用__block變量內部的copy函數,copy函數內部會調_Block_object_assign函數,_Block_object_assign函數會根據所指向對象的修飾符(__strong、__weak、__unsafe_unretained)作出相應的操做,造成強引用(retain)或者弱引用(注意:這裏僅限於ARC時會retain,MRC時不會retain。
  • 循環引用

    • __weak: 當person被釋放時, block中的__weak person會指向nil
    • __unsafe_unretained: 當person被釋放時, block中的__unsafe_unretained person不會指向nil, 形成野指針
    • __block: 手動設置nil

iOS底層-第十一章 isa詳解

  • isa指針的變化
    • 在arm64架構以前,isa就是一個普通的指針,存儲着Class、Meta-Class對象的內存地址
    • 從arm64架構開始,對isa進行了優化,變成了一個共用體(union)結構,還使用位域來存儲更多的信息
    • bits佔用一個字節,而結構體使用了位域, 也只佔一個字節, 因此共用體只佔用一個字節
  • 什麼是聯合體,位域?
  • isa位域解釋

iOS底層-第十二章 Class結構

  • class的底層結構
    • 一開始class_data_bits_t bits;指向ro, 在加載的過程當中建立了rw, 此時的指向順序是bits->rw->ro
  • class_rw_t結構
    • class_rw_t裏面的methods、properties、protocols是二維數組,是可讀可寫的,包含了類的初始內容、分類的內容
  • method_t結構
    • method_t是對方法\函數的封裝
  • cache_t結構
    • Class內部結構中有個方法緩存(cache_t),用散列表(哈希表)來緩存曾經調用過的方法,能夠提升方法的查找速度
  • 方法查找的邏輯
    • 經過isa找到class
    • 先會先從cache中查找, 若是有方法, 直接調用
    • 若是沒有方法, 在自身的class->bits->rw中查找方法
    • 若是找到方法直接調動,並將方法緩存到cache中
    • 若是沒有找到, 會經過superclass找到父類, 從父類->bits->rw中查找方法
    • 若是在父類中找到方法, 就直接調用, 同時將方法存到本身(不是父類的)的cache中, 若是一直找不到, 就進入下一個階段
  • 散列表的存儲方法
    • 一開始, 系統會分配給cache_t->_buckets一段內存, 假設第一次分配了足夠存儲3個方法的內存
    • 此時cache_t的mask等於2, 即_buckets的長度 - 1
    • 當存儲方法時, 會用SEL & mask, 獲取到一個數字, 用這個數字作爲索引, 將該方法存儲到_buckets中
    • 當一個Student實例調用learning方法時, 就會用@selector(learning) & _buckets.mask來獲取存儲的索引,而後將learning方法存放到Student類對象的cache中
    • 假設獲取到的索引值爲0, 那麼散列表的結構相似下圖
    • 若是Student實例調用父類Person的eat方法時, 根據@selector(eat) & _buckets.mask的結果將becket_t插入到相應位置
    • 假設@selector(eat) & _buckets.mask結果是2, 那麼散列表結構相似下圖
    • 即便eat是Person中的方法, 可是Student調用,也會存到Student的cache中
    • 當多個數都&一個固定值時, 那麼確定就有重複的可能出現
    • 此時,存儲方法的索引就會-1, 而後在查找-1後所在位置是否空缺, 若是有空缺就會存儲
    • 若是Student實例調用exercises方法, 會計算@selector(exercises) & _buckets.mask的結果做爲索引值
    • 假設@selector(exercises) & _buckets.mask的結果是2, 此時由於索引2的位置已經存儲eat方法, 因此索引會-1變成1, 而後查看1的位置是否空缺, 若是空缺就會存儲, 以下圖
    • 若是Student實例繼續調用dancing方法, 此時cache->buckets已經存儲滿
    • 那麼buckets的容量會擴大一倍, 容量變爲6,從新計算cache->mask的值爲5, 接着清空buckets中以前緩存的全部方法
    • 而後計算@selector(dancing) & _buckets.mask的值, 若是此時的結果是3, 那麼散列表的結構相似下圖
    • 由於已經清空過cache中的全部方法, 因此此時只存儲dancing方法

iOS底層-第十三章 消息發送

  • objc_msgSend
    • OC中調用方法, 會使用objc_msgSend函數給消息接收者發送消息,因此OC調用方法的過程也被稱爲消息機制
  • 消息發送流程
    • 當消息接收者爲空時, 直接返回, 結束objc_msgSend函數的調用
    • 當消息接收者有值時, 查看緩存
    • 若是方法沒有被緩存過, 就會查詢方法列表
    • 當緩存中沒有找到須要調用的方法時, 就會在方法列表中查找, 若是找到就會存到緩存cache中
    • 方法存在cls->rw->methods中, 而methods是個二位數組, 因此須要進行遍歷查詢, 這裏先拿到一維數組調用search_method_list函數查詢
    • 在一維數組中查找方法, 這裏有兩種狀況
      • 第一種: 方法列表已經排好序, 會經過findMethodInSortedMethodList函數查找,findMethodInSortedMethodList函數使用的是二分查找的方式查詢方法
      • 第二種: 方法列表沒有排好序, 會一個一個遍歷查找
    • 當找到方法後, 會先將方法存儲到cache中
    • 若是在本身的類對象中沒有找到須要調用的方法, 就會去查找父類中是否有該方法
      • 1.查找時會一層一層遍歷全部父類, 只要某個父類中找到方法, 就會結束查找
      • 2.先從父類的緩存中找, 若是找到, 會先存到本身的cache中
      • 3.若是父類的緩存中沒有該方法, 就會從父類的方法列表中查找, 若是找到就會存入到本身的cache中, 並不會存入到父類的cache中
      • 4.若是沒找到, 就會經過for循環查看父類的父類中有沒有方法, 依次類推, 只要找到就會結束查詢, 並存到本身的cache中
    • 若是最後仍是沒找到, 就會進入下一個階段, 動態解析階段

iOS底層-第十四章 動態方法解析

  • 動態方法解析流程
    • 當消息發送過程當中,沒有找到要調用的方法時, 就會進入動態方法解析階段,
    • 在動態方法解析過程當中, 會根據類對象和元類對象進行判斷, 分別處理
      • 類對象調用resolveInstanceMethod:方法
      • 元類對象調用resolveClassMethod:方法
    • 咱們能夠在+ (BOOL)resolveInstanceMethod:(SEL)sel方法中, 使用Runtime添加其餘方法的實現

iOS底層-第十五章 消息轉發

  • 消息轉發流程
    • 當動態方法解析也沒有找到須要調用的方法實現時, 就會進入消息轉發階段。調用的是forwardingTargetForSelector:方法
    • 能夠在+forwardingTargetForSelector:方法中設置消息轉發的對象
    • 由於objc_msgSend的原理是給 消息接收者 發送 一條消息, 而這個消息是SEL類型的,而且不分是類方法(+)仍是對象方法(-)
    • 因此, 咱們能夠將消息接收者設置爲實例對象
    • 若是不實現+forwardingTargetForSelector:方法, 就會調用+methodSignatureForSelector:方法, 並調用+forwardInvocation:方法
    截屏2021-03-29 下午2.53.34.png 截屏2021-03-29 下午2.54.49.png

iOS底層-第十六章 super

  • super的結構
    • super的底層調用了objc_msgSendSuper方法, 並傳入兩個參數
      • __rw_objc_super: 結構體
        • receiver: 消息接收者
        • super_class: 從super_class開始查找調用的方法
      • sel_registerName("run"): 方法SEL
      struct objc_super {
         __unsafe_unretained _Nonnull id receiver;
         __unsafe_unretained _Nonnull Class super_class;
      };
      複製代碼
    • 從實際代碼中能夠看到, 這兩個成員變量分別傳入了self和[Person class]
    • 因此消息接收者是self, 從[Person class]中開始查找方法
    • 總結:
      • super的含義是, 查詢方法的起點是父類, 不是自己的類對象
      • 消息接收者是self, 不是父類對象
      • 發送的消息是調用的方法

iOS底層-第十七章 Runloop基本認識

  • runloop與線程的關係

    • 每條線程都有惟一的一個與之對應的RunLoop對象
    • RunLoop保存在一個全局的Dictionary裏, 線程做爲key, RunLoop對象作爲Value
    • 線程剛建立時並無RunLoop對象, RunLoop會在第一次獲取它時建立
    • RunLoop會在線程結束時銷燬
    • 主線程的RunLoop已經自動獲取(建立), 子線程默認沒有開啓RunLoop
  • runloop底層結構

    • CFRunLoopRef 結構
    typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef; struct __CFRunLoop {
    	pthread_t _pthread;
    	CFMutableSetRef _commonModes;
    	CFMutableSetRef _commonModeItems;
    	CFRunLoopModeRef _currentMode;
    	CFMutableSetRef _modes;
    };
    複製代碼
    • _modes中存放的是CFRunLoopModeRef類型數據, 其中就有_currentMode, 只不過_currentMode是當前使用的mode
    • CFRunLoopModeRef 結構
    typedef struct __CFRunLoopMode *CFRunLoopModeRef; struct __CFRunLoopMode {
    	CFStringRef _name;
    	CFMutableSetRef _sources0;
    	CFMutableSetRef _sources1;
    	CFMutableArrayRef _observers;
    	CFMutableArrayRef _timers;
    };
    複製代碼
  • CFRunLoopModeRef

    • CFRunLoopModeRef表明RunLoop的運行模式
    • 一個RunLoop包含若干個Mode,每一個Mode又包含若干個Source0/Source1/Timer/Observer
    • RunLoop啓動時只能選擇其中一個Mode,做爲currentMode
    • 若是須要切換Mode,只能退出當前Loop,再從新選擇一個Mode進入
    • 不一樣組的Source0/Source1/Timer/Observer能分隔開來,互不影響
    • 若是Mode裏沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出
    • 常見的兩種CFRunLoopModeRef
      • kCFRunLoopDefaultMode(NSDefaultRunLoopMode): App的默認Mode,一般主線程是在這個Mode下運行
      • UITrackingRunLoopMode: 界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其餘 Mode 影響
  • CFRunLoopModeRef各屬性做用

    • Source0
      • 觸摸事件處理
      • performSelector:onThread:
    • Source1
      • 基於Port的線程間通訊
      • 系統事件捕捉
    • Timers
      • NSTimer
      • performSelector:withObject:afterDelay:
    • Observers
      • 用於監聽RunLoop的狀態
      • UI刷新(BeforeWaiting)
      • Autorelease pool(BeforeWaiting)
  • mode的切換

    • 滾動以前, 先退出kCFRunLoopDefaultMode, 進入UITrackingRunLoopMode
    • 等滾動結束, 會先退出UITrackingRunLoopMode, 進入kCFRunLoopDefaultMode
  • 如何保活

    • 每一條線程都有與之相對應的惟一一個RunLoop, 只有在主動獲取RunLoop時纔會建立(主線程中的RunLoop由系統自動建立)
    • 固然, 咱們在使用RunLoop對線程進行保活的時候, 不能僅僅運行就好了, 由於RunLoop當前執行的_currentModel中若是沒有Sources0, Sources1, Timers, Observers, 那麼RunLoop會自動退出
    • 因此咱們須要建立一個事件讓RunLoop處理, 這樣RunLoop纔不會退出

  • 運行流程

  • runloop休眠和工做的切換

  • RunLoop中的source0、source1

  • iOS Touch Event from the inside out(該文章下有一篇講解mach機制的文章)

iOS底層-第十八章 多線程的安全隱患(鎖)

  • iOS中有哪些鎖

    • OSSpinLock
      • 自旋鎖,等待鎖的線程會處於忙等(busy-wait)狀態,一直佔用着CPU資源
      • 目前已經再也不安全,可能會出現優先級反轉問題
      • 可能出現低優先級的線程先加鎖,可是CPU更多的執行高優先級線程, 此時就會出現相似死鎖的問題
    • os_unfair_lock
      • 互斥鎖,用於取代不安全的OSSpinLock, 從iOS10開始才支持
      • 線程會處於休眠狀態, 並不是忙等
    • pthread_mutex
      • mutex叫作互斥鎖,等待鎖的線程會處於休眠狀態
      • 設置pthread初始化時的屬性類型爲PTHREAD_MUTEX_RECURSIVE, 這樣pthread就是一把遞歸鎖
      • 遞歸鎖容許同一線程內, 對同一把鎖進行重複加鎖
    • dispatch_semaphore
      • 信號量
      • 使用dispatch_semaphore_t設置信號量爲1, 來控制贊成之間只有一條線程能執行
    • dispatch_queue(DISPATCH_QUEUE_SERIAL)
      • 同步隊列解決多線程隱患
    • NSLock
      • 基於pthread封裝的OC對象
      • NSLock是基於pthread封裝的normal鎖
    • NSRecursiveLock
      • 遞歸鎖
      • 基於pthread封裝的OC對象
      • 基於pthread_mutex封裝的遞歸鎖
    • NSCondition
      • 基於pthread封裝的OC對象
      • 基於pthread_cond & pthread_mutex封裝的條件鎖
    • NSConditionLock
      • 基於pthread封裝的OC對象
      • NSConditionLock是對NSCondition的進一步封裝
    • @synchronized
      • @synchronized是對mutex遞歸鎖的封裝
      • @synchronized(obj)內部會生成obj對應的遞歸鎖,而後進行加鎖、解鎖操做
      • 底層是經過os_unfair_recursive_lock封裝的鎖
  • 什麼是優先級反轉

    • 假設經過OSSpinLock給兩個線程thread1thread2加鎖
    • thread優先級高, thread2優先級低
    • 若是thread2先加鎖, 可是尚未解鎖, 此時CPU切換到thread1
    • 由於thread1的優先級高, 因此CPU會更多的給thread1分配資源, 這樣每次thread1中遇到OSSpinLock都處於使用狀態
    • 此時thread1就會不停的檢測OSSpinLock是否解鎖, 就會長時間的佔用CPU
    • 這樣就會出現相似於死鎖的問題
  • iOS鎖的性能

    • 性能從高到低排序
    • os_unfair_lock
    • OSSpinLock
    • dispatch_semaphore
    • pthread_mutex
    • dispatch_queue(DISPATCH_QUEUE_SERIAL)
    • NSLock
    • NSCondition
    • pthread_mutex(recursive)
    • NSRecursiveLock
    • NSConditionLock
    • @synchronized
  • 自旋鎖、互斥鎖比較

    • 什麼狀況使用自旋鎖比較划算?
      • 預計線程等待鎖的時間很短
      • 加鎖的代碼(臨界區)常常被調用,但競爭狀況不多發生
      • CPU資源不緊張
      • 多核處理器
    • 什麼狀況使用互斥鎖比較划算?
      • 預計線程等待鎖的時間較長
      • 單核處理器
      • 臨界區有IO操做
      • 臨界區代碼複雜或者循環量大
      • 臨界區競爭很是激烈

iOS底層-第十九章 atomic

  • 基礎概念
    • atomic用於保證屬性setter、getter的原子性操做,至關於在getter和setter內部加了線程同步的鎖
    • 它並不能保證使用屬性的過程是線程安全的
    • atomic底層使用的是os_unfair_lock(性能最高)

iOS底層-第二十章 文件的讀寫安全

  • 多讀單寫方案
    • pthread_rwlock
    • dispatch_barrier_async
      • 這個函數傳入的併發隊列必須是本身經過dispatch_queue_cretate建立的
      • 若是傳入的是一個串行或是一個全局的併發隊列,那這個函數便等同於dispatch_async函數的效果
      • 爲何讀用dispatch_sync,寫用dispatch_async
      image.png

iOS底層-第二十一章 內存管理-定時器

  • CADisplayLink
    • 使用頻率和屏幕的刷新頻率保持一致, 60FPS
  • NSTimer
    • NSTimer依賴於RunLoop,若是RunLoop的任務過於繁重,可能會致使NSTimer不許時
  • GCD定時器
    • GCD定時器不依賴於RunLoop, 會更加的準時

iOS底層-第二十二章 Tagged Pointer

iOS - 老生常談內存管理(五):Tagged Pointer

iOS 內存管理之 Tagged Pointer

聊聊僞指針 Tagged Pointer

  • iOS程序的內存佈局
  • Tagged Pointer
    • 從64bit開始,iOS引入了Tagged Pointer技術,用於優化NSNumber、NSDate、NSString等小對象的存儲
    • 在沒有使用Tagged Pointer以前, NSNumber等對象須要動態分配內存、維護引用計數等,NSNumber指針存儲的是堆中NSNumber對象的地址值
    • 使用Tagged Pointer以後,NSNumber指針裏面存儲的數據變成了: Tag + Data,也就是將數據直接存儲在了指針中
    • 當指針不夠存儲數據時,纔會使用動態分配內存的方式來存儲數據
    • objc_msgSend能識別Tagged Pointer,好比NSNumber的intValue方法,直接從指針提取數據,節省了之前的調用開銷

iOS底層-第二十三章 OC對象的內存管理

iOS SideTable

iOS開發-weak引用以及sidetable表

  • 引用計數
    • 一個新建立的OC對象引用計數默認是1,當引用計數減爲0,OC對象就會銷燬,釋放其佔用的內存空間
    • 調用retain會讓OC對象的引用計數+1,調用release會讓OC對象的引用計數-1
    • 當調用alloc、new、copy、mutableCopy方法返回了一個對象,在不須要這個對象時,要調用release或者autorelease來釋放它
    • 想擁有某個對象,就讓它的引用計數+1;不想再擁有某個對象,就讓它的引用計數-1
  • MRC下的setter方法
- (void)setName:(NSString *)name {
    if (_name != name) {
        [_name release];
        _name = [name retain];
    }
}
複製代碼
  • copy和mutableCopy
    • 深拷貝: 產生一個新的副本, 與源對象相互獨立
    • 淺拷貝: 指針拷貝, 指向源對象
    • 自定義對象的拷貝須要實現NSCopying協議
  • 引用計數的存儲
    • 若是指針是Tagged Pointer, 那麼直接返回, 不然進入下一步
    • 判斷isa是否優化過
      • 若是優化過, 那麼最後isa的最後19位存儲的是引用計數
      • 若是isa沒有優化過, 那麼就會進入sidetable_retainCount函數, 獲取sidetable中的引用計數
      • sidetable 使用的是spinLock_t
    • 若是最後19位不足以存儲, 那麼多餘的引用計數會存儲到sidetable中, 同時將倒數第20位的值置爲1, 就是has_sidetable_rc的值爲1
    • 若是has_sidetable_rc的值爲1, 就會從sidetable_getExtraRC_nolock函數中取出sidetable中存儲的引用計數
  • dealloc函數原理
    • isa是優化過的指針, 對象沒有被弱引用, 沒有關聯對象, 沒有c++析構函數, 沒有將引用計數存到Sidetable中, 就會當即釋放
    • 不然調用object_dispose函數
    • 進入object_dispose函數, 能夠看到調用了objc_destructInstance函數
    • 進入objc_destructInstance函數, 能夠看到對objc的處理, 是在clearDeallocating函數中將弱指針置爲nil的
    • 進入clearDeallocating函數, 又能夠看到兩種狀況
      • 對象的isa沒有優化過
        • 當isa沒有被優化過, 進入sidetable_clearDeallocating函數, 能夠看到weak引用是存放到SideTable中的
        • 存放在了SideTable的weak_table_t中
        • 查看weak_table_t, 即weak會被存放到一個全局的散列表中
        • 會經過weak_clear_no_lock函數, 對弱指針置爲nil, 同時移除刪列表中的weak記錄
      • 和優化過, 而且被弱指針引用 或者 將引用計數存放到了Sidetable中
        • 若是isa被優化過, 而且對象被弱引用或者將引用計數存到Sidetable中, 就會調用clearDeallocating_slow函數
        • 進入clearDeallocating_slow函數, 能夠看到在函數中, 調用了weak_clear_no_lock函數, 並清空了引用計數

iOS - 老生常談內存管理(四):內存管理方法源碼分析

retainCount方法

  • 在arm64以前,isa不是nonpointer。對象的引用計數全都存儲在SideTable中,retainCount方法返回的是對象自己的引用計數值 1,加上SideTable中存儲的值;
  • 從arm64開始,isa是nonpointer。對象的引用計數先存儲到它的isa中的extra_rc中,若是 19 位的extra_rc不夠存儲,那麼溢出的部分再存儲到SideTable中,retainCount方法返回的是對象自己的引用計數值 1,加上isa中的extra_rc存儲的值,加上SideTable中存儲的值。
  • 因此,其實咱們經過retainCount方法打印alloc建立的對象的引用計數爲 1,這是retainCount方法的功勞,alloc方法並無設置對象的引用計數。

retain方法

  • 若是isa不是nonpointer,那麼就對Sidetable中的引用計數進行 +1;
  • 若是isa是nonpointer,就將isa中的extra_rc存儲的引用計數進行 +1,若是溢出,就將extra_rc中RC_HALF(extra_rc滿值的一半)個引用計數轉移到sidetable中存儲。
  • 從rootRetain函數中咱們能夠看到,若是extra_rc溢出,設置它的值爲RC_HALF,這時候又對sidetable中的refcnt增長引用計數RC_HALF。extra_rc是19位,而RC_HALF宏是(1ULL<<18),實際上相等於進行了 +1 操做。

release方法

  • 若是isa不是nonpointer,那麼就對Sidetable中的引用計數進行 -1,若是引用計數 =0,就dealloc對象;
  • 若是isa是nonpointer,就將isa中的extra_rc存儲的引用計數進行 -1。若是下溢,即extra_rc中的引用計數已經爲 0,判斷has_sidetable_rc是否爲true便是否有使用Sidetable存儲。若是有的話就申請從Sidetable中申請RC_HALF個引用計數轉移到extra_rc中存儲,若是不足RC_HALF就有多少申請多少,而後將Sidetable中的引用計數值減去RC_HALF(或是小於RC_HALF的實際值),將實際申請到的引用計數值 -1 後存儲到extra_rc中。若是extra_rc中引用計數爲 0 且has_sidetable_rc爲false或者Sidetable中的引用計數也爲 0 了,那就dealloc對象。
  • 爲何須要這麼作呢?直接先從Sidetable中對引用計數進行 -1 操做不行嗎?我想應該是爲了性能吧,畢竟訪問對象的isa更快。

dealloc方法

  • ① 判斷 5 個條件(1.isa爲nonpointer;2.沒有弱引用;3.沒有關聯對象;4.沒有C++的析構函數;5.沒有額外採用SideTabel進行引用計數存儲),若是這 5 個條件都成立,直接調用free函數銷燬對象,不然調用object_dispose作一些釋放對象前的處理;
    • 1.若是有C++的析構函數,調用object_cxxDestruct;
    • 2.若是有關聯對象,調用_object_remove_assocations函數,移除關聯對象;

  * 3.調用weak_clear_no_lock將指向該對象的弱引用指針置爲nil;   * 4.調用table.refcnts.erase從引用計數表中擦除該對象的引用計數(若是isa爲nonpointer,還要先判斷isa.has_sidetable_rc)

  • ③ 調用free函數銷燬對象。

  • 根據dealloc過程,__weak修飾符的變量在對象被dealloc時,會將該__weak置爲nil。可見,若是大量使用__weak變量的話,則會消耗相應的 CPU 資源,因此建議只在須要避免循環引用的時候使用__weak修飾符。

  • 在《iOS - 老生常談內存管理(三):ARC 面世 —— 全部權修飾符》章節中提到,__weak對性能會有必定的消耗,當一個對象dealloc時,須要遍歷對象的weak表,把表裏的全部weak指針變量值置爲nil,指向對象的weak指針越多,性能消耗就越多。因此__unsafe_unretained比__weak快。當明確知道對象的生命週期時,選擇__unsafe_unretained會有一些性能提高。

清除weak

  • 當一個對象被銷燬時,在dealloc方法內部通過一系列的函數調用棧,經過兩次哈希查找,第一次根據對象的地址找到它所在的Sidetable,第二次根據對象的地址在Sidetable的weak_table中找到它的弱引用表。弱引用表中存儲的是對象的地址(做爲key)和weak指針地址的數組(做爲value)的映射。weak_clear_no_lock函數中遍歷弱引用數組,將指向對象的地址的weak變量全都置爲nil。

添加weak

  • 一個被標記爲__weak的指針,在通過編譯以後會調用objc_initWeak函數,objc_initWeak函數中初始化weak變量後調用storeWeak。添加weak的過程以下:通過一系列的函數調用棧,最終在weak_register_no_lock()函數當中,進行弱引用變量的添加,具體添加的位置是經過哈希算法來查找的。若是對應位置已經存在當前對象的弱引用表(數組),那就把弱引用變量添加進去;若是不存在的話,就建立一個弱引用表,而後將弱引用變量添加進去。

iOS底層-第二十四章 @autoreleasepool

  • autoreleasepool的結構

    • 每一個AutoreleasePoolPage對象佔用4096字節內存,除了用來存放它內部的成員變量,剩下的空間用來存放autorelease對象的地址
    • AutoreleasePoolPage中除了一開始的56個字節用來存儲成員變量, 其餘的全部內存空間都是用來存儲被autorelease對象的地址
    • 當一個AutoreleasePoolPage不夠存儲autorelease對象地址時, 就會在建立一個AutoreleasePoolPage
    • 全部的AutoreleasePoolPage對象經過雙向鏈表的形式鏈接在一塊兒
  • autoreleasepool的運行機制

    • 調用push方法會將一個POOL_BOUNDARY入棧,而且返回其存放的內存地址
    • 調用pop方法時傳入一個POOL_BOUNDARY的內存地址,會從最後一個入棧的對象開始發送release消息,直到遇到這個POOL_BOUNDARY
    • id *next指向了下一個能存放autorelease對象地址的區域
  • autoreleasepool的嵌套

  • Runloop和Autorelease

    • iOS在主線程的Runloop中註冊了2個Observer
    • 第1個Observer
      • 監聽了kCFRunLoopEntry事件,會調用objc_autoreleasePoolPush()
    • 第2個Observer
      • 監聽了kCFRunLoopBeforeWaiting事件,會調用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
      • 監聽了kCFRunLoopBeforeExit事件,會調用objc_autoreleasePoolPop()

高手課 - 02 App啓動速度怎麼作優化與監控?

虛擬內存的那點事兒

  • 動態庫共享緩存(dyld shared cache)
    • 好處就是節約內存
  • 動態庫的加載
    • 在iOS中是使用了dyld來加載動態庫
    • dyld(dynamic loader),動態加載器
  • mach-o
    • 是mach object的縮寫,是iOS上用於存儲程序、庫的標準格式。
  • 常見的mach-o文件
    • mh_object
      • 目標文件(.o):可執行文件和代碼之間的中間產物。
        • .c -> .o
        • 多個.o合併爲一個可執行文件
      • 靜態庫文件(.a):靜態庫其實就是N個.o合併在一塊兒。
    • mh_execute
      • 可執行文件
    • mh_dylib
      • 動態庫文件
      • .dylib
      • .framework/xx
    • mh_dsym
      • 存儲着二進制文件符號信息的文件
    • mh_dylikner
      • /usr/lib/dyld
      • 動態連接編輯器
  • Universal Binary
    • 通用二進制文件
    • 包含了多種不一樣架構的獨立二進制文件
    • 能夠經過命令瘦身
  • mach-o的基礎結構
    • Header
      • 文件類型、目標架構類型等
    • Load commands
      • 描述文件在內存中的邏輯結構、佈局
    • Rwa segment data
      • 在Load commands中定義的Segment的原始數據
  • dyld和Mach-O的關係
    • dyld負責加載mh_execute、mh_dylib、mh_bundle類型的mach-o文件。

小碼哥底層 app的啓動

  • 經過添加環境變量能夠打印出app的啓動事件分析(edit scheme -> run -> arguments)

    • DYLD_PRINT_STATISTICS設置爲1

    • DYLD_PRINT_STATISTICS_DETAILS設置爲1

  • app冷啓動的過程

    • dyld
      • app編譯後生成一個Mach-O格式的可執行文件
      • 裝載app的可執行文件,同時會遞歸加載全部依賴的動態庫。
      • 當dyld把可執行文件、動態庫都裝載完成後,會通知runtime進行下一步的處理
    • rebase
    • bind
    • runtime
      • 調用map_images進行可執行文件內容的解析和處理
      • 在load_images中調用call_load_methods,調用全部claa和category的+load方法
      • 進行各類objc結構的初始化(註冊objc類、初始化類對象等等)
      • 調用c++靜態初始化器和_attribute_((constructor))修飾的函數
      • 到此爲止,可執行文件和動態庫中全部的符號(class、protocol、selector、imp...)都已經按格式加載到內存中,被runtime所管理。
    • main
      • 全部初始化工做結束後,dyld就會調用main函數。
      • 接下來就是UIApplicationMain函數,AppDelegate的application:didFinishLaunching方法

  • app的啓動優化

    • dyld
      • 減小動態庫、合併一些動態庫
      • 減小objc類、分類的數量、減小selector數量
      • 減小c++虛函數數量
      • swift儘可能使用struct
    • runtime
      • 用+initialize和dispatch_once取代_attribute_((constructor))、c++靜態構造器、objc的+load
    • main
      • 在不影響用戶體驗的前提下,儘量將一些操做延遲,不要所有放在finishLaunching方法中
      • 按需加載

深刻理解iOS App的啓動過程

抖音品質建設 - iOS啓動優化《原理篇》

iOS 啓動優化 + 監控實踐

iOS App啓動優化(一):檢測啓動時間

iOS 優化篇 - 啓動優化之Clang插樁實現二進制重排

高手課 - 03 AutoLayout是怎麼進行自動佈局的

深刻理解 Autolayout 與列表性能 -- 背鍋的 Cassowary 和偷懶的 CPU

高手課 - 05 符號是怎麼綁定到地址上的

  • LLVM 編譯過程

    • 預處理
    • 詞法分析
    • 語法分析
    • 生成 AST
      • 由於 Swift 在編譯時就完成了方法綁定直接經過地址調用屬於強類型語言,方法調用再也不是像 Objective-C 那樣的消息轉發,這樣編譯就能夠得到更多的信息用在後面的後端優化上。所以咱們能夠在 SIL 上對 Swift 作針對性的優化,而這些優化是 LLVM IR 所沒法實現的。
      • 強類型和弱類型的語言有什麼區別
    • 靜態分析
    • 生成 LLVM IR
    • 編譯器優化
    • Bitcode (可選)
    • 生成彙編
    • 生成目標文件
      • 連接器作符號和地址綁定
      • 連接器還要把項目中的多個 Mach-O 文件合併成一個
    • 生成可執行文件
  • 源碼到可執行文件流程

    • 編譯器Clang會將源碼XXX.m編譯爲目標文件XXX.o
    • 連接器會將目標文件連接打包進最終的可執行文件Mach-O中
      • 連接器解決了目標文件和庫之間的連接。
    • 點擊App ICON時,動態連接器dyld會加載可執行文件以及依賴的動態庫,並最終執行到main.m裏,至此App啓動完成

  • iOS 編譯知識小結

  • iOS底層學習 - 從編譯到啓動的奇幻旅程(一)

高手課 - 06 App如何經過注入動態庫的方式實現極速編譯調試

  • 建立監聽SimpleSocket,經過File Watcher監聽觀察文件改動
  • 修改代碼,保存後從新編譯修改的類文件,修改後的文件被編譯爲了.dylib動態庫
  • 而後經過writestring給咱們的App發"INJECT"消息,通知App更新代碼
  • 經過SwiftEval.instance.loadAndInject方法dlopen加載.dylib動態庫
  • 而後經過OC runtime 的class_replaceMethod把整個類的實現方法都替換
  • 而後再調SwiftInjected.injected咱們的類收到消息開始重繪UI

高手課 - 07 咱們應該使用誰來作靜態分析?

  • OCLint/SwiftLint
  • Infer
  • Clang 靜態分析器

高手課 - 08 如何利用Clang爲App提質?

iOS 查漏補缺 - LLVM & Clang

高手課 - 09 無侵入的埋點方案如何實現

靜下心來讀源碼之Aspects iOS AOP 框架 - Aspects 源碼解讀

高手課 - 10 包大小:如何從資源和代碼層面實現全方位瘦身?

  • 官方 App Thinning
  • 無用圖片資源
  • 圖片資源壓縮
  • 經過 AppCode 找出無用代碼

高手課 - 12 iOS 崩潰千奇百怪,如何全面監控?

iOS Crash防禦

iOS中常見Crash總結

  • 可捕獲崩潰
    • KVO 問題、NSNotification 線程問題、數組越界、野指針等崩潰信息
    • EXC_BAD_ACCESS 這個異常會經過 SIGSEGV 信號發現有問題的線程。雖然信號的種類有不少,可是均可以經過註冊 signalHandler 來捕獲到。
    • oc本身也有定義一些並沒深刻到內核的一些exception,這些是經過註冊exceptionhandle來進行捕獲的
    • 對各類信號都進行了註冊,捕獲到異常信號後,在處理方法 handleSignalException 裏經過 backtrace_symbols 方法就能獲取到當前的堆棧信息。堆棧信息能夠先保存在本地,下次啓動時再上傳到崩潰監控服務器就能夠了。
  • 不可捕獲崩潰
    • 後臺任務超時、內存被打爆、主線程卡頓超閾值等信息
    • 設置閥值,保存堆棧信息。
  • 殭屍對象原理
    • 開啓Zombie Objects後,dealloc將會被hook,被hook後執行dealloc,內存並不會真正釋放,系統會修改對象的isa指針,指向_NSZombie_前綴名稱的殭屍類,將該對象變爲殭屍對象。
    • 殭屍類作的事情比較單一,就是響應全部的方法:拋出異常,打印一條包含消息內容及其接收者的消息,而後終止程序。

Crash

Unrecoginzed Selector Crash

  • hook NSObject的 -(id)forwardingTargetForSelector:(SEL)aSelector 方法啓動消息轉發

KVO Crash

  • 找一個 Proxy 用來作轉發, 真正的觀察者是 Proxy,被觀察者出現了通知信息,由 Proxy 作分發。因此 Proxy 裏面要保存一個數據結構 {keypath : [observer1, observer2,...]} 。
  • Hook NSObject的KVO相關方法
      • (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
      • (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
  • hook dealloc在dealloc中遍歷刪除proxy中的數對象。

數組 Crash

  • hook 常見的方法加入檢測功能而且捕獲堆棧信息上報。
  • 不過要注意容器類是類簇,直接hook容器類是沒法成功的,須要hook對外隱藏的實際起做用的類,好比NSMutableArray的實際類名爲__NSArrayM。

NSNotification Crash

  • 相似 KVO 中間加上 Proxy 層,使用 weak 指針來持有對象
  • 在 dealloc 的時候將未被移除的觀察者移除

NSNull Crash

  • 一旦對 NSNull 這個類型調用任何方法都會出現 unrecongized selector 錯誤

野指針 Crash

  • 借用系統的NSZombies對象的設計
    • 創建白名單機制,因爲系統的類基本不會出現野指針,並且 hook 全部的類開銷較大。因此咱們只過濾開發者自定義的類。
    • hook dealloc 方法 這些須要保護的類咱們並不讓其釋放,而是調用objc_desctructInstance 方法釋放實例內部所持有屬性的引用和關聯對象。
    • 利用 object_setClass(id,Class) 修改 isa 指針將其指向一個Proxy 對象(類比�系統的 KVO 實現),此 Proxy 實現了一個和前面所說的智能轉發類同樣的 return 0的函數。
    • 在 Proxy 對象內的 - (void)forwardInvocation:(NSInvocation *)anInvocation 中收集 Crash 信息。
    • 緩存的對象是有成本的,咱們在緩存對象到達必定數量時候將其釋放(object_dispose)。
  • 建議使用的時候若是近期沒有野指針的Crash能夠沒必要開啓,若是野指針類型的Crash忽然增多,能夠考慮在 hot Patch 中開啓野指針防禦,待收取異常信息以後,再關閉此開關。

高手課 - 13 如何利用RunLoop原理去監控卡頓

天羅地網? iOS卡頓監控實戰(開源)

iOS卡頓監測方案總結

使用RunLoop檢測卡頓

  • 一旦發現進入睡眠前的 kCFRunLoopBeforeSources 狀態,或者喚醒後的狀態 kCFRunLoopAfterWaiting,在設置的時間閾值內一直沒有變化,便可斷定爲卡頓。接下來,咱們就能夠 dump 出堆棧的信息,從而進一步分析出具體是哪一個方法的執行時間過長。

高手課 - 14 臨近 OOM,如何獲取詳細內存分配信息,分析內存問題?

iOS 性能優化實踐:頭條抖音如何實現 OOM 崩潰率降低50%+

如何斷定發生了OOM

分析字節跳動解決OOM的在線Memory Graph技術實現

高手課 - 15 日誌監控:怎樣獲取 App 中的全量日誌?

CocoaLumberjack源碼

高手課 - 17 臨近 OOM,如何獲取詳細內存分配信息,分析內存問題?

  • 爲何AFNetworking 2.0須要常駐線程

這個問題的根源在於 AFNetworking 2.0 使用的是 NSURLConnection,而 NSURLConnection 的設計上存在些缺陷。NSURLConnection 發起請求後,所在的線程須要一直存活,以等待接收 NSURLConnectionDelegate 回調方法。可是,網絡返回的時間不肯定,因此這個線程就須要一直常駐在內存中。AFNetworking 在 3.0 版本時,使用蘋果公司新推出的 NSURLSession 替換了 NSURLConnection,從而避免了常駐線程這個坑。NSURLSession 能夠指定回調 NSOperationQueue,這樣請求就不須要讓線程一直常駐在內存裏去等待回調了。

  • 相似數據庫這種須要頻繁讀寫磁盤操做的任務,儘可能使用串行隊列來管理,避免由於多線程併發而出現內存問題

新浪 - 1 UI視圖相關面試問題

iOS觸摸事件全家桶

  • 事件傳遞

  • 事件響應
    • 若是響應視圖沒法處理響應事件,則響應事件會經過響應鏈傳遞給父視圖嘗試處理,直到傳遞給UIApplication。
  • 圖像顯示原理
    • CPU和GPU是經過事件總線連接在一塊兒的。
    • CPU輸出的位圖,在適當時機由事件總線上傳給GPU。
    • GPU會對位圖進行渲染,而後將結果放入幀緩衝區。
    • 視頻控制器經過Vsync信號,在指定時間(16.7ms)以前,從幀緩衝區中提取屏幕顯示內容,而後顯示在顯示器上。

WechatIMG36.jpeg WechatIMG37.jpeg

  • 卡頓&掉幀的緣由
    • 若是CPU和GPU的工做時長超過16.7ms,那麼當Vsync信號來臨時,沒法提供這一幀的畫面,就會出現掉幀現象。
  • 繪製原理
  • 異步繪製

  • 離屏渲染

新浪 - 2 Objective-C語言特性相關面試問題

  • 分類和擴展的區別
    • 分類是運行時決議,擴展是編譯時決議
  • 如何手動實現Notification
  • MRC手動修飾變量的setter

加固

  • 加殼
    • 利用特殊的算法,對可執行文件的編碼進行改變(好比壓縮,加密),以達到保護程序代碼的目的。
  • 脫殼
    • 硬脫殼
    • 動態脫殼

新浪 - 4 內存管理相關面試問題

  • 散列表結構

  • sideTable結構

  • sideTable算法
    • 經過對象地址 與Hash表的count取模,獲取目標值下標索引。
  • 弱引用表結構

  • retain & relase實現

  • dealloc原理

新浪 - 6 多線程相關面試

  • 死鎖

    • 首先在主線程執行主隊列中的viewDidLoad函數。
    • 當執行到block時,由於是同步,因此須要hold住主線程中主隊列正在執行的viewDidLoad函數,等執行完主隊列中block內部代碼後,再執行主線程中主隊列的viewDidLoad函數。
    • 因此出現了viewDidLoad等待block的狀況。
    • block內的代碼要執行,必須等待隊列中其餘函數執行完,即先進先出。
    • 因此出現了block等待viewDidLoad的狀況。
    • 最終兩個函數相互等待,出現形成死鎖。
  • 狀況二

    • 首先在主線程執行主隊列中的viewDidLoad函數。
    • 當執行到block時,由於是同步,因此須要hold住主線程中主隊列正在執行的viewDidLoad函數,等執行完主隊列中block內部代碼後,再執行主線程中主隊列的viewDidLoad函數。
    • 因此出現了viewDidLoad等待block的狀況。
    • block內部的代碼會在serialQueue的隊列中取出,由於serialQueue中block排在最前,因此block會被當即取出,並在主線程中執行。
    • 待block執行完畢,會執行viewDidLoad剩餘代碼。
  • 狀況三

    • 由於是併發隊列,因此運行隊列中的任務一塊兒執行,不須要等待上一個任務執行完再執行下一個,因此不會死鎖。
    • 若是global_queue換成串行隊列,就會產生死鎖。
  • 異步串行

    • 先執行完viewDidLoad,再執行block內的代碼。
  • 子線程默認未開起runloop

    • 由於子線程默認沒有開啓runloop,performSelector沒法執行。
    • 這個方法調用後,在當前runloop裏設置了一個timer,來觸發這個方法執行。而當前這個方法是在子線程中調用的,在子線程中runloop不是自動建立並跑起來的,須要手動調用,纔會建立。由於這個在子線程中的調用沒有建立runloop,因此就沒有執行
  • 怎樣利用GCD實現多讀單寫?

    • 讀的時候使用dispatch_sync,是由於使用同步隊列能夠在賦值結束後,再執行返回值的操做。
  • 使用GCD實現A、B、C三個任務併發,完成後執行任務D。

  • NSLock死鎖問題

新浪 - 8 網絡相關面試問題

  • 你都瞭解哪些狀態碼?

HTTP 響應代碼

  • GET和POST的區別?
    • get請求參數以?分隔拼接到URL後面,post請求參數在Body內部。
    • get參數長度顯示2048個字符,post通常沒有該限制。
    • get請求不安全,post請求比較安全。
  • 鏈接創建流程

  • HTTP的特色

    • 無鏈接
      • HTTP持久鏈接方案可解決該問題
      • 開啓持久鏈接須要設置的頭部字段
        • Connection: keep-alive 須要開啓持久鏈接
        • time: 20 持續時間
        • max: 10 持久鏈接最多可發起的網絡請求次數
    • 無狀態
      • Cookie

        • 狀態保存在客戶端
        • 客戶端發送的cookie在http請求報文的Cookie首部字段中。
        • 服務器端設置http響應報文的Set-Cookie首部字段。
      • Session

        • 狀態存放在服務器端
        • Seesion須要依賴於Cookie機制實現。
  • 怎樣判斷一個請求是否結束?

    • Content-length: 1024根據所接受數據是否達到Content-length來判斷。
    • chunkedpost請求會有屢次返回,最後一次返回會有一個空的chunked。
  • Charles抓包原理

    • 當客戶端和服務器創建鏈接時,Charles會攔截到服務器返回的證書(服務器公鑰)
    • 而後動態生成一張僞造證書(Charles公鑰/假公鑰)發送給客戶端
    • 客戶端收到Charles證書後,進行驗證;由於以前咱們手機設置了信任,因此驗證經過;(只要手機不信任這種證書,HTTPS仍是能確保安全的)
    • 客戶端生成會話密鑰,使用Charles證書對會話密鑰進行加密再傳輸給服務器
    • Charles攔截到客戶端傳輸的數據,使用本身的Charles私鑰進行解密獲得會話密鑰
    • 鏈接成功後,客戶端和服務器通訊,客戶端對傳輸的數據使用會話密鑰加密並使用公鑰對數據摘要進行數字簽名,一同傳輸給服務器;
    • Charles攔截到通訊的數據,使用以前得到的會話密鑰解密就能獲得原始數據;
    • Charles一樣也能篡改通訊的數據:將篡改後的數據從新加密並從新生成摘要並使用以前得到的公鑰進行數字簽名,替換本來的簽名,再傳輸給服務器;
    • 服務器收取到數據,按正常流程解密驗證;
    • 服務器返回響應數據時,Charles也是相似攔截過程
  • HTTPS連接創建流程是怎樣的?

    • 客戶端向服務器發送一段報文,報文內容包括三部分,客戶端支持的TLS協議版本,客戶端支持的加密算法,一段隨機數C。
    • 服務器返回客戶端一段握手的報文消息,內容也包括三部分,服務器從客戶端上報的多種加密算法中選擇的加密算法,一段隨機數S,服務器證書。
    • 客戶端對服務器返回的證書進行驗證,判斷服務器是不是合法的服務器,即對服務器公鑰進行驗證。
    • 客戶端經過預主密鑰、一段隨機數C、一段隨機數S,組裝會話密鑰。
    • 客戶端經過服務器的公鑰對預主密鑰進行加密傳輸。
    • 服務器經過私鑰解密獲得預主密鑰。
    • 服務器經過預主密鑰、一段隨機數C、一段隨機數S,組裝會話密鑰。
    • 客戶端和服務器相互發送加密的握手消息,驗證握手是否完成。
  • UDP

  • TCP如何作到可靠傳輸

  • 滑動窗口協議

    • 接收方有接收緩存,若是發送速度過快,可能形成溢出。
    • 接收方能夠動態調整發送方的發送窗口,達到動態調整發送速率的目的。
    • 發送窗口和接收窗口是兩個字段,位於TCP報文的首部。
  • 擁塞控制

  • 爲何要進行三次握手而不是兩次?

  • DNS解析

新浪 - 9 設計模式、架構、三方庫

設計模式

原型模式

  • NSCopying
    • 深拷貝
    • 淺拷貝

工廠模式

  • 簡單工廠

  • 工廠方法模式
    • 在工廠方法模式中,咱們再也不提供一個統一的工廠類來建立全部的產品對象,而是針對不一樣的產品提供不一樣的工廠

  • 抽象工廠模式
    • 在工廠方法模式中一個具體工廠只生產一種具體產品, 可是有時候咱們須要一個工廠可以生產多個具體產品對象, 而不是一個單一的具體對象,這就引入了抽象工廠模式。

  • 三種工廠方法關係
    • 當抽象工廠模式中每個具體工廠類只建立一個產品對象,也就是隻存在一個產品等級結構時,抽象工廠模式退化成工廠方法模式;
    • 當工廠方法模式中抽象工廠與具體工廠合併,提供一個統一的工廠來建立產品對象,並將建立對象的工廠方法設計爲靜態方法時,工廠方法模式退化成簡單工廠模式。

單例模式

  • dispatch_once 線程安全 有加鎖

適配器模式

  • 將一個類接口轉化爲客戶代碼須要的另外一個接口。適配器使本來因爲兼容性而不能協同工做的類能夠工做在一塊兒,消除了客戶代碼和目標對象的類之間的耦合性。

組合模式

  • 組合模式爲樹形結構的面向對象提供了一種靈活的解決方案
  • view的結構使用組合模式

裝飾模式

  • 用於替代繼承的技術, 無需定義子類就能夠給原來的類增長新的功能, 使用對象關聯關係替代繼承。
  • 最終執行的是裝飾器接入的基本組件中的函數。
  • OC中是category
  • swift中是extension

外觀模式

  • 一個客戶類要和多個業務類交互, 而這些交互的業務類常常做爲一個總體出現, 這個時候可使用外觀模式, 爲客戶端提供一個簡化的入口, 簡化客戶類和業務類的交互.
  • 相似不少三方庫中的manager,用於銜接其餘工具類。

代理模式

責任鏈模式

  • 響應者鏈
  • NSResponder

命令模式

  • NSInvocation
    • NSInvocation類的實例用於封裝Objective-C消息。一個調用對象中含有一個目標對象、一個方法選擇器、以及方法參數。
    • 您能夠動態地改變調用對象中消息的目標及其參數,一旦消息被執行,您就能夠從該對象獲得返回值。經過一個調用對象能夠屢次調用目標或參數不一樣的消息。
    • 建立NSInvocation對象須要使用NSMethodSignature對象,該對象負責封裝與方法參數和返回值有關係的信息。NSMethodSignature對象的建立又須要用到一個方法選擇器。
  • Target&Action
    • 當您用Interface Builder構建程序的用戶界面時,能夠對控件的動做和目標進行設置。您所以可讓控件具備定製的行爲,而又沒必要爲控件自己書寫任何的代碼。動做選擇器和目標鏈接被歸檔在nib文件中,並在nib文件被解檔時復活。您也能夠經過向控件或它的單元對象發送setTarget:和setAction:消息來動態地改變目標和動做。

解釋器模式

  • 好比判斷郵件地址、電話號碼、證件號碼是不是正確的正則表達式,就是應用瞭解釋器模式。

迭代器模式

  • 這種模式提供一種順序訪問聚合對象(也就是一個集合)中的元素,而又沒必要暴露潛在表示的方法。迭代器模式將訪問和遍歷集合元素的責任從集合對象轉移到迭代器對象。迭代器定義一個訪問集合元素的接口,並對當前元素進行跟蹤。不一樣的迭代器能夠執行不一樣的遍歷策略。
  • Foundation框架中的NSEnumerator類實現了迭代器模式。

中介者模式

  • MVC模式是中介者模式的一種表現形式,Comtroller(中介者)承擔兩個同事類(View和Modle)之間的中轉和協調做用。

備忘錄模式

  • 這種模式在不破壞封裝的狀況下,捕捉和外部化對象的內部狀態,使對象在以後能夠回覆到該狀態。備忘錄模式使關鍵對象的重要狀態外部化,同時保持對象的內聚性。
  • 經過NSCoder對象能夠執行編解碼操做,在編解碼過程當中最好使用鍵化的歸檔技術(須要調用NSKeyedArchiver和NSKeyedUnarchiver類的方法)。被編解碼的對象必須遵循NSCoding協議;該協議的方法在歸檔過程當中會被調用。

觀察者模式

  • KVO

狀態模式

  • 同一操做在不一樣狀態,執行不一樣的方案。
  • 代碼中包含大量與對象狀態有關的條件語句: 一個操做中含有龐大的多分支的條件(if else(或switch case)語句,且這些分支依賴於該對象的狀態。
  • 這個狀態一般用一個或多個枚舉常量表示。一般, 有多個操做包含這一相同的條件結構。
  • State模式將每個條件分支放入一個獨立的類中。這使得你能夠根據對象自身的狀況將對象的狀態做爲一個對象,這一對象能夠不依賴於其餘對象而獨立變化。

策略模式

iOS設計模式之策略模式

模版方法模式

不常使用

  • 建造者模式
  • 橋接模式
  • 訪問者模式

《Objective-C 高級編程》

《Objective-C 高級編程》乾貨三部曲(一):引用計數篇

《Objective-C 高級編程》乾貨三部曲(二):Blocks篇

《Objective-C 高級編程》乾貨三部曲(三):GCD篇

《Effective Objective-C》

《Effective Objective-C》乾貨三部曲(一):概念篇

《Effective Objective-C》乾貨三部曲(二):規範篇

《Effective Objective-C》乾貨三部曲(三):技巧篇

網絡協議

小碼哥《網絡協議從入門到底層原理》筆記(1、二):基本概念、集線器、網橋、交換機、路由器

小碼哥《網絡協議從入門到底層原理》筆記(三):MAC地址、IP地址

小碼哥《網絡協議從入門到底層原理》筆記(四):路由、區域網、NAT

小碼哥《網絡協議從入門到底層原理》筆記(五):物理層、數據鏈路層

小碼哥《網絡協議從入門到底層原理》筆記(六):網絡層

小碼哥《網絡協議從入門到底層原理》筆記(七):傳輸層、UDP、TCP可靠傳輸

小碼哥《網絡協議從入門到底層原理》筆記(八):TCP可靠傳輸、流量控制、擁塞控制

小碼哥《網絡協議從入門到底層原理》筆記(九):TCP序號、確認號、創建鏈接、釋放鏈接

小碼哥《網絡協議從入門到底層原理》筆記(十):應用層、域名、DNS解析

小碼哥《網絡協議從入門到底層原理》筆記(十一):HTTP、報文、請求頭、狀態碼、form

知識盲區面試題

相關文章
相關標籤/搜索