[toc]
1. 常見的多線程有幾種,區(qū)別在哪,優(yōu)缺點(diǎn)
常見的有NSThread,NSOperation & NSOperationQueue,GCD,Pthreads,其中Pthreads一般不會用到,NSThread多用于調(diào)試。
GCD是基于c的底層api,NSOperation底層通過GCD實(shí)現(xiàn)。
NSThread
–優(yōu)點(diǎn):NSThread 比其他兩個(gè)輕量級,使用簡單
–缺點(diǎn):需要自己管理線程的生命周期、線程同步、加鎖、睡眠以及喚醒等。線程同步對數(shù)據(jù)的加鎖會有一定的系統(tǒng)開銷NSOperation
–封裝完善,提供各種方便的Api??梢钥刂埔蕾嚕瑑?yōu)先級,繼承,鍵值對觀察、最大并發(fā)量這些;
支持KVO??梢员O(jiān)測operation是否正在執(zhí)行、是否結(jié)束,是否取消
提供方法可以取消還未執(zhí)行的線程。但是沒辦法做到取消一個(gè)正在執(zhí)行的線程;
–較GCD會多一點(diǎn)開銷,運(yùn)行效率較GCD慢些;
*GCD
-簡單高效,GCD執(zhí)行任務(wù)可以自由組裝,自由度比NSOperation高;直接使用GCD效率確實(shí)會更高效,NSOperation會多一點(diǎn)開銷
-未提供取消線程的方法等,功能沒有NSOperation全面
相關(guān)詳細(xì)內(nèi)容可參考:http://m.itdecent.cn/p/0b0d9b1f1f19
2.進(jìn)程與線程
進(jìn)程:
- 進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序關(guān)于某次數(shù)據(jù)集合的一次運(yùn)行活動,它是操作系統(tǒng)分配資源的基本單元.
- 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,就是一段程序的執(zhí)行過程,我們可以理解為手機(jī)上的一個(gè)app.
- 每個(gè)進(jìn)程之間是獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存空間內(nèi),擁有獨(dú)立運(yùn)行所需的全部資源
線程
- 程序執(zhí)行流的最小單元,線程是進(jìn)程中的一個(gè)實(shí)體.
- 一個(gè)進(jìn)程要想執(zhí)行任務(wù),必須至少有一條線程.應(yīng)用程序啟動的時(shí)候,系統(tǒng)會默認(rèn)開啟一條線程,也就是主線程
進(jìn)程和線程的關(guān)系
- 線程是進(jìn)程的執(zhí)行單元,進(jìn)程的所有任務(wù)都在線程中執(zhí)行
- 線程是 CPU 分配資源和調(diào)度的最小單位
- 一個(gè)程序可以對應(yīng)多個(gè)進(jìn)程(多進(jìn)程),一個(gè)進(jìn)程中可有多個(gè)線程,但至少要有一條線程
- 同一個(gè)進(jìn)程內(nèi)的線程共享進(jìn)程資源
3. 在多線程情況下,如何保證數(shù)據(jù)安全
使用線程同步技術(shù)解決,常見的線程同步技術(shù)就是加鎖,加鎖常見有以下幾種,@synchronized、NSLock、dispatch_semaphore(信號量)、NSRecursiveLock (遞歸鎖)、NSConditionLock(條件鎖)、pthread_mutex(互斥鎖)、OSSpinLock(自旋鎖)、NSCondition
其中@synchronized是一個(gè)對象層面的鎖,用法簡便,性能較差;
NSRecursiveLock遞歸鎖,這個(gè)鎖可以被同一線程多次請求,而不會引起死鎖。這主要是用在循環(huán)或遞歸操作中;
dispatch_semaphore信號量,會先將信號量減一,并判斷是否大于等于0,如果是,則返回0,并繼續(xù)執(zhí)行后續(xù)代碼,否則,使線程進(jìn)入睡眠狀態(tài),讓出cpu時(shí)間。直到信號量大于0或者超時(shí),則線程會被重新喚醒執(zhí)行后續(xù)操作;
NSConditionLock條件鎖,只有滿足一定條件的情況下才能打開這把鎖。lockWhenCondition::獲取鎖,如果condition與屬性相等,則可以獲得鎖,否則阻塞線程,等待被喚醒;unlockWithCondition:釋放鎖,并修改condition屬性。
pthread_mutex互斥鎖,同一時(shí)刻只能有一個(gè)線程獲得互斥鎖,其余線程處于掛起狀態(tài)
OSSpinLock自旋鎖,循環(huán)獲取鎖,在沒有獲得鎖的期間,b線程會一直處于忙等的狀態(tài),等待時(shí)會消耗大量 CPU 資源,但是可以節(jié)省喚醒時(shí)間和性能,所以用在對安全性要求不高,臨界區(qū)執(zhí)行時(shí)間比較短的環(huán)境
NSCondition,一種最基本的條件鎖。手動控制線程wait和signal。
針對性能,總的來說:
OSSpinLock和dispatch_semaphore的效率遠(yuǎn)遠(yuǎn)高于其他。
@synchronized和NSConditionLock效率較差。
鑒于OSSpinLock的不安全,所以我們在開發(fā)中如果考慮性能的話,建議使用dispatch_semaphore。
如果不考慮性能,只是圖個(gè)方便的話,那就使用@synchronized
參考:http://m.itdecent.cn/p/938d68ed832c
拓展:atomic并不能完全保證多線程安全問題,因?yàn)閍tomic即getter方法和setter內(nèi)部增加了線程同步鎖,只能保證方法內(nèi)部的線程同步,如果通過別的方式獲取到屬性進(jìn)行修改,比如下例,就會出現(xiàn)問題
NSMutableArray *arr = self.dataArr;//getter方法是安全的
for (int i = 0; i<5; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[arr addObject:@"1"];//這里會有多線程操作_dataArr,atomic無法保證這里的線程同步
});
}
4.死鎖是怎么出現(xiàn)的 必要條件
使用sync函數(shù)往當(dāng)前串行隊(duì)列中添加任務(wù),會卡住當(dāng)前的串行隊(duì)列
四個(gè)條件:
(1) 互斥條件:互斥條件:進(jìn)程對所分配到的資源不允許其他進(jìn)程進(jìn)行訪問,若其他進(jìn)程訪問該資源,只能等待,直至占有該資源的進(jìn)程使用完成后釋放該資源。
(2)請求與保持條件:進(jìn)程獲得一定的資源之后,又對其他資源發(fā)出請求,但是該資源可能被其他進(jìn)程占有,此事請求阻塞,但又對自己獲得的資源保持不放。
(3)不剝奪條件:是指進(jìn)程已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用完后自己釋放。
(4)循環(huán)等待條件:若干進(jìn)程之間形成的一種頭尾相接的循環(huán)等待資源關(guān)系。
解決方法:
(1)破壞請求與保持條件:一次性申請所有需要用到的資源。
(2)破壞不剝奪條件:占用部分資源的線程進(jìn)一步申請不到其他的資源時(shí),可以主動釋放它占有的資源。
(3)破壞循環(huán)等待條件:按某種序申請資源,釋放資源則反序釋放,破壞循環(huán)等待條件。
5.自動釋放池的原理 生命周期 創(chuàng)建和銷毀
自動釋放池轉(zhuǎn)成C++后發(fā)現(xiàn),構(gòu)造函數(shù)調(diào)用objc_autoreleasePoolPush(),~__AtAutoreleasePool() 析構(gòu)函數(shù)調(diào)用 objc_autoreleasePoolPop(),實(shí)際上就是調(diào)用AutoreleasePoolPage的push和pop兩個(gè)類方法
一個(gè) push 操作其實(shí)就是創(chuàng)建一個(gè)新的 autoreleasepool ,對應(yīng) AutoreleasePoolPage 的具體實(shí)現(xiàn)就是往 AutoreleasePoolPage 中的 next 位置插入一個(gè) POOL_SENTINEL ,并且返回插入的 POOL_SENTINEL 的內(nèi)存地址。
pop 函數(shù)的入?yún)⒕褪?push 函數(shù)的返回值,也就是 POOL_SENTINEL 的內(nèi)存地址即 pool token 。當(dāng)執(zhí)行 pop 操作時(shí),內(nèi)存地址在 pool token 之后的所有 autoreleased 對象都會被 release 。直到 pool token 所在 page 的 next 指向 pool token 為止。
一個(gè)線程的自動釋放池是一個(gè)指針堆棧
每一個(gè)指針或者指向被釋放的對象,或者是自動釋放池的 POOL_BOUNDARY,POOL_BOUNDARY 是自動釋放池的邊界。
一個(gè)池子的 token 是指向池子 POOL_BOUNDARY 的指針。當(dāng)池子被出棧的時(shí)候,每一個(gè)高于標(biāo)準(zhǔn)的對象都會被釋放掉。
堆棧被分成一個(gè)頁面的雙向鏈表。頁面按照需要添加或者刪除。
本地線程存放著指向當(dāng)前頁的指針,在這里存放著新創(chuàng)建的自動釋放的對象。
生命周期:
App一啟動,系統(tǒng)就會添加兩個(gè)Observer,監(jiān)聽主線程對應(yīng)的RunLoop,主要負(fù)責(zé)autoreleasepool的創(chuàng)建和銷毀。第一個(gè)Observer監(jiān)聽的是“即將進(jìn)入RunLoop”狀態(tài),它的回調(diào)里會創(chuàng)建一個(gè)autoreleasepool,這個(gè)Observer的優(yōu)先級最高,以此保證創(chuàng)建autoreleasepool發(fā)生在其它所有回調(diào)之前。第二個(gè)Observer監(jiān)聽的是“線程即將進(jìn)入休眠”和“剛剛退出RunLoop”兩個(gè)狀態(tài),“線程即將進(jìn)入休眠”時(shí)它的回調(diào)里會銷毀舊autoreleasepool并創(chuàng)建新autoreleasepool,“剛剛退出RunLoop”時(shí)它的回調(diào)里會銷毀autoreleasepool,這個(gè)Observer的優(yōu)先級最低,以此保證銷毀autoreleasepool發(fā)生在所有回調(diào)之后。
參考:
http://m.itdecent.cn/p/9dad9c3247ed
http://m.itdecent.cn/p/554c9fe0f041
6.App優(yōu)化有哪些方面 詳細(xì)介紹方案
內(nèi)存優(yōu)化、耗電優(yōu)化、啟動優(yōu)化、卡頓優(yōu)化、安裝包瘦身,網(wǎng)絡(luò)優(yōu)化等
- 內(nèi)存優(yōu)化
- 處理內(nèi)存泄漏等問題
- 在臨時(shí)創(chuàng)建大量對象時(shí),使用NSAutoreleasepool
- 正確運(yùn)用多線程技術(shù),控制多線程同時(shí)執(zhí)行并發(fā)量
- 正確使用cell復(fù)用和單例延遲加載(lazy load) Views
- 避免過于龐大的xib
- 根據(jù)實(shí)際情況選擇是否緩存圖片,是否需要緩存到內(nèi)存
- 減少離屏渲染,如UIButton設(shè)置圓角,UIImageView設(shè)置陰影效果等;避免對UIView使用透明
- 使圖片符合UIImageView的尺寸。不要在運(yùn)行的時(shí)候再讓UIImageView自行壓縮
- 處理低內(nèi)存警告
- 耗電優(yōu)化
App耗電主要關(guān)注以下幾個(gè)地方,CPU處理、網(wǎng)絡(luò)、定位、圖像
- CPU處理:
1). 盡可能少用定時(shí)器;
2). 優(yōu)化I/O操作(所謂的I/O操作也就是文件操作,我們簡稱為I/O操作。怎么優(yōu)化呢?盡量不要頻繁寫入小數(shù)據(jù),最好批量一次性寫入。讀寫大量主要的數(shù)據(jù)時(shí),考慮用dispatch_io,其提供了基于GCD的異步操作文件I/O的API。用dispatch_io系統(tǒng)會優(yōu)化磁盤訪問);
- 數(shù)據(jù)量比較大的,建議使用數(shù)據(jù)庫(SQlite、CoreData);
2.網(wǎng)絡(luò):
1). 減少、壓縮網(wǎng)絡(luò)數(shù)據(jù)。(不同格式數(shù)據(jù)提交關(guān)系:XML提交比較大;JSON 提交比較小,protobuf提交最?。?/p>
2).如果多次請求的結(jié)果相同,盡量使用緩存。NSMutableRequest 里面可以設(shè)置使用NSCache進(jìn)行緩存;
3). 盡量使用斷點(diǎn)續(xù)傳,否則網(wǎng)絡(luò)不穩(wěn)定的時(shí)候可能多次傳輸相同的內(nèi)容。(傳輸1M文件,如果一次性下載,一旦網(wǎng)絡(luò)問題下載失敗,下次重新請求,會從頭開始。之前下載過的部分會進(jìn)行重新下載,斷點(diǎn)續(xù)傳可以保證之前下載的數(shù)據(jù)緩存起來);
4). 網(wǎng)絡(luò)不可用,不要嘗試執(zhí)行網(wǎng)絡(luò)請求;
5). 讓用戶可以取消長時(shí)間運(yùn)行或者速度很慢的網(wǎng)絡(luò)操作,設(shè)置合適的超時(shí)時(shí)間;
6). 批量傳輸,比如下載視頻流時(shí),不要傳輸很小的數(shù)據(jù)包,直接下載整個(gè)文件或者一大塊一大塊地下載。如果下載廣告,一次性多下載一些,然后再慢慢展示。如果下載電子郵件,一次下載多封,不要一封一封地下載
- 定位:
1). 如果只是需要快速確定用戶的位置,最好用CLLocationManager的requestLocation方法。定位完成后,會自動讓定位硬件斷電;
2). 如果不是導(dǎo)航的應(yīng)用,盡量不要實(shí)時(shí)更新位置,定位完畢就關(guān)掉定位服務(wù);
3). 盡量降低定位精度,比如盡量不要使用精度最高的KCLLocationAccuracyBest;精度越高,硬件模塊功耗越大;
4). 需要后臺定位時(shí),盡量設(shè)置pauseLocationUpdatesAutomatically為YES,如果用戶不太可能移動的時(shí)候系統(tǒng)會自動暫停位置更新。
5). 盡量不要使用startMonitoringSignificantLocationChanges,優(yōu)先考慮startMonitoringForRegion:
- 圖像:
1). 圖片與imageView相同大小,避免多余運(yùn)算
2). 可以使用整副的圖片,增加應(yīng)用體積,但是節(jié)省CPU
- 啟動優(yōu)化
- 先說下啟動流程,APP的啟動可以分為2種
冷啟動(Cold Launch):從零開始啟動APP
熱啟動(Warm Launch):APP已經(jīng)在內(nèi)存中,在后臺存活著,再次點(diǎn)擊圖標(biāo)啟動APP
APP啟動時(shí)間的優(yōu)化,主要是針對冷啟動進(jìn)行優(yōu)化
通過添加環(huán)境變量可以打印出APP的啟動時(shí)間分析(Edit scheme -> Run -> Arguments)
DYLD_PRINT_STATISTICS設(shè)置為1
如果需要更詳細(xì)的信息,那就將DYLD_PRINT_STATISTICS_DETAILS設(shè)置為1
App的冷啟動大概分為三個(gè)階段,dyld,runtime,main
1). dyld(dynamic link editor),Apple的動態(tài)鏈接器,可以用來裝載Mach-O文件(可執(zhí)行文件、動態(tài)庫等)
啟動APP時(shí),dyld所做的事情有
裝載APP的可執(zhí)行文件(可執(zhí)行文件包含代碼和動態(tài)庫依賴信息),同時(shí)會遞歸加載所有依賴的動態(tài)庫
當(dāng)dyld把可執(zhí)行文件、動態(tài)庫都裝載完畢后,會通知Runtime進(jìn)行下一步的處理
2). runtime
啟動APP時(shí),runtime所做的事情有
調(diào)用map_images進(jìn)行可執(zhí)行文件內(nèi)容的解析和處理
在load_images中調(diào)用call_load_methods,調(diào)用所有Class和Category的+load方法
進(jìn)行各種objc結(jié)構(gòu)的初始化(注冊O(shè)bjc類 、初始化類對象等等)
調(diào)用C++靜態(tài)初始化器和attribute((constructor))修飾的函數(shù)
到此為止,可執(zhí)行文件和動態(tài)庫中所有的符號(Class,Protocol,Selector,IMP,…)都已經(jīng)按格式成功加載到內(nèi)存中,被runtime 所管理
總結(jié)一下,APP的啟動由dyld主導(dǎo),將可執(zhí)行文件加載到內(nèi)存,順便加載所有依賴的動態(tài)庫
并由runtime負(fù)責(zé)加載成objc定義的結(jié)構(gòu)
所有初始化工作結(jié)束后,dyld就會調(diào)用main函數(shù)
接下來就是UIApplicationMain函數(shù),AppDelegate的application:didFinishLaunchingWithOptions:方法
2.接下來介紹一下啟動優(yōu)化的點(diǎn):
按照不同的階段
dyld
減少動態(tài)庫、合并一些動態(tài)庫(定期清理不必要的動態(tài)庫)
定期清理不必要的類、分類, 裝載可執(zhí)行文件時(shí)候有加載類分類的操作.
runtime
少在+load方法里寫邏輯代碼,可以用+initialize方法和dispatch_once取代
main
在不影響用戶體驗(yàn)的前提下,盡可能將一些操作延遲,不要全部都放在finishLaunching方法中
1)把可以延遲執(zhí)行的邏輯,做延遲執(zhí)行處理;可以延遲加載的庫,做延遲加載處理
2)避免在首頁控制器的viewDidLoad和viewWillAppear做太多事情,這2個(gè)方法執(zhí)行完,首頁控制器才能顯示,部分可以延遲創(chuàng)建的視圖應(yīng)做延遲創(chuàng)建/懶加載處理。
3)首頁控制器用純代碼方式來構(gòu)建。
- 卡頓優(yōu)化
卡頓監(jiān)測:
平時(shí)所說的“卡頓”主要是因?yàn)樵谥骶€程執(zhí)行了比較耗時(shí)的操作
除了用xcode的Time profiler (程序耗時(shí)檢測), Core Animation(檢測刷新幀率)工具外,代碼層面也可以檢測.
可以添加Observer到主線程RunLoop中,通過監(jiān)聽RunLoop狀態(tài)切換的耗時(shí),以達(dá)到監(jiān)控卡頓的目的
檢測runloop間隔,打印主線程堆棧卡頓原因:
界面展示流程: cpu計(jì)算->GPU渲染->幀緩存->視頻控制器讀取->顯示到屏幕
在iOS中是雙緩沖機(jī)制,有前幀緩存、后幀緩存
每次垂直同步信號出來時(shí)候,把處理好的幀顯示在屏幕上.按照60FPS的刷幀率,每隔16ms就會有一次VSync信號
垂直同步信號來的時(shí)候,GPU如果沒有處理完成,只能將上一次處理好的幀顯示出來,這就是掉幀,等下一次垂直同步信號出來后,這個(gè)數(shù)據(jù)處理好后再顯示.
卡頓解決的主要思路
盡可能減少CPU、GPU資源消耗
- 卡頓解決方案
cpu優(yōu)化
1) 盡量用輕量級的對象,比如用不到事件處理的地方,可以考慮使用CALayer取代UIView(下劃線)
2) 不要頻繁地調(diào)用UIView的相關(guān)屬性,比如frame、bounds、transform等屬性,盡量減少不必要的修改
3) 盡量提前計(jì)算好布局,在有需要時(shí)一次性調(diào)整對應(yīng)的屬性,不要多次修改屬性
4) Autolayout會比直接設(shè)置frame消耗更多的CPU資源
5) 圖片的size最好剛好跟UIImageView的size保持一致
6) 控制一下線程的最大并發(fā)數(shù)量
7) 盡量避免日期格式轉(zhuǎn)換 [NSDate dateWithString:@"1990-11-11" format:@"yyyy-MM-dd"]
8) 盡量把耗時(shí)的操作放到子線程
文本處理(尺寸計(jì)算、繪制)
圖片處理(解碼、繪制)
9) tableView不要動態(tài)創(chuàng)建子控件,盡可能使用懶加載,盡量少設(shè)置透明度.
GPU優(yōu)化:
1)盡量避免短時(shí)間內(nèi)大量圖片的顯示,盡可能將多張圖片合成一張進(jìn)行顯示
GPU能處理的最大紋理尺寸是4096x4096,一旦超過這個(gè)尺寸,就會占用CPU資源進(jìn)行處理,所以紋理盡量不要超過這個(gè)尺寸
2)盡量減少視圖數(shù)量和層次
3)減少透明的視圖(alpha<1),不透明的就設(shè)置opaque為YES
4)盡量避免出現(xiàn)離屏渲染
在OpenGL中,GPU有2種渲染方式
On-Screen Rendering:當(dāng)前屏幕渲染,在當(dāng)前用于顯示的屏幕緩沖區(qū)進(jìn)行渲染操作
Off-Screen Rendering:離屏渲染,在當(dāng)前屏幕緩沖區(qū)以外新開辟一個(gè)緩沖區(qū)進(jìn)行渲染操作
(離屏渲染消耗性能的原因
需要創(chuàng)建新的緩沖區(qū)
離屏渲染的整個(gè)過程,需要多次切換上下文環(huán)境,先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上,又需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕)
5)根據(jù)實(shí)際情況控制圖片加載方式
- 安裝包瘦身優(yōu)化
安裝包(IPA)主要由可執(zhí)行文件(源代碼文件 編譯鏈接生產(chǎn)的)、資源(圖片 音視頻 stroyboard xib)組成
項(xiàng)目編譯完生產(chǎn)app文件,app文件壓縮后成IPA文件
1) 對資源文件進(jìn)行無損壓縮后再拖進(jìn)項(xiàng)目里使用
2) 清理項(xiàng)目中不再用到的資源文件
3) 編譯器優(yōu)化,去掉異常支持,Enable C++ Exceptions、Enable Objective-C Exceptions設(shè)置為NO, Other C Flags添加-fno-exceptions可以適當(dāng)?shù)販p小安裝包的體積
4) 手動移除代碼
5) 視頻/音頻 大圖片資源不要放到包里,可以從服務(wù)端下載.
6) 使用 xib/storyboard 來開發(fā)視圖界面會一定程序增加安裝包的大小。盡可能用代碼布局.
參考:
http://m.itdecent.cn/p/ea6c31fe837c
http://m.itdecent.cn/p/00e042ca2e72
http://m.itdecent.cn/p/870864e2a384
7.MVC與MVVM
MVVM是用ViewModel替代了蘋果版MVC Controller的角色。
不過MVVM和MVP不同的地方就在于ViewModel和View存在一個(gè)雙向綁定,具體地說就是:
ViewModel最基本的職責(zé)當(dāng)然是起到Presenter的調(diào)度作用,因此它會持有(綁定)View;
然后ViewModel之所以叫ViewModel,是因?yàn)樗€是一個(gè)用來供View顯示的實(shí)體,這個(gè)實(shí)體是擁有屬性的,它的屬性跟Model的屬性基本保持一致,我們在加載到Model之后,會把Model的每個(gè)屬性依次賦值給ViewModel相應(yīng)的屬性,而不是賦值給View去顯示。而是View會持有(綁定)這個(gè)ViewModel,并監(jiān)聽ViewModel屬性的變化來顯示和更新UI,這就使得View更有封裝性,同時(shí)顯示數(shù)據(jù)和更新數(shù)據(jù)更加靈活。(View監(jiān)聽ViewModel屬性的變化可以用RAC或者FaceBook的KVOController)
- Controller-ViewModel-Model 模式
1)建立協(xié)議,協(xié)議內(nèi)容為,當(dāng)VM數(shù)據(jù)發(fā)生變化,對應(yīng)的Controller需要進(jìn)行的操作。
@protocol SearchViewProtocol <NSObject>
// 刷新顯示
- (void)reloadTable;
@end
由Controller遵循,并實(shí)現(xiàn)協(xié)議
#pragma mark - View protocol
- (void)reloadTable {
[self.tableView reloadData];
}
- Controller里面,創(chuàng)建ViewModel屬性
@property (nonatomic, strong, readonly) SearchViewModel *vm;
完成初始化,并將Controller自己當(dāng)做參數(shù)傳入,實(shí)現(xiàn)雙向綁定
_vm = [[SearchViewModel alloc] initWithView:self];
3)在ViewModel里面,創(chuàng)建擁有Controller屬性,weak修飾
并建立初始化方法,在方法中完成賦值
@property (nonatomic, weak, readonly) id< SearchViewProtocol > view;
//ViewModel初始化,擁有Controller屬性
- (instancetype)initWithView:(id< SearchViewProtocol >)view;
4)當(dāng)Controller觸發(fā)點(diǎn)擊事件等時(shí),調(diào)用ViewModel提供的方法完成數(shù)據(jù)處理,再通過SearchViewProtocol協(xié)議,使用代理完成Controller事件回傳
- View-ViewModel-Model 模式
參考:http://m.itdecent.cn/p/0d7fb890d3b6
8.深拷貝和淺拷貝 集合類深拷貝怎么實(shí)現(xiàn)
copy:copy拷貝出來的對象類型總是不可變類型(例如, NSString, NSDictionary, NSArray等等)
mutableCopy拷貝出來的對象類型總是可變類型(例如, NSMutableString, NSMutableDictionary, NSMutableArray等等)
對象要想具有copy和mutablecopy功能要是NSCopying和NSMutableCopy協(xié)議,實(shí)現(xiàn)copywithzone和mutablecopywithzone
- 非集合類對象:
對immutable對象進(jìn)行copy操作,是指針復(fù)制mutableCopy操作時(shí)內(nèi)容復(fù)制
對mutable對象進(jìn)行copy和mutableCopy都是內(nèi)容復(fù)制
可變對象通過copy之后就變成不可變了對象。
2.集合類對象:
在集合類對象中,對immutable對象進(jìn)行copy,是指針復(fù)制,mutableCopy是內(nèi)容復(fù)制。
在集合類對象中,對mutable對象進(jìn)行copy和mutableCopy都是內(nèi)容復(fù)制。
在集合類對象中,對對象進(jìn)行copy的對象就是不可變的,進(jìn)行mutable就是可變的
但是,對于集合對象的內(nèi)容復(fù)制僅僅是對對象本身,但是對象的里面的元素還是指針復(fù)制。要想復(fù)制整個(gè)集合對象,就要用集合深復(fù)制的方法,有兩種:
(1)使用initWithArray:copyItems:方法,將第二個(gè)參數(shù)設(shè)置為YES即可
NSDictionary shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:YES];
(2)將集合對象進(jìn)行歸檔(archive)然后解歸檔(unarchive):
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
參考:
http://m.itdecent.cn/p/1a553d58707b?utm_campaign=hugo
https://blog.csdn.net/u012094456/article/details/102951796
9.KVO原理
1、KVO是關(guān)于runtime機(jī)制實(shí)現(xiàn)的
2、當(dāng)某個(gè)類的對象屬性第一次被觀察時(shí),系統(tǒng)就會在運(yùn)行期動態(tài)地創(chuàng)建該類的一個(gè)派生類,在這個(gè)派生類中重寫基類中任何被觀察屬性的setter方法。派生類在被重寫的setter方法內(nèi)實(shí)現(xiàn)真正的通知機(jī)制
3、如果原類為Person,那么生成的派生類名為NSKVONotifying_Person
4、每個(gè)類對象中都有一個(gè)isa指針指向當(dāng)前類,當(dāng)一個(gè)類對象的第一次被觀察,那么系統(tǒng)就會偷偷講isa指針指向動態(tài)生成的派生類,從而在給被監(jiān)控屬性復(fù)制是執(zhí)行的是派生類的setter方法
5、鍵值觀察通知依賴于NSObject的兩個(gè)方法:willChangeValueForKey:和didChangeValueForKey:,在一個(gè)被觀察屬性發(fā)生改變之前,willChangeValueForkey:和didChangeValueForKey:;在一個(gè)被觀察屬性發(fā)生改變之前,willChangeValueForKey:一定會被調(diào)用,這就會記錄舊的值。而當(dāng)改變發(fā)生后,didChangeValueForKey:會被調(diào)用,繼而observeValueForKey:ofObject:change:context:也會被調(diào)用
參考:https://blog.csdn.net/github_38318102/article/details/80651506
10.KVC底層實(shí)現(xiàn)
KVC賦值的底層實(shí)現(xiàn)
1.按照setKey和_setKey的順序找set方法賦值。
2.如果找不到set方法,就調(diào)用accessinstancevariablesdirectly獲取返回值。
3.返回YES,按照_key,_isKey,key,isKey的順序找成員變量賦值。
4.返回NO,拋出異常Nsunknownkevexception。
5.如果四個(gè)成員變量也找不到,就拋出異常Nsunknownkevexception。

KVC取值的底層實(shí)現(xiàn)
1.按照getKey,key,isKey,_key的順序找get方法取值。
2.如果找不到上述四個(gè)get方法,就調(diào)用accessinstancevariablesdirectly獲取返回值。
3.返回YES,按照_key,_isKey,key,isKey的順序找成員變量取值。
4.返回NO,拋出異常Nsunknownkevexception。
5.如果四個(gè)成員變量也找不到,就拋出異常Nsunknownkevexception。

補(bǔ)充
1.accessinstancevariablesdirectly方法返回是否允許訪問成員變量,默認(rèn)返回YES。
2.@property修飾的成員變量會自動生成對應(yīng)的getter和setter方法。
3.通過KVC修改屬性會觸發(fā)KVO,因?yàn)镵VC內(nèi)部調(diào)用了willChangeValueForKey和didChangeValueForKey方法。
參考:https://blog.csdn.net/weixin_31064353/article/details/113897899
11.NSTimer解決循環(huán)引用問題
NSTimer創(chuàng)建的定時(shí)器,使用時(shí)會造成循環(huán)引用(target對self做了強(qiáng)引用,self又對timer進(jìn)行了強(qiáng)引用),從而導(dǎo)致內(nèi)存泄漏
有以下幾種方法來解決循環(huán)引用問題:
- 合適的時(shí)機(jī)關(guān)閉定時(shí)器
#pragma mark - 第一種方法:合適的時(shí)機(jī)關(guān)閉定時(shí)器
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (!parent) {
[_timer invalidate];
_timer = nil;
}
}
- 更改target,并給target通過Runtime添加方法
// 添加屬性
@property (nonatomic, strong) id target;
// 第二種方法
_target = [NSObject new];
class_addMethod([_target class], @selector(run), class_getMethodImplementation([self class], @selector(run)), "v@:");
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_target selector:@selector(fire) userInfo:nil repeats:YES];
- (void)dealloc {
if (_timer) {
[_timer invalidate];
_timer = nil;
}
}
- (void)run
{
NSLog(@"Hello");
}
- 通過使用NSProxy解決循環(huán)引用
新建一個(gè)FKProxy繼承自NSProxy,添加屬性target,實(shí)現(xiàn)方法
@property (nonatomic, weak) id target;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
把timer的target改為FKProxy類型的對象
@property (nonatomic, strong) FKProxy *fkProxy;
_fkProxy = [FKProxy alloc]; //FKProxy只有alloc方法,沒有init方法
_fkProxy.target = self;
_timer3 = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_fkProxy selector:@selector(run) userInfo:nil repeats:YES];
- 添加一個(gè)NSTimer的分類,使用block解決循環(huán)應(yīng)用
分類實(shí)現(xiàn)方法
+ (NSTimer *)fk_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block {
void (^inBlock)(void) = [block copy];
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(fk_blockHandle:) userInfo:inBlock repeats:repeats];
}
+ (void)fk_blockHandle:(NSTimer *)timer
{
if (timer.userInfo) {
void(^block)(void) = (void (^)(void))timer.userInfo;
block();
}
}
使用分類方法創(chuàng)建定時(shí)器
__weak typeof (self) weakSelf = self;
_timer4 = [NSTimer fk_scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
__strong typeof (weakSelf) strongSelf = weakSelf;
[strongSelf run];
}];
參考:http://m.itdecent.cn/p/722464260b9e
12.iOS APP生命周期 和 UIViewController的生命周期
http://m.itdecent.cn/p/1f6820a7d3fd