(1)RunTime總結:
oc動態性, 運行時將代碼轉化爲runtime的C代碼
RunTime運行流程:
生成對應objc_msgSend方法 isa指針查看當前類有沒有這個方法, 之後尋找父類, 每個方法SEL(方法選擇器)對應IMP(類似於一個編號,是函數指針,指向函數實現,找到內存裏對應函數), 直到NSObeject, 如果找不到IMP, 會進入消息轉發機制, resolveClassMethod, resolveInstanceMethod, forwardingTargetForSelector, forwardInvocation 第一個方法所屬類方法動態方法解析, 第二個和第一個類似,是對應實例方法的, 第三個是備援接受者, 第四個方法是消息重定向, 真正消息轉發,也是Aspects的核心操作, 如果都找不到調用doesNotRecognizeSelector:方法拋出異常
RunTime的實際應用
交換方法(黑魔法.hook,讓SEL1->IMP2,SEL2->IMP1),爲系統類添加自定義方法,三方Aspects
①用方法交換添加保護, 如數組賦值時添加越界判斷等等 ②統計頁面點擊數用 ③多繼承 ④自動化歸檔(kvo) ⑤NSTime內存泄漏(vc被釋放通過消息轉發找回vc) ⑥系統類添加自定義方法, 寫一些更便捷的代碼,比如控件加手勢,字典加加密方法,代碼更簡潔
(2)KVO總結:
KVO是OC的一種觀察者設計模式,另一種是通知機制, 是基於runtime機制實現的, 也是一種響應式編程(kvo,block,代理,通知,定時器等)
KVO運行流程:
當觀察對象A時,KVO動態創建了新的名爲NSKVONotifying_A的新類,該類時爲對象A的子類(根據父類—>子類, 創建類名,開闢內存空間. 利用RunTime拿到父類的函數實現,用黑魔法isa-swizzling交換父類方法調用) 並且KVO重寫了新類的觀察屬性的setter方法,setter方法負責在調用原setter方法之前和之後,通知所有觀察者該屬性的變化情況(利用消息轉發,子類—>父類)
子類重寫setter方法:
KVO的鍵值觀察通知依賴於NSObject的兩個方法:willChangeValueForKey:和didChangeValueForKey:,在存取數值的前後分別調用2個方法;中間利用消息轉發,之後,observeValueForKey:ofObject:change:context:也會被調用。
KVO注意事項:
①沒有set方法無法觀察,例如成員變量(類外部不可訪問,不可賦值,類內部可以通過self->屬性名或者屬性名訪問和賦值) ②觀察可變數組方法不一樣
KVO實際應用:
①監聽屬性(模型)變化 ②MVVM雙向綁定 ③網絡,斷點續傳
(3)MVC, MVP, MVVM總結:
MVC是運用最廣泛的架構模式,MVP和MVVM是基於MVC衍生的新框架, 可以實現解耦, 真正實現高內聚低耦合的特性. 但架構沒有最好的, 只有最合適的!!!
MVC, MVP, MVVM區別:
MVC缺點: 厚重的VC, 代碼可讀性差, 邏輯混亂, 基本無法測試.
在日常開發使用MVC中,經常爲了減少代碼量,冗餘將Model寫在View,這樣View的移植性差, 增加了耦合性. MVP是面向協議編程,使用代理完成雙向綁定, 但一些延遲性操作難以管理(如請求接口數據). MVVM是MV(X)系列最好的架構模式, 雙向綁定,面向需求添加方法, 隨掉隨用, MVC的代碼抽離也是一種MVVM思想
MVVM優點: 低耦合, 可重用性, 獨立開發(業務邏輯通過VM和UI分離), 可測試(可以針對viewModel測試)
MVVM缺點: 界面異常BUG難調試, 代碼不直觀, 企業應用存在學習成本
(4)Block總結: ✨ 最常用沒有之一, 敲黑板✨
將OC語言block改寫成C語言的block, 動態生成.cpp文件(c++代碼). block是可以%@打印的, 說明也是一個對象. 可以理解爲特殊格式, 帶函數回調的對象. block的靈活之處也在於可以將block作爲一個屬性封裝, 保存一段代碼塊, 可以在任意時候調用
block分類:
NSGlobalBlock(全局)、NSStackBlock(棧)、NSMallocBlock(堆)
當block回調對外部變量操作時, 將外部變量copy到堆上,
block應用場景:
①傳值 ②MVVM ③封裝回調代碼塊
__block:
block對外部變量是隻讀的,要變成可讀可寫,就需要加上__block, 將棧中的block複製到堆上一份,從而避免了循環引用這個情況
__block原理: 能夠將觀察到int a=0的值copy到堆裏, 對a的指針地址進行修改, block回調裏a指針地址和外部變量a的指針地址相同(相當於淺拷貝, 拷貝指針地址) 棧/常量 -> copy -> 堆
block解決循環引用(面試總問:多種解決方法):
循環引用原因: A持有B,B又持有A, 就形成了互相持有, 形成了閉環. 從引用計數分析是B想釋放, 但A還持有B, 同理A也無法釋放(A,B就是self和block)
解決方案: ①弱引用__weak typeof(self) weakSelf = self;(原理強弱共舞) ②__block ViewController *bWeakSelf = self; 同時block回調裏 bWeakSelf = nil; (原理把self至nil)
③self.block = ^(ViewController *obj){ };(原理以參數形式傳入self)
block裏Cope和Strong的區別:
因爲在MRC下,block在創建的時候,它的內存是分配在棧(stack)上的,而不是在堆(heap)上,可能被隨時回收。他本身的作用域是屬於創建時候的作用域,一旦在創建時候的作用域外面調用block將導致程序崩潰。通過copy可以把block拷貝(copy)到堆,保證block的聲明域外使用。在ARC下寫不寫都行,編譯器會自動對block進行copy操作
在ARC下, 沒區別, Strong在ARC也會自動將block拷貝到堆上, MRC需要使用Copy
(5)NSTime
兩種初始化方法:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
注:不用scheduled方式初始化的,需要手動addTimer:forMode: 將timer添加到一個runloop中。而scheduled的初始化方法將以默認mode直接添加到當前的runloop中.
可能存在的問題:
(1)帶有RunLoop的定時器發生內存泄漏
原因:RunLoop->timer->self 形成循環引用,無法在頁面離開時釋放
解決方案:①在生命週期代碼里加上timer的停止代碼(最簡單方式) ②思路:不讓RunLoop持有timer 實現:一種是使用runtime方法解決 另一種是使用NSProxy
錯誤方案:用weakSelf解決, 其實定時器內部是有strongSelf強引用的, 所以用weak無法解決定時器循環引用問題
(2)頁面滑動操作定時器停止
原因:默認的NSDefaultRunLoopMode在滑動視圖時會暫停定時器
解決方案:使用NSRunLoopCommonModes模式的Runloop即可解決
(3)NSTimer加到了RunLoop中但遲遲的不觸發事件
原因:①每一個線程都有它自己的runloop,程序的主線程會自動的使runloop生效,但對於我們自己新建的線程,它的runloop是不會自己運行起來,當我們需要使用它的runloop時,就得自己啓動 ②timer添加的時候,我們需要指定一個mode,因爲同一線程的runloop在運行的時候,任意時刻只能處於一種mode。所以只能當程序處於這種mode的時候,timer才能得到觸發事件的機會
解決方案:要讓timer生效,必須保證該線程的runloop已啓動,而且其運行的runloopmode也要匹配
(3)NSTimer不準時觸發事件(定時器不準!!!)
原因:程序是多線程的,而你的timer只是添加在某一個線程的runloop的某一種指定的runloopmode中,由於多線程通常都是分時執行的,而且每次執行的mode也可能隨着實際情況發生變化
解決方案:①納秒級精度的Timer,用mach_absolute_time()來實現更高精度的定時器 ②CADisplayLink是一個頻率能達到屏幕刷新率的定時器類。iPhone屏幕刷新頻率爲60幀/秒,也就是說最小間隔可以達到1/60s ③GCD定時器代替.因爲GCD定時器不受RunLoop影響.
(4)檢測內存泄漏方案
①靜態檢測方法 ②動態檢測方法instrument ③delloc ④騰訊三方庫MLeaksFinder
(6)性能優化
優化cpu佔有率, 提高用戶體驗(縮短加載時間, 確保幀數不會出現卡頓)
①重用池和懶加載 ②少用離屏渲染(如設置圓角不用Lab.layer.masksToBounds = true, 使用Core Graphics繪製, 還有設置陰影和光柵化也會觸發離屏渲染) ③CADisplayLink來測量幀率(大於60fpz即沒有卡頓感) ④Instuments, 靜態分析等方法檢測內存泄漏 ⑤減少UIWebView的使用 ⑥不阻塞主線程, 使用GCD等多線程技術 ⑦tableView預加載, 滑動流暢 ⑧MLeaksFinder檢測內存泄漏
(7)GCD
同步 阻塞當前線程 不會開闢新線程 dispatch_sync(queue, ^{ //回調 });
異步 不會阻塞當前線程 開闢新線程 dispatch_async(queue, ^{ //回調 });
串行 一個個執行 dispatch_queue_t queue = dispatch_queue_create("標識符", DISPATCH_QUEUE_SERIAL);
併發 多個同時執行 dispatch_queue_t queue = dispatch_queue_create("標識符", DISPATCH_QUEUE_CONCURRENT);
主隊列 異步隊列不會開闢線程 需要等到主線程中的任務執行完 纔會執行主隊列中的任務
GCD 柵欄方法:dispatch_barrier_async (例任務123,柵欄,456 能控制任務執行順序)
GCD 延時執行方法:dispatch_after (延遲調用,使用便捷)
GCD 一次性代碼(只執行一次):dispatch_once (寫單例等使用)
GCD 快速迭代方法:dispatch_apply (for循環添加異步併發)
GCD 的隊列組:dispatch_group (監聽 group 中任務的完成狀態,可以當上面所有的任務都執行完成後,才執行任務dispatch_group_notify)
GCD 信號量:dispatch_semaphore (保證線程安全,爲線程加鎖)
(8)鏈式編程,函數式編程,響應式編程
鏈式:msonry使用鏈式編程,先執行A方法,再執行B方法… 核心是點語法調用方法, 點語法傳參通過返回值block實現, 所以也使用了函數式編程
函數式:需要什麼block返回什麼
響應式:a=b+c 賦值之後 b 或者 c 的值變化後,a 的值不會跟着變化. 響應式編程,目標就是,如果 b 或者 c 的數值發生變化,a 的數值會同時發生變化; 標準應用:RAC框架(對KVO等效果的封裝)
(9)KVO,KVC,代理,通知區別
KVC,即Key-Value-Coding,是一個非正式協議,使用字符串(key)來訪問一個對象實例變量的機制
KVO,即Key-Value-Observing,它提供一種機制,當被觀察者的屬性值更改時,觀察者就會接收到通知
通知監聽不侷限於屬性的變化,還可以是狀態的變化,監聽範圍廣,例如鍵盤的出現、app進入後臺等,使用也更靈活方便
KVO和通知都負責發送和接收通知,剩下的事情都由系統來完成,所以不用返回值,而delegate則需要協議和代理對象來關聯
delegate適用於一對一,KVO和通知則適用於一對多情況, 代理效率更高
KVC和KVO實現的根本是OC語言的動態性和運行時runtime,以及訪問器方法的實現
(10)weak 關鍵字, 相比 assign 有什麼不同?
weak 在屬性所指的對象遭到摧毀時,系統會將 weak 修飾的屬性對象的指針指向 nil , 雖然assign不會增加引用計數但也不會自動至nil
assign內部還是添加了一層強引用
assign可以用於修飾非 OC 對象,而 weak 必須用於 OC 對象
(11)屬性@property的實質
@property = ivar + getter + setter;
@synthesize的語義是如果你沒有手動實現setter方法和getter方法,那麼編譯器會自動爲你加上這兩個方法。
@dynamic告訴編譯器,屬性的setter與getter方法由用戶自己實現。
(12)深淺拷貝的區別(Copy與MutableCopy) ✨這裏面繞彎彎的地方很多,想了解最好自己寫點邏輯測試,特別是淺拷貝的理解
簡單來說:淺拷貝複製容器,深拷貝複製容器及其內部元素
有兩種類型的對象拷貝,淺拷貝和深拷貝。正常的拷貝,生成一個新的容器,但卻是和原來的容器共用內部的元素(即內存地址相同),這叫做淺拷貝。深拷貝不僅生成新的容器,還生成了新的內部元素(即內部元素雖然和原對象內部元素數值相同,但生成新的內存地址,新內部元素指向新地址,和原地址元素無任何關係),這叫深拷貝
誤解:淺拷貝就是用copy,深拷貝就是用mutableCopy。如果有這樣的誤解,一定要更正過來。copy只是不可變拷貝,而mutableCopy是可變拷貝。比如,NSArray *arr = [modelsArray copy],那麼arr是不可變的。而NSMutableArray *ma = [modelsArray mutableCopy],那麼ma是可變的
這裏需要注意淺拷貝雖然是指針拷貝,但只要copy就會生出新容器,不會隨原內容改變而改變
注意:①對可變對象(mutable,model)無論使用copy還是mutableCopy(包括等號),都會深拷貝! 會生成新的內存地址 ②使用mutableCopy無論是可變還是不可變都是深拷貝! 會生成新的內存地址 ③對不可變對象使用copy,是淺拷貝,內部元素指向同一地址,容器類(數組,model)如果修改原數據的值,copy出來的值也會改變! ④copy和mutableCopy都是單層深拷貝,如數組套模型,只會copy數組元素,裏面的模型因爲沒有生成新的容器,指向相同內存地址,所以改變modelA會改變modelB , 但是單層拷貝層如果發生改變不會改變另一個
讓一個對象有copy功能:要想自定義對象可以複製,那麼該類就必須遵守NSCopying 或 NSMutableCopying協議, 實現協議中copyWithZone或者mutableCopyWithZone方法
(13)nonatomic 和 atomic
對於線程的安全,有nonatomic,這樣效率就更高了,但是不是線程安全的。如果要線程安全,可以使用atomic,這樣在訪問是就會有線程鎖。但atomic只能保證set方法的線程安全(加了鎖,效率會變低),並不是絕對的線程安全,所以在實際開發中很少使用,如果沒有添加系統默認是使用atomic和strong類型
(14)SDWebImage底層原理
SDWebImage是一個圖片加載和緩存的框架,通過三級緩存機制很好解決了圖片緩存問題
SDWebImage加載圖片的流程:
1、sd_setImageWithURL:placeholderImage:options: 會先把placeholderImage顯示,然後SDWebImageManager根據URL開始處理圖片
2、先從內存圖片緩存查找是否有圖片。若有顯示圖片
3、如果內存緩存中沒有,生成NSInvocationOperation添加到隊列開始從硬盤查找圖片是否已經緩存。若有顯示圖片
4、如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片,生成一個下載器SDWebImageDownloader開始下載圖片(異步下載)
5、下載完成後顯示圖片,且內存緩存和硬盤緩存同時保存。寫文件到硬盤也在以單獨NSInvocationOperation完成,避免拖慢主線程。
SDWebImage使用過程中可能存在的問題:
(1)Url鏈接的圖片改變,但未顯示新圖片(Url地址本身未變)
問題原因:圖片的緩存是根據url來設置的,在加載過程中找到了該Url對應的緩存,所以顯示以前的圖片
解決方法:①根據http,每個Url有一個ETag參數,是通過哈希編碼得到的,當資源發生變更時,那麼ETag也隨之發生變化,客戶端可以判斷NSURLCache來判斷該地址下圖片是否發生改變(sd對應的方法options:SDWebImageRefreshCached,注意是比正常請求多消耗性能的) ②不使用緩存
(2)圖片顯示錯亂的問題
問題原因:由於cell的重用導致,用戶下拉或者上拉,當網絡不好的情況,該cell的圖片還沒有被加載,但是對應的cell已經被顯示,就會顯示cell被重用之前的數據,造成數據混亂
解決方法:設置每個cell中image爲nil或者設置默認圖片
SDImageCache是怎麼做數據管理的?
SDImageCache分兩個部分,一個是內存層面的,一個是硬盤層面的。
內存層面的相當於是個緩存器,以Key-Value的形式存儲圖片,當內存不夠的時候回清除所有緩存圖片。
用搜索文件系統的方式做管理,文件替換方式是以時間爲單位,剔除時間大於一週的圖片文件
當SDWebImageManager向SDImageCache要資源時,先搜索內存層面的數據,如果有直接返回,沒有的話就去訪問磁盤,將圖片從磁盤讀取出來,然後做Decoder,將圖片對象放到內存層面做備份,再返回調用層。
SDWebImage詳解博客: