iOS開發常用技術底層實現(精簡概述)

(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)  ⑥系統類添加自定義方法, 寫一些更便捷的代碼,比如控件加手勢,字典加加密方法,代碼更簡潔 



OC對象的本質<二> 實例對象,類對象,元類對象

runtime - iOS類對象、實例對象、元類對象

(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詳解博客:

iOS開發之SDWebImage原理 - 簡書

iOS-SDWebimage底層實現原理 - 木子沉雨 - 博客園

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章