下載&後臺下載相關
首先了解NSURLSession
:
NSURLSession
中負責下載策略的URLSessionConfiguration
:
SessionDelegate
中負責下載的DownloadSessionDelegate
:
extension DownloadSessionDelegate: URLSessionDownloadDelegate {
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)
}
}
public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
manager?.didBecomeInvalidation(withError: error)
}
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)
}
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 文件夾裏;
- 若是不被session管理,且能夠恢復,則緩存文件會被移動到 Tmp 文件夾裏;
- 若是不被Background Session管理,且不能夠恢復,則緩存文件會被刪除。
- 手動 Kill App 會調用了
cancel(byProducingResumeData:)
或者cancel
,最後會調用urlSession(_:task:didCompleteWithError:)
代理方法,能夠在這裏作集中處理,管理 downloadTask,把resumeData保存起來。
- 進入後臺、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源碼解析
雲盤上傳
- 將添加的全部上傳任務建立爲
DownloadTask
,並保存self.sessionManager.cache.storeUploadTasks(self.waittingUploadArray)
- 檢查正在上傳的任務數,將可上傳任務數補充至3個。
uploadArray
&waittingUploadArray
- 被暫停的任務從新開始(舊任務)
- 獲取上傳狀態
- 1.若是未完成,則執行上傳。
- 2.已完成上傳的任務,將任務從等待數組中移除,繼續添加任務。
- 若是是照片或視頻,則文件拷貝到沙盒。
- 檢查硬盤空間
- 上傳以前,將待上傳文件複製到解密區。
- 經過密碼中間件計算哈希
- 密鑰轉保護、文件加密
- 文件預上傳
- 將文件分片,讀取第一片上傳
- 分片上傳完成後,判斷是否還有分片未上傳
- 未上傳完,分片加一,從新調用上傳
- 所有上傳完,繼續添加上傳任務
後臺上傳
iOS項目技術還債之路《一》後臺下載趟坑html
上傳加密流程
下載解密流程
預覽流程
文件存儲流程
iOS底層-第一章 oc對象本質
- 一個OC對象在內存中是如何佈局的?
- 一個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
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,兩個方法必須同時出現。
iOS底層-第五章 kvc
- setValue:forKey的原理?
- 按照setKey、_setkey順序查找方法
- 沒有找到方法,則查看accessInstanceVariablesDirectly方法返回值,默認爲true。
- 若爲true,按照_key、_isKey、key、isKey順序查找成員變量,找到了直接賦值。
- 若爲false或沒有找到成員變量,調用valueForUndefineKey:並跑出異常NSUnknownKeyException
- valueForKey的原理?
- 按照getKey、key、isKey、_key順序查找方法
- 沒有找到方法,則查看accessInstanceVariablesDirectly方法返回值,默認爲true。
- 若爲true,按照_key、isKey、key、isKey順序查找成員變量,找到了直接取值。
- 若爲false或沒有找到成員變量,調用valueForUndefineKey:並跑出異常NSUnknownKeyException
- kvc賦值時,會觸發kvo嗎?
- 使用KVO給屬性或成員變量賦值時, 都會觸發KVO, 系統會自動調用willChangeValueForKey:和didChangeValueForKey:兩個方法
iOS底層-第六章 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內存管理
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:方法
iOS底層-第十六章 super
iOS底層-第十七章 Runloop基本認識
-
runloop與線程的關係
- 每條線程都有惟一的一個與之對應的RunLoop對象
- RunLoop保存在一個全局的Dictionary裏, 線程做爲key, RunLoop對象作爲Value
- 線程剛建立時並無RunLoop對象, RunLoop會在第一次獲取它時建立
- RunLoop會在線程結束時銷燬
- 主線程的RunLoop已經自動獲取(建立), 子線程默認沒有開啓RunLoop
-
runloop底層結構
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
- 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給兩個線程
thread1
和thread2
加鎖
- 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
iOS底層-第二十一章 內存管理-定時器
- CADisplayLink
- 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函數, 並清空了引用計數
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):可執行文件和代碼之間的中間產物。
- 靜態庫文件(.a):靜態庫其實就是N個.o合併在一塊兒。
- mh_execute
- mh_dylib
- 動態庫文件
- .dylib
- .framework/xx
- mh_dsym
- mh_dylikner
- 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的啓動
深刻理解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 使用的是 NSURLConnection,而 NSURLConnection 的設計上存在些缺陷。NSURLConnection 發起請求後,所在的線程須要一直存活,以等待接收 NSURLConnectionDelegate 回調方法。可是,網絡返回的時間不肯定,因此這個線程就須要一直常駐在內存中。AFNetworking 在 3.0 版本時,使用蘋果公司新推出的 NSURLSession 替換了 NSURLConnection,從而避免了常駐線程這個坑。NSURLSession 能夠指定回調 NSOperationQueue,這樣請求就不須要讓線程一直常駐在內存裏去等待回調了。
- 相似數據庫這種須要頻繁讀寫磁盤操做的任務,儘可能使用串行隊列來管理,避免由於多線程併發而出現內存問題
新浪 - 1 UI視圖相關面試問題
iOS觸摸事件全家桶
- 事件響應
- 若是響應視圖沒法處理響應事件,則響應事件會經過響應鏈傳遞給父視圖嘗試處理,直到傳遞給UIApplication。
- 圖像顯示原理
- CPU和GPU是經過事件總線連接在一塊兒的。
- CPU輸出的位圖,在適當時機由事件總線上傳給GPU。
- GPU會對位圖進行渲染,而後將結果放入幀緩衝區。
- 視頻控制器經過Vsync信號,在指定時間(16.7ms)以前,從幀緩衝區中提取屏幕顯示內容,而後顯示在顯示器上。
- 卡頓&掉幀的緣由
- 若是CPU和GPU的工做時長超過16.7ms,那麼當Vsync信號來臨時,沒法提供這一幀的畫面,就會出現掉幀現象。
- 繪製原理
- 異步繪製
新浪 - 2 Objective-C語言特性相關面試問題
- 分類和擴展的區別
- 如何手動實現Notification
- MRC手動修飾變量的setter
加固
- 加殼
- 利用特殊的算法,對可執行文件的編碼進行改變(好比壓縮,加密),以達到保護程序代碼的目的。
- 脫殼
新浪 - 4 內存管理相關面試問題
- sideTable算法
- 經過對象地址 與Hash表的count取模,獲取目標值下標索引。
- 弱引用表結構
新浪 - 6 多線程相關面試
新浪 - 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 設計模式、架構、三方庫
設計模式
原型模式
工廠模式
- 工廠方法模式
- 在工廠方法模式中,咱們再也不提供一個統一的工廠類來建立全部的產品對象,而是針對不一樣的產品提供不一樣的工廠
- 抽象工廠模式
- 在工廠方法模式中一個具體工廠只生產一種具體產品, 可是有時候咱們須要一個工廠可以生產多個具體產品對象, 而不是一個單一的具體對象,這就引入了抽象工廠模式。
- 三種工廠方法關係
- 當抽象工廠模式中每個具體工廠類只建立一個產品對象,也就是隻存在一個產品等級結構時,抽象工廠模式退化成工廠方法模式;
- 當工廠方法模式中抽象工廠與具體工廠合併,提供一個統一的工廠來建立產品對象,並將建立對象的工廠方法設計爲靜態方法時,工廠方法模式退化成簡單工廠模式。
單例模式
適配器模式
- 將一個類接口轉化爲客戶代碼須要的另外一個接口。適配器使本來因爲兼容性而不能協同工做的類能夠工做在一塊兒,消除了客戶代碼和目標對象的類之間的耦合性。
組合模式
- 組合模式爲樹形結構的面向對象提供了一種靈活的解決方案
- view的結構使用組合模式
裝飾模式
- 用於替代繼承的技術, 無需定義子類就能夠給原來的類增長新的功能, 使用對象關聯關係替代繼承。
- 最終執行的是裝飾器接入的基本組件中的函數。
- OC中是category
- swift中是extension
外觀模式
- 一個客戶類要和多個業務類交互, 而這些交互的業務類常常做爲一個總體出現, 這個時候可使用外觀模式, 爲客戶端提供一個簡化的入口, 簡化客戶類和業務類的交互.
- 相似不少三方庫中的manager,用於銜接其餘工具類。
代理模式
責任鏈模式
命令模式
- 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協議;該協議的方法在歸檔過程當中會被調用。
觀察者模式
狀態模式
- 同一操做在不一樣狀態,執行不一樣的方案。
- 代碼中包含大量與對象狀態有關的條件語句: 一個操做中含有龐大的多分支的條件(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
知識盲區面試題
- 爲何不能在異步線程中更新頁面,介紹緣由
- 網絡優化、弱網優化方案
- 網絡深度優化的點
- NSCache緩存、Last-Modified、ETag
- 失敗重發、緩存請求有網發送
- DNS解析
- 數據壓縮:protobuf,WebP
- 弱網:2G、3G、4G、wifi下設置不一樣的超時時間
- TCP對頭阻塞:GOOGLE提出QUIC協議,至關於在UDP協議之上再定義一套可靠傳輸協議
- 正常一條網絡請求須要通過的流程是這樣:
- DNS 解析,請求DNS服務器,獲取域名對應的 IP 地址。
- 與服務端創建鏈接,包括 tcp 三次握手,安全協議同步流程。
- 鏈接創建完成,發送和接收數據,解碼數據。
- 這裏有明顯的三個優化點:
- 直接使用 IP 地址,去除 DNS 解析步驟。
- 不要每次請求都從新創建鏈接,複用鏈接或一直使用同一條鏈接(長鏈接)。
- 壓縮頭部、壓縮數據,減少傳輸的數據大小。
- 弱網
- 提高鏈接成功率
- 制定最合適的超時時間
- 調優TCP參數,使用TCP優化算法
- http2.0與http1.x的區別
- 靜態庫與動態庫
- Cookie與Session的區別-總結很好的文章