2016.05.20 10:24
塵封已久的學(xué)習(xí)基礎(chǔ)總結(jié),最近公司項(xiàng)目不是很忙,終于抽空整理出來,現(xiàn)分享出來。
1.1 談一談GCD和NSOperation的區(qū)別?
- 首先二者都是多線程相關(guān)的概念,當(dāng)然在使用中也是根據(jù)不同情境進(jìn)行不同的選擇;
- GCD是將任務(wù)添加到隊(duì)列中(串行/并發(fā)/主隊(duì)列),并且制定任務(wù)執(zhí)行的函數(shù)(同步/異步),其性能最好,底層是C語言的API,也更輕量級(jí)。iOS4.0以后推出的,針對(duì)多核處理器的并發(fā)技術(shù),只能設(shè)置某一個(gè)隊(duì)列的優(yōu)先級(jí),常用的功能包括
一次性執(zhí)行 dispatch_once
,延遲操作dispatch_after
(這里是延遲推到線程中,而不是在線程中等待,因此比如設(shè)置延遲1秒執(zhí)行,但是一秒后只是推到了線程中,不會(huì)立刻執(zhí)行),調(diào)度組等,其高級(jí)功能有-
dispatch_barrier_async
柵欄來控制異步操作的順序 -
dispatch_apply
充分利用多核進(jìn)行快速迭代遍歷 -
dispatch_group_t
隊(duì)列組,添加到隊(duì)列組中的任務(wù)完成之后會(huì)調(diào)用dispatch_group_notify
函數(shù),可以實(shí)現(xiàn)類似A、B兩個(gè)耗時(shí)操作都完成之后,去主線程更新UI的操作
-
- NSOperation把操作(異步)添加到隊(duì)列中(全局的并發(fā)隊(duì)列),是OC框架,更加面向?qū)ο螅菍?duì)GCD的封裝,iOS2.0推出,蘋果推出GCD之后,對(duì)NSOperation的底層全部重寫,可以隨時(shí)取消已經(jīng)設(shè)定準(zhǔn)備要執(zhí)行的任務(wù),已經(jīng)執(zhí)行的除外,可以設(shè)置隊(duì)列中每一個(gè)操作的優(yōu)先級(jí),其基本功能包括設(shè)置最大操作并發(fā)數(shù)
maxConcurrentOperationCount
,繼續(xù)/暫停/全部取消,可以快隊(duì)列設(shè)置操作的依賴關(guān)系,通過KVO監(jiān)聽 NSOperation 對(duì)象的屬性,如 isCancelled、isFinished;對(duì)象可重用。-
NSInvocationOperation
和NSBlockOperation
創(chuàng)建方法等這些基礎(chǔ)面試官往往默認(rèn)你是會(huì)的 -
NSOperationQueue
只有兩種隊(duì)列:主隊(duì)列、其他隊(duì)列。其他隊(duì)列包含了串行和并發(fā) -
NSOperation + NSOperationQueue
將任務(wù)加入到隊(duì)列 - 操作依賴:
[operation2 addDependency:operation1];
(operation2 依賴于operation1的完成,但這兩個(gè)任務(wù)要加入到同一個(gè)隊(duì)列中)
-
1.2 談?wù)劧嗑€程的應(yīng)用
通常耗時(shí)的操作都放在子線程處理,然后到主線程更新UI,如
- 我們要從數(shù)據(jù)庫提取數(shù)據(jù)還要將數(shù)據(jù)分組后顯示,那么就會(huì)開個(gè)子線程來處理,處理完成后才去刷新UI顯示。
- 拍照后,會(huì)在子線程處理圖片,完成后才回到主線程來顯示圖片。拍照出來的圖片太大了,因此要做處理。
- 音頻、視頻處理會(huì)在子線程來操作
- 文件較大時(shí),文件操作會(huì)在子線程中處理
- 做客戶端與服務(wù)端數(shù)據(jù)同步時(shí),會(huì)在后臺(tái)閑時(shí)自動(dòng)同步
2. 線程之間是如何通信的?
- 通過主線程和子線程切換的時(shí)候傳遞參數(shù)performSelecter:onThread:withObject:waitUntilDone:
3. 網(wǎng)絡(luò)圖片處理問題怎么解決圖片重復(fù)下載問題?(SDWebImage大概實(shí)現(xiàn)原理)
這個(gè)就需要用到字典,以圖片的下載地址url為key,下載操作為value,所有的圖片大概分成三類:已經(jīng)下載好的,正在下載的和將要下載的;
當(dāng)一張圖片將要進(jìn)行下載操作的時(shí)候,先判斷緩存中是否有相同的圖片,如果有的話就返回,沒有的話就根據(jù)url的md5加密值去沙盒中找,有的話就拿出來用,沒有的話再去以圖片的url為key去字典中找有沒有正在進(jìn)行的任務(wù),最后去判斷等待的下載操作任務(wù)里面的字典有無相同key,如果沒有,就自己開啟任務(wù),記錄一下,文件保存的名稱是url的md5值
-
這里建立了兩個(gè)字典 :
1.iconCache:保存緩存的圖片
2.blockOperation 用來保存下載任務(wù)
653183AF-3074-47AD-8460-10B5CEF1323C.png 每當(dāng)進(jìn)入或退出程序時(shí),會(huì)進(jìn)行圖片文件的管理:超過一星期的文件會(huì)被清除,如果設(shè)置了最大緩存,超過這個(gè)緩存就會(huì)刪除最舊的文件,直到當(dāng)前緩存文件為最大緩存文件的一半大小;
一般app中大部分緩存都是圖片的情況下,可以直接調(diào)用clear方法進(jìn)行清除緩存,getSize()方法獲取當(dāng)前緩存大小。
4. 多線程安全的幾種解決方法?
- 1> 只有在主線程刷新訪問UI
- 2> 如果要防止資源搶奪,需要用synchronize進(jìn)行加鎖保護(hù)
- 3> 如果是異步操作要保證線程安全等問題,盡量使用GCD(有些函數(shù)默認(rèn)就是安全的)
- 4> 單例為什么用static dispatch_once?使用dispatch_once可以簡(jiǎn)化代碼并且徹底保證線程安全,開發(fā)者無需擔(dān)心加鎖或同步。此外,dispatch_once更高效,它沒有使用重量級(jí)的同步機(jī)制,若是那樣做的話,每次運(yùn)行代碼前都要獲取鎖。
5. 原子屬性
- 原子屬性采用的是"多讀單寫"機(jī)制的多線程策略;"多讀單寫"縮小了鎖范圍,比互斥鎖的性能好
- 規(guī)定只在主線程更新UI,就是因?yàn)槿绻诙嗑€程中更新,就需要給UI對(duì)象加鎖,防止資源搶占寫入錯(cuò)誤,但是這樣會(huì)降低UI交互的性能,所以ios設(shè)計(jì)讓所有UI對(duì)象都是非線程安全的(不加鎖)
6. 代理的作用、block
- 代理又叫委托,是一種設(shè)計(jì)模式(可以理解為java中回調(diào)監(jiān)聽機(jī)制),代理是對(duì)象與對(duì)象之間的通信交互,代理解除了對(duì)象之間的耦合性
- 改變或傳遞控制鏈,允許一個(gè)類在某些特定時(shí)刻通知到其他類,而不需要獲取到那些類的指針,可以減少框架復(fù)雜度
- 代理的屬性常是 weak 的原因:防止循環(huán)引用,以致對(duì)象無法得到正確的釋放(具體原因會(huì)在下文第十一條闡述)
- block底層是根據(jù)函數(shù)指針和結(jié)構(gòu)體結(jié)合實(shí)現(xiàn)的,block本身就是結(jié)構(gòu)體,更加簡(jiǎn)潔,不需要定義繁瑣的協(xié)議方法,但通信事件比較多的話,建議使用Delegate
- block就是一個(gè)數(shù)據(jù)類型,存放一段代碼,編譯的時(shí)候不會(huì)執(zhí)行,只有用到的時(shí)候才會(huì)去執(zhí)行里面的代碼。聲明的時(shí)候使用copy是因?yàn)橐獜臈^(qū)拷貝到堆區(qū),在棧區(qū)會(huì)受到作用域的限制,超出所在的函數(shù)就會(huì)被銷毀,就沒辦法進(jìn)行傳值回調(diào)等一系列操作了。應(yīng)注意循環(huán)引用,__weak來修飾。如果一個(gè)變量是在block外部創(chuàng)建,需要在block內(nèi)部修改,那么需要使用__block修飾這個(gè)變量(__block可以在ARC和MRC情況下使用,可以修飾對(duì)象和基本數(shù)據(jù)類型,__weak只能在ARC下使用,只能修飾對(duì)象,不能修飾基本數(shù)據(jù)類型)
- 最常用的是使用block作為參數(shù)傳值,不同情況下回調(diào)不同的代碼(如成功回調(diào)失敗回調(diào))
7. 談?wù)勀銓?duì)runTime運(yùn)行時(shí)機(jī)制的了解(注意哦,這個(gè)很重要的)
- runtime是一套比較底層的純C語言API,屬于一個(gè)C語言庫,包含了很多底層的C語言的API
- 平時(shí)編寫的OC代碼,在程序運(yùn)行過程中,其實(shí)都是轉(zhuǎn)成了runtime的C語言代碼,runtime是OC的幕后工作者,底層語言,例如:
- OC--> [[WPFPerson alloc] init]
- runtime-->objc_msgSend(objc_msgSend("WPFPerson", "alloc"), "init")
- 利用runtime可以實(shí)現(xiàn)一些非常底層的操作(用OC不好實(shí)現(xiàn))
- 在程序運(yùn)行過程中,動(dòng)態(tài)創(chuàng)建一個(gè)類(比如KVO底層實(shí)現(xiàn):檢測(cè)isa指針,發(fā)現(xiàn)是新建了一個(gè)類,當(dāng)然Xcode7.0以前的版本才可以監(jiān)聽到isa指針)
- 遍歷一個(gè)類的所有成員變量、方法,訪問私有變量(先通過runtime的class_getInstanceVariable獲取成員變量,再通過class_getIvar獲取它的值)
- 在程序運(yùn)行過程中,動(dòng)態(tài)為某個(gè)類添加屬性\方法,修改屬性值\方法,比如產(chǎn)品經(jīng)理需要跟蹤記錄APP中按鈕的點(diǎn)擊次數(shù)和頻率等數(shù)據(jù),可以通過集成按鈕或者類別實(shí)現(xiàn),但是帶來的問題比如別人不一定去實(shí)例化你寫的子類,或者其他類別也實(shí)現(xiàn)了點(diǎn)擊方法導(dǎo)致不確定會(huì)調(diào)用哪一個(gè),runtime可以這樣解決:在按鈕的分類里面,重寫load方法,新建監(jiān)控按鈕點(diǎn)擊的方法,先用class_addMethod方法,判斷其返回的bool值,如果添加成功,就用class_replaceMethod將原來的方法移除,如果添加失敗,就用method_exchangeImplementations方法進(jìn)行替換
- 攔截并替換方法,比如由于某種原因,我們要改變這個(gè)方法的實(shí)現(xiàn),但是又不能動(dòng)它的源碼(比如一些開源庫出現(xiàn)問題的時(shí)候,這時(shí)候runtime就可以出場(chǎng)了)-->先增加一個(gè)tool類,然后寫一個(gè)我們自己實(shí)現(xiàn)的方法-change,通過runtime的class_getInstanceMethod獲取兩個(gè)方法,在用class_replaceMethod方法進(jìn)行替換。防止數(shù)組越界的方法:數(shù)組越界的時(shí)候報(bào)錯(cuò)的方法是add_object,做一個(gè)邏輯判斷,越界的時(shí)候通過class_replaceMethod交換掉add_object(相當(dāng)于重寫了這個(gè)方法)
- 相關(guān)應(yīng)用
- NSCoding(歸檔和解檔),如果一個(gè)模型有很多個(gè)屬性,那么需要對(duì)每個(gè)屬性都實(shí)現(xiàn)一遍encodeObject和decodeObjectForKey方法,十分麻煩,但是如果使用class_copyIvarList獲取所有屬性,然后循環(huán)遍歷,使用[ivarName substringFromIndex:1]去掉成員變量下劃線
- 字典轉(zhuǎn)模型:像幾個(gè)出名的開源庫JSONModel、MJExtension等都是通過這種方式實(shí)現(xiàn)的(利用runtime的class_copyIvarList獲取屬性數(shù)組,遍歷模型對(duì)象的所有成員屬性,根據(jù)屬性名找到字典中key值進(jìn)行賦值,當(dāng)然這種方法只能解決NSString、NSNumber等,如果含有NSArray或NSDictionary,還要進(jìn)行第二步轉(zhuǎn)換,如果是字典數(shù)組,需要遍歷數(shù)組中的字典,利用objectWithDict方法將字典轉(zhuǎn)化為模型,在將模型放到數(shù)組中,最后把這個(gè)模型數(shù)組賦值給之前的字典數(shù)組)
- Method Swizzling:OC中調(diào)用方法事實(shí)上就是向?qū)ο蟀l(fā)送消息,而查找消息的唯一依據(jù)就是selector的名字,因此可以使用runtime運(yùn)行時(shí)機(jī)制動(dòng)態(tài)交換方法。在+load方法里面調(diào)換,因?yàn)閙ethod swizzling的影響范圍是全局的,所以應(yīng)該放在最保險(xiǎn)的地方來處理,+load方法能夠保證能在類初始化的時(shí)候一定能被調(diào)用,可以保證統(tǒng)一性,如果是在使用的時(shí)候才去調(diào)用,可能達(dá)不到全局處理的效果;使用dispatch_once保證只交換一次。[objc_getClass("__NSArrayM") swizzleSelector:@selector(addObject:)
withSwizzledSelector:@selector(wpf_safeAddObject:)];使用場(chǎng)景:addObject方法添加的值為nil的時(shí)候會(huì)崩潰。調(diào)用objectAtIndex:時(shí)出現(xiàn)崩潰提示empty數(shù)組問題
8. 談?wù)勀銓?duì)Run Loop的理解
- RunLoop是多線程的一個(gè)很重要的機(jī)制,就是一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完任務(wù)后就會(huì)退出線程。主線程會(huì)通過do-while死循環(huán)讓程序持續(xù)等待下一個(gè)任務(wù)不退出。通過mach_msg()讓runloop沒事時(shí)進(jìn)入trap狀態(tài),節(jié)省CPU資源。非主線程通常來說就是為了執(zhí)行某個(gè)任務(wù)而創(chuàng)建的,執(zhí)行完就會(huì)歸還資源,因此默認(rèn)不開啟RunLoop
- 實(shí)質(zhì)上,對(duì)于子線程的runloop是默認(rèn)不存在的,因?yàn)樘O果采用了懶加載的方式,如果沒有手動(dòng)調(diào)用[NSRunLoop currentRunLoop]的話,就不會(huì)去查詢當(dāng)前線程的RunLoop,也不會(huì)創(chuàng)建、加載
- 當(dāng)然如果子線程處理完某個(gè)任務(wù)后不退出,需要繼續(xù)等待接受事件,需要啟動(dòng)的時(shí)候也可以手動(dòng)啟動(dòng),比如說添加定時(shí)器的時(shí)候就要手動(dòng)開始RunLoop
如何處理事件
-
界面刷新: 當(dāng)UI改變( Frame變化、 UIView/CALayer 的繼承結(jié)構(gòu)變化等)時(shí),或手動(dòng)調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,這個(gè) UIView/CALayer 就被標(biāo)記為待處理。 蘋果注冊(cè)了一個(gè)用來監(jiān)聽BeforeWaiting和Exit的Observer,在它的回調(diào)函數(shù)里會(huì)遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實(shí)際的繪制和調(diào)整,并更新 UI 界面。
- setNeedsLayout:刷新布局(不會(huì)立刻刷新,等下次runloop)
- setNeedsDisplay:調(diào)用drawRect方法,重繪UI(不會(huì)立刻刷新,等下次runloop)
- layoutIfNeeded:立刻刷新布局
手勢(shì)識(shí)別: 如果上一步的 _UIApplicationHandleEventQueue() 識(shí)別到是一個(gè)guesture手勢(shì),會(huì)調(diào)用Cancel方法將當(dāng)前的touchesBegin/Move/End 系列回調(diào)打斷。隨后系統(tǒng)將對(duì)應(yīng)的 UIGestureRecognizer 標(biāo)記為待處理。 蘋果注冊(cè)了一個(gè) Observer 監(jiān)測(cè) BeforeWaiting (Loop即將進(jìn)入休眠) 事件,其回調(diào)函數(shù)為 _UIGestureRecognizerUpdateObserver(),其內(nèi)部會(huì)獲取所有剛被標(biāo)記為待處理的 GestureRecognizer,并執(zhí)行GestureRecognizer的回調(diào)。 當(dāng)有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時(shí),這個(gè)回調(diào)都會(huì)進(jìn)行相應(yīng)處理。
網(wǎng)絡(luò)請(qǐng)求:最底層是CFSocket層,然后是CFNetwork將其封裝,然后是NSURLConnection對(duì)CFNetwork進(jìn)行面向?qū)ο蟮姆庋b。當(dāng)網(wǎng)絡(luò)開始傳輸時(shí),NSURLConnection創(chuàng)建了兩個(gè)新線程:com.apple.NSURLConnectionLoader和com.apple.CFSocket.private。其中CFSocket線程是處理底層socket連接的。NSURLConnectionLoader這個(gè)線程內(nèi)部會(huì)使用RunLoop來接受底層socket的事件,并添加到上層的Delegate
應(yīng)用
滑動(dòng)與圖片刷新:當(dāng)tableView的cell上有需要從網(wǎng)絡(luò)獲取的圖片的時(shí)候,滾動(dòng)tableView,異步線程回去加載圖片,加載完成后主線程會(huì)設(shè)置cell的圖片,但是會(huì)造成卡頓。可以設(shè)置圖片的任務(wù)在CFRunloopDefaultMode下進(jìn)行,當(dāng)滾動(dòng)tableView的時(shí)候,Runloop切換到UITrackingRunLoopMode,不去設(shè)置圖片,而是而是當(dāng)停止的時(shí)候,再去設(shè)置圖片。(在viewDidLoad中調(diào)用self.imageView performSelector@selector(setImage) withObject:...afterDelay:...inModes@[NSDefayltRunLoopMode])
常駐子線程,保持子線程一直處理事件 為了保證線程長(zhǎng)期運(yùn)轉(zhuǎn),可以在子線程中加入RunLoop,并且給Runloop設(shè)置item,防止Runloop自動(dòng)退出
10. 關(guān)于Socket,談?wù)凾CP/IP 和 UDP的理解
- Socket是一個(gè)用于傳輸網(wǎng)絡(luò)數(shù)據(jù)的工具,TCP/IP 和 UDP都是傳輸協(xié)議,用于定義網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)母袷剑瑢儆陂L(zhǎng)連接
- TCP/IP 側(cè)重可靠傳輸,傳輸速度慢,不會(huì)丟失數(shù)據(jù),安全,聊天和下載文件時(shí)用到
- UDP:側(cè)重快速傳輸,傳輸速度快,容易丟失數(shù)據(jù)包,不安全。局域網(wǎng)游戲和網(wǎng)絡(luò)游戲,視頻聊天的時(shí)候用到
- TCP更安全是因?yàn)橛幸粋€(gè)三次握手:
- 第一次握手(客戶端發(fā)送syn包到服務(wù)器,并進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器確認(rèn))
- 第二次握手(服務(wù)器收到syn包,必須確認(rèn)客戶的SYN包,同時(shí)自己發(fā)送一個(gè)SYN+ACK包(或否認(rèn)應(yīng)答NACK),此時(shí)服務(wù)器進(jìn)入SYN_RECV狀態(tài))
- 第三次握手(客戶端收到服務(wù)器的SYN+ACK包,向服務(wù)器發(fā)送確認(rèn)包ACK,發(fā)送完畢后服務(wù)器和客戶端都進(jìn)入ESTABLISHED狀態(tài),完成三次握手),三次握手之后才開始正式傳輸數(shù)據(jù)。
- TCP有順序控制的功能:通過序列號(hào)來確認(rèn)發(fā)送的數(shù)據(jù)。比如第一次握手傳1000,第二次握手服務(wù)端返回3000,那么就代表服務(wù)端確認(rèn)收到了2000個(gè)字節(jié)
- 那么現(xiàn)在即時(shí)通訊更適合用TCP還是UDP?
- 早期使用MSN是使用TCP協(xié)議的,QQ使用采用UDP的,但并不代表UDP就不安全,因?yàn)榭梢允謩?dòng)對(duì)UDP的數(shù)據(jù)收發(fā)進(jìn)行驗(yàn)證(比如發(fā)送方對(duì)每個(gè)數(shù)據(jù)包進(jìn)行編號(hào)然后由接收方進(jìn)行驗(yàn)證),正是因?yàn)檫@個(gè),QQ的傳遞速度是遠(yuǎn)遠(yuǎn)快于MSN,可能這也是打敗MSN的其中一個(gè)原因吧
- Http:超文本傳輸協(xié)議,用于定義網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)母袷?短鏈接)http1.0之前不支持短連接,1.1之后默認(rèn)就是長(zhǎng)連接,只要在服務(wù)器和客戶端同時(shí)設(shè)置Connection為keep-alive即可
- 長(zhǎng)連接是為了復(fù)用,長(zhǎng)連接指的是TCP連接,也就是為了復(fù)用TCP連接,也就是說多個(gè)HTTP請(qǐng)求可以復(fù)用一個(gè)TCP連接,節(jié)省了很多TCP連接建立和斷開的消耗
- 比如請(qǐng)求了一個(gè)網(wǎng)頁,這個(gè)網(wǎng)頁肯定還包含了CSS、JS等一系列資源,如果是短連接的話,每次打開一個(gè)網(wǎng)頁,基本要建立幾個(gè)甚至幾十個(gè)TCP連接,浪費(fèi)了大量資源
- 長(zhǎng)連接不是永久連接,如果一段時(shí)間內(nèi),具體的時(shí)間長(zhǎng)短,是可以在header當(dāng)中進(jìn)行設(shè)置的,也就是所謂的超時(shí)時(shí)間,這個(gè)連接沒有HTTP請(qǐng)求發(fā)出的話,那么這個(gè)長(zhǎng)連接就會(huì)被斷掉
- socket連接是長(zhǎng)連接,客戶端與服務(wù)器保持通道,雙方可以主動(dòng)發(fā)送數(shù)據(jù),一般多用于即時(shí)通訊,游戲,默認(rèn)超時(shí)時(shí)間是30秒,默認(rèn)大小是8k(一個(gè)數(shù)據(jù)包大小)
11. 談一談內(nèi)存管理
- iOS的內(nèi)存管理分為 MRC 和 ARC,管理的是堆區(qū)動(dòng)態(tài)產(chǎn)生的對(duì)象,基本數(shù)據(jù)類型就不是內(nèi)存管理的范圍
- 內(nèi)存管理的核心概念是引用計(jì)數(shù)器:當(dāng)對(duì)象被alloc、copy、new的時(shí)候,引用計(jì)數(shù)器+1,當(dāng)被release的時(shí)候引用計(jì)數(shù)器—1,為0的時(shí)候就會(huì)被系統(tǒng)回收,調(diào)用dealloc方法
- 說道內(nèi)存管理,就必須說說@property的內(nèi)存管理參數(shù):
- assign --> 針對(duì)于基本數(shù)據(jù)類型的簡(jiǎn)單賦值操作
- retain --> release 一次舊對(duì)象 retain 一次新對(duì)象 (適用于OC對(duì)象類型)
- copy --> release 一次舊對(duì)象 拷貝一個(gè)新對(duì)象出來(一般修飾字符串和block)
- weak--> 表示一種非擁有關(guān)系,設(shè)置該屬性時(shí)既不釋放新值,也不保留舊值,和assign類似,但是目標(biāo)對(duì)象釋放時(shí),屬性值也會(huì)自動(dòng)清空
- 如何避免內(nèi)存泄露 --> 使用Analyze進(jìn)行代碼的靜態(tài)分析
- 當(dāng)然使用block的時(shí)候最應(yīng)該注意下循環(huán)引用,使用Leaks檢測(cè)內(nèi)存泄露,顯示綠色的勾告知內(nèi)存處理的不錯(cuò),實(shí)際上內(nèi)存得不到釋放。一般我的方法是在控制器聲明周期的viewDidAppear和dealloc方法里面打印日志[[self class] description],如果沒有打印出來,就說明沒有被釋放。使用__weak __typeof(self) weakSelf = self;解決。有一次我是直接使用成員變量,而不是屬性,_age,我以為這樣沒有使用self就可以了,但是后來測(cè)試發(fā)現(xiàn)還是造成循環(huán)引用了,因?yàn)開age是控制器的成員變量,也就是強(qiáng)引用了控制器,也要改成弱引用__block __weak __typeof(_currentModel) weakModel = _currentModel;
11.1 為什么用 weak 修飾的變量會(huì)自動(dòng)置為 nil?
- runtime 對(duì)注冊(cè)類,會(huì)進(jìn)行布局,將 weak 修飾的對(duì)象放到一個(gè)hash表中,key值是該對(duì)象的內(nèi)存地址,value是該對(duì)象
- 當(dāng)該對(duì)象retainCount為0時(shí),執(zhí)行dealloc,根據(jù)該地址去weak的hash表中查詢到該對(duì)象,從而設(shè)置為nil(向nil發(fā)送消息是安全的)
12. 常見的數(shù)據(jù)持久化有哪些
- 偏好設(shè)置(preference),利用NSUserDefaults
- 用來保存應(yīng)用程序設(shè)置和屬性、用戶保存的數(shù)據(jù)。用戶再次打開程序或開機(jī)后這些數(shù)據(jù)仍然存在
- NSUserDefaults可以存儲(chǔ)的數(shù)據(jù)類型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存儲(chǔ)其他類型,需要先轉(zhuǎn)化為前面的類型,才能用NSUserDefault存儲(chǔ)
- 偏好設(shè)置是專門用來保存應(yīng)用程序的配置信息的,一般不要在偏好設(shè)置中保存其他數(shù)據(jù)
- 偏好設(shè)置會(huì)將所有數(shù)據(jù)保存到同一個(gè)文件中。即preference目錄下的一個(gè)以此應(yīng)用包名來命名的plist文件。
//1.獲得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向文件中寫入內(nèi)容
[userDefaults setObject:@"AAA" forKey:@"a"];
[userDefaults setBool:YES forKey:@"sex"];
[userDefaults setInteger:21 forKey:@"age"];
//2.1立即同步
[userDefaults synchronize];
//3.讀取文件
NSString *name = [userDefaults objectForKey:@"a"];
BOOL sex = [userDefaults boolForKey:@"sex"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);
- 歸檔(Archiver)、解檔(unArchiver),利用NSKeyedArchiver實(shí)現(xiàn)歸檔、利用NSKeyedUnarchiver反接的那個(gè)
- 歸檔及時(shí)將內(nèi)存中的對(duì)象寫入到磁盤文件中,歸檔也叫序列化,解檔就是講磁盤中文件中的對(duì)象讀取出來
- 必須遵循NSCoding協(xié)議,只要遵循了NSCoding協(xié)議的對(duì)象都可以通過它實(shí)現(xiàn)序列化,兩個(gè)協(xié)議方法必須實(shí)現(xiàn)
// 反歸檔
- (id)initWithCoder:(NSCoder *)aDecoder {
if ([super init]) {
self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
// 歸檔
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.avatar forKey:@"avatar"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
* 歸檔,把對(duì)象歸檔時(shí)需要調(diào)用NSKeyedArchiver的工廠方法archiveRootObject: toFile: 方法
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [[Person alloc] init];
person.avatar = self.avatarView.image;
person.name = self.nameField.text;
person.age = [self.ageField.text integerValue];
[NSKeyedArchiver archiveRootObject:person toFile:file];
* 反歸檔
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
if (person) {
self.avatarView.image = person.avatar;
self.nameField.text = person.name;
self.ageField.text = [NSString stringWithFormat:@"%ld", person.age];
}
屬性列表
數(shù)據(jù)庫:SQLite
Core Data
點(diǎn)擊查看大神講解屬性列表
這五種持久化操作不同點(diǎn)
從存儲(chǔ)數(shù)據(jù)大小來看,歸檔、偏好設(shè)置、屬性列表三種方法適合存儲(chǔ)數(shù)據(jù)量較小的數(shù)據(jù),數(shù)據(jù)庫、CoreData方法適合存儲(chǔ)數(shù)據(jù)量較大的數(shù)據(jù)
從加密性來看,其中歸檔會(huì)將數(shù)據(jù)進(jìn)行加密,而偏好設(shè)置是直接保存到屬性列表中,不會(huì)對(duì)數(shù)據(jù)進(jìn)行加密
從存儲(chǔ)類型來看,屬性列表只能存放固定的七種類型(可在plist文件中看到),歸檔對(duì)存儲(chǔ)類型無限制
13. KVC 和 KVO
-
KVC(key-value-coding鍵值編碼,跟多情況下會(huì)簡(jiǎn)化程序代碼)的常見用法:
- 給私有變量(該變量不對(duì)外開放)賦值:[Person setValue: @"19" ForKeyPath:@"age"]
- 字典轉(zhuǎn)模型:setValuesForKeyWithDictionary
- 取出私有變量:[Person valueForKey:@"age"]
- 沒有找到對(duì)應(yīng)的key會(huì)崩潰:重寫setValueForUndefinedKey
KVC缺點(diǎn):一旦使用KVC,編譯器無法檢查出錯(cuò)誤,即不會(huì)對(duì)設(shè)置的鍵、鍵路徑進(jìn)行錯(cuò)誤檢查,且執(zhí)行效率低于自定義的setter和getter方法,因?yàn)槭褂肒VC鍵值編值,必須先解析字符串,然后設(shè)置或訪問對(duì)象的實(shí)例變量
-
通過KVO(key-value-observing,典型的觀察者模式,被觀察的對(duì)象必須使用KVC鍵值編碼來修改它的實(shí)例變量,這樣才能被觀察者觀察到)監(jiān)聽person對(duì)象中name屬性發(fā)生改變
- 給監(jiān)聽的屬性設(shè)置一個(gè)觀察者:
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
* 當(dāng)person的name的值發(fā)生改變時(shí),就會(huì)執(zhí)行該方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
do something....
}
- 當(dāng)一個(gè)類的屬性被觀察的時(shí)候,系統(tǒng)會(huì)通過runtime動(dòng)態(tài)的創(chuàng)建一個(gè)該類的派生類,并且會(huì)在這個(gè)類中重寫基類被觀察的屬性的setter方法,而且系統(tǒng)將這個(gè)類的isa指針指向了派生類(NSNotifying_類名),從而實(shí)現(xiàn)了給監(jiān)聽的屬性賦值時(shí)調(diào)用的是派生類的setter方法。重寫的setter方法會(huì)在調(diào)用原setter方法前后,通知觀察對(duì)象值得改變。
14. @synthesize和@dynamic區(qū)別是什么
- 這兩個(gè)關(guān)鍵字都是@property對(duì)應(yīng)的詞
- @synthesize 語義是如果沒有手動(dòng)實(shí)現(xiàn)setter和getter方法,那么編譯器會(huì)自動(dòng)幫你加上這兩個(gè)方法;如果同時(shí)重寫了某屬性的setter和getter,系統(tǒng)就不會(huì)自動(dòng)生成ivar,需要手動(dòng)創(chuàng)建ivar或者使用@synthesize將ivar和property關(guān)聯(lián)起來
- @dynamic告訴編譯器,屬性的setter和getter由用戶自己實(shí)現(xiàn),不自動(dòng)生成(readOnly只實(shí)現(xiàn)getter即可),但是如果沒有自己實(shí)現(xiàn),編譯的時(shí)候不會(huì)報(bào)錯(cuò),運(yùn)行的時(shí)候就會(huì)報(bào)錯(cuò),這就是所謂的動(dòng)態(tài)綁定
15. 談?wù)剷r(shí)間響應(yīng)鏈的一般順序
- 第一步:事件的產(chǎn)生:首先找到最合適的從UIApplication到keyWindow依次找到view,由上到下
- 發(fā)生觸摸事件后,系統(tǒng)會(huì)將該事件加入到一個(gè)由UIApplication管理的事件隊(duì)列中(先進(jìn)先出)
- UIApplication 會(huì)從事件隊(duì)列中取出最前面的事件,并將事件分發(fā)下去以便處理,通常,先把事件發(fā)送給應(yīng)用程序的主窗口(keywindow)
- 主窗口會(huì)在視圖層級(jí)結(jié)構(gòu)中找到一個(gè)最合適的視圖來處理觸摸事件
- hitTest:方法遍歷當(dāng)前view的所有子類,返回最合適的view
- 如self的userInterface/enable屬性為NO,透明度小于0.1/hidden,則self及其子view都不會(huì)進(jìn)入響應(yīng)者鏈,hitTest方法直接return nil
- 找到合適的視圖控件后,就會(huì)用視圖控件的touches 方法來做具體的事件處理
- 由于是從父視圖到子視圖,因此如果父view不接收事件,子視圖也無法接收事件
- 如果確定父控件是最合適的view,那么父控件的子控件的hitTest:withEvent: 方法也會(huì)被調(diào)用
- 想讓誰成為最合適的view就重寫自己的父控件的hitTest:withEvent: 方法,返回指定的控件
- 第二步:事件的響應(yīng):由下到上
- 先看inital view能否處理這個(gè)事件,如果不能依次往上傳遞,一直傳遞到該視圖的VC/window/application ,如果application還不能處理,就將該事件拋棄
- 在事件的響應(yīng)過程中,如果某控件實(shí)現(xiàn)了 touches 方法,則這個(gè)事件由該控件來接收,如果調(diào)用了[super touches] 方法,就會(huì)將事件順著響應(yīng)者鏈條往上傳遞,傳遞給上一個(gè)響應(yīng)者
16.1 post和get方式的區(qū)別
GET請(qǐng)求的數(shù)據(jù)會(huì)負(fù)載URL之后,即把數(shù)據(jù)放在HTTP協(xié)議頭中,以?區(qū)分URL和傳輸數(shù)據(jù),參數(shù)之間以&相連,英文字母/數(shù)字,原樣發(fā)送,如果是空格,轉(zhuǎn)化為+,如果是中文,把字符串用BASE64加密;POST就是把提交的數(shù)據(jù)放在HTTP包的包體中
GET一般用于提交少量數(shù)據(jù)(最多提交1k,瀏覽器限制),POST用于提交大量數(shù)據(jù)(理論上無限制,收服務(wù)器限制)
GET 無副作用,POST 有副作用
GET提交的數(shù)據(jù)可以在瀏覽器歷史記錄中看到,安全性不好,別人可以拿到賬號(hào)密碼,POST不會(huì)
Get是向服務(wù)器發(fā)索取數(shù)據(jù)的一種請(qǐng)求,而POST是向服務(wù)器發(fā)提交數(shù)據(jù)的一種請(qǐng)求,只是發(fā)送機(jī)制不同
GET不可以設(shè)置書簽,POST可以設(shè)置書簽
POST支持更多編碼類型且不對(duì)數(shù)據(jù)類型限制
-
什么情況下用POST:
- 請(qǐng)求的結(jié)果具有持續(xù)性副作用,如數(shù)據(jù)庫添加新的數(shù)據(jù)行
- 若使用get方法,則表單上手機(jī)的數(shù)據(jù)可能讓URL過長(zhǎng)
- 要傳送的數(shù)據(jù)不是采用7位的ASCII編碼
-
什么情況下用GET:
- 請(qǐng)求是為了查找資源,HTML表單數(shù)據(jù)僅用來幫助搜索
- 請(qǐng)求結(jié)果無持續(xù)副作用性的副作用
- 手機(jī)的數(shù)據(jù)及HTML表單內(nèi)的輸入字段名稱的總長(zhǎng)不超過1024個(gè)字符
16.2 POST和PUT區(qū)別
POST請(qǐng)求的url表示處理該封閉實(shí)體的資源,該資源可能是個(gè)數(shù)據(jù)接收過程、某種協(xié)議的網(wǎng)關(guān)、或者接收注解的獨(dú)立實(shí)體。
PUT請(qǐng)求中的url表示請(qǐng)求中封閉的實(shí)體-用戶代理知道url的目標(biāo),并且服務(wù)器無法將請(qǐng)求應(yīng)用到其他資源。如果服務(wù)器希望該請(qǐng)求應(yīng)用到另一個(gè)url,就必須發(fā)送一個(gè)301響應(yīng);用戶代理可通過自己的判斷來決定是否轉(zhuǎn)發(fā)該請(qǐng)求。
POST是用來提交數(shù)據(jù)的。提交的數(shù)據(jù)放在HTTP請(qǐng)求的正文里,目的在于提交數(shù)據(jù)并用于服務(wù)器端的存儲(chǔ),而不允許用戶過多的更改相應(yīng)數(shù)據(jù)(主要是相對(duì)于在url 修改要麻煩很多)。
PUT操作是冪等的。所謂冪等是指不管進(jìn)行多少次操作,結(jié)果都一樣。比如我用PUT修改一篇文章,然后在做同樣的操作,每次操作后的結(jié)果并沒有不同
POST操作既不是安全的,也不是冪等的,比如常見的POST重復(fù)加載問題:當(dāng)我們多次發(fā)出同樣的POST請(qǐng)求后,其結(jié)果是創(chuàng)建出了若干的資源。
安全和冪等的意義在于:當(dāng)操作沒有達(dá)到預(yù)期的目標(biāo)時(shí),我們可以不停的重試,而不會(huì)對(duì)資源產(chǎn)生副作用。從這個(gè)意義上說,POST操作往往是有害的,但很多時(shí)候我們還是不得不使用它。
還有一點(diǎn)需要注意的就是,創(chuàng)建操作可以使用POST,也可以使用PUT,區(qū)別在于POST 是作用在一個(gè)集合資源之上的,而PUT操作是作用在集合的一個(gè)具體資源之上的,再通俗點(diǎn)說,如果URL可以在客戶端確定,那么就使用PUT,如果是在服務(wù)端確定,那么就使用POST,比如說很多資源使用數(shù)據(jù)庫自增主鍵作為標(biāo)識(shí)信息,而創(chuàng)建的資源的標(biāo)識(shí)信息到底是什么只能由服務(wù)端提供,這個(gè)時(shí)候就必須使用POST。
17. 深復(fù)制和淺復(fù)制
- 非集合類對(duì)immutable對(duì)象進(jìn)行copy操作,是指針復(fù)制,mutableCopy操作時(shí)內(nèi)容復(fù)制
- 非集合類對(duì)mutable對(duì)象進(jìn)行copy和mutableCopy都是內(nèi)容復(fù)制
- 在集合類對(duì)象中,對(duì)immutable對(duì)象進(jìn)行copy,是指針復(fù)制,mutableCopy是內(nèi)容復(fù)制
- 在集合類對(duì)象中,對(duì)mutable對(duì)象進(jìn)行copy和mutableCopy都是內(nèi)容復(fù)制。但是:集合對(duì)象的內(nèi)容復(fù)制僅限于對(duì)象本身,對(duì)象元素仍然是指針復(fù)制
- copy出來的對(duì)象都是不可變的,mutableCopy出來的對(duì)象都是可變的
- NSString *str = @"string"; str = @"newString"; 打印對(duì)象地址,發(fā)現(xiàn)是發(fā)生變化的,需要把@"newStirng"當(dāng)做一個(gè)新的對(duì)象,將這段對(duì)象的內(nèi)存地址賦值給str
18. 關(guān)于項(xiàng)目中動(dòng)畫的使用
- 序列幀動(dòng)畫:self.imageView.animationImages = array;
- [UIView animateWithDuration] + CGAffinetransform
- 核心動(dòng)畫CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position.y"]; anim.fromValue toValue repeatCount [btn.layer addAnimation]
- 關(guān)鍵幀動(dòng)畫CAKeyframeAnimation,anim.values = array,添加到layer上
- 組動(dòng)畫CAAnimationGroup,將以上動(dòng)畫組合起來
- 轉(zhuǎn)場(chǎng)動(dòng)畫:CATransition,設(shè)置duration和type,然后添加到layer上。利用UIView 的類方法實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫
[UIView transitionWithView: duration: options: animations:^{ } completion:nil]; - UIDynamicAnimator仿真者 、 UISnapBehavior吸附行為,設(shè)置damping來調(diào)節(jié)震動(dòng)幅度 、 UIPushBehavior推動(dòng)行為 、 UICollisionBehavior碰撞邊緣檢測(cè)行為 、 UIAttachmentBehavior附著行為 、 UIGravityBehavior重力行為
- POPSpringAnimation
- springBounciness[0,20]越大振幅越大。
- springSpeed速度
21. 優(yōu)化tableViewCell高度
一種是針對(duì)所有 Cell 具有固定高度的情況,通過:self.tableView.rowHeight = 88;
指定了一個(gè)所有 cell 都是 88 高度的 UITableView,對(duì)于定高需求的表格,強(qiáng)烈建議使用這種(而非下面的)方式保證不必要的高度計(jì)算和調(diào)用。另一種方式就是實(shí)現(xiàn) UITableViewDelegate 中的:heightForRowAtIndexPath:需要注意的是,實(shí)現(xiàn)了這個(gè)方法后,rowHeight 的設(shè)置將無效。所以,這個(gè)方法適用于具有多種 cell 高度的 UITableView。
-
iOS7之后出了了estimatedRowHeight,面對(duì)不同高度的cell,只要給一個(gè)預(yù)估的值就可以了,先給一個(gè)預(yù)估值,然后邊滑動(dòng)邊計(jì)算,但是缺點(diǎn)就是
- 設(shè)置估算高度以后,tableView的contentSize.height是根據(jù)cell高度預(yù)估值和cell的個(gè)數(shù)來計(jì)算的,導(dǎo)致導(dǎo)航條處于很不穩(wěn)定的狀態(tài),因?yàn)閏ontentSize.height會(huì)逐漸由預(yù)估高度變?yōu)閷?shí)際高度,很多情況下肉眼是可以看到導(dǎo)航條跳躍的
- 如果是設(shè)計(jì)不好的上拉加載或下拉刷新,有可能使表格滑動(dòng)跳躍
- 估算高度設(shè)計(jì)初衷是好的,讓加載速度更快,但是損失了流暢性,與其損失流暢性,我寧愿讓用戶加載界面的時(shí)候多等那零點(diǎn)幾秒
-
iOS8 WWDC 中推出了 self-sizing cell 的概念,旨在讓 cell 自己負(fù)責(zé)自己的高度計(jì)算,使用 frame layout 和 auto layout 都可以享受到:
- self.tableView.estimatedRowHeight = 213;
self.tableView.rowHeight = UITableViewAutomaticDimension;
如果不加上估算高度的設(shè)置,自動(dòng)算高就失效了 - 這個(gè)自動(dòng)算高在 push 到下一個(gè)頁面或者轉(zhuǎn)屏?xí)r會(huì)出現(xiàn)高度特別詭異的情況,不過現(xiàn)在的版本修復(fù)了。
- self.tableView.estimatedRowHeight = 213;
-
相同的代碼在 iOS7 和 iOS8 上滑動(dòng)順暢程度完全不同,iOS8 莫名奇妙的卡。很大一部分原因是 iOS8 上的算高機(jī)制大不相同,從 WWDC 也倒是能找到點(diǎn)解釋,cell 被認(rèn)為隨時(shí)都可能改變高度(如從設(shè)置中調(diào)整動(dòng)態(tài)字體大小),所以每次滑動(dòng)出來后都要重新計(jì)算高度。
- dequeueReusableCellWithIdentifier:forIndexPath: 相比不帶 “forIndexPath” 的版本會(huì)多調(diào)用一次高度計(jì)算
- iOS7 計(jì)算高度后有”緩存“機(jī)制,不會(huì)重復(fù)計(jì)算;而 iOS8 不論何時(shí)都會(huì)重新計(jì)算 cell 高度
使用 UITableView+FDTemplateLayoutCell 無疑是解決算高問題的最佳實(shí)踐之一,既有 iOS8 self-sizing 功能簡(jiǎn)單的 API,又可以達(dá)到 iOS7 流暢的滑動(dòng)效果,還保持了最低支持 iOS6
-
FDTemplateLayoutCell 的高度預(yù)緩存是一個(gè)優(yōu)化功能,利用RunLoop空閑時(shí)間執(zhí)行預(yù)緩存任務(wù)計(jì)算,當(dāng)用戶正在滑動(dòng)列表時(shí)顯然不應(yīng)該執(zhí)行計(jì)算任務(wù)影響滑動(dòng)體驗(yàn)。
- 當(dāng)用戶正在滑動(dòng) UIScrollView 時(shí),RunLoop 將切換到 UITrackingRunLoopMode 接受滑動(dòng)手勢(shì)和處理滑動(dòng)事件(包括減速和彈簧效果),此時(shí),其他 Mode (除 NSRunLoopCommonModes 這個(gè)組合 Mode)下的事件將全部暫停執(zhí)行,來保證滑動(dòng)事件的優(yōu)先處理,這也是 iOS 滑動(dòng)順暢的重要原因
- 注冊(cè) RunLoopObserver 可以觀測(cè)當(dāng)前 RunLoop 的運(yùn)行狀態(tài),并在狀態(tài)機(jī)切換時(shí)收到通知:
- RunLoop開始
- RunLoop即將處理Timer
- RunLoop即將處理Source
- RunLoop即將進(jìn)入休眠狀態(tài)
- RunLoop即將從休眠狀態(tài)被事件喚醒
- RunLoop退出
- 分解成多個(gè)RunLoop Source任務(wù),假設(shè)列表有 20 個(gè) cell,加載后展示了前 5 個(gè),那么開啟估算后 table view 只計(jì)算了這 5 個(gè)的高度,此時(shí)剩下 15 個(gè)就是“預(yù)緩存”的任務(wù),而我們并不希望這 15 個(gè)計(jì)算任務(wù)在同一個(gè) RunLoop 迭代中同步執(zhí)行,這樣會(huì)卡頓 UI,所以應(yīng)該把它們分別分解到 15 個(gè) RunLoop 迭代中執(zhí)行,這時(shí)就需要手動(dòng)向 RunLoop 中添加 Source 任務(wù)(由應(yīng)用發(fā)起和處理的是 Source 0 任務(wù))
23. 為什么AFN顯示圖片不如SDWebImage流暢?同樣是從網(wǎng)絡(luò)上下載圖片而不是從緩存取圖片?
- 因?yàn)镾DWebImage有一個(gè)decoder
- UIImage的imageWithData函數(shù)是每次畫圖的時(shí)候才將Data解壓成ARGB的圖像
- 所以每次畫圖的時(shí)候,會(huì)有一個(gè)解壓操作,這樣效率很低,但是只有瞬時(shí)的內(nèi)存需求
- 為了提高效率通過SDWebImageDecoder將包裝在Data的資源解壓,然后畫在另外一張圖片上,這樣新的圖片就不再需要重復(fù)解壓了
- 這是典型的空間換時(shí)間的做法
25. 我是怎樣用兩個(gè)imageView實(shí)現(xiàn)了無線輪播!
- 建立一個(gè)scrollView,設(shè)置contentsize為3*kWidth,contentOffSet為kWidth
- 接下來使用代理方法scrollViewDidScroll來監(jiān)聽scrollview的滾動(dòng),定義一個(gè)枚舉變量來記錄滾動(dòng)的方向
- 使用KVO來監(jiān)聽direction屬性值的改變-->[self addObserver:self forKeyPath:@"direction" options:NSKeyValueObservingOptionNew context:nil];
- 通過observeValueForKeyPath判斷滾動(dòng)的方向,當(dāng)偏移量大于x,表示左移,則將otherImageView加在右邊,偏移量小于x,表示右移,則將otherImageView加在左邊。同時(shí)判斷設(shè)置對(duì)應(yīng)的索引,圖片
- 通過代理方法scrollViewDidEndDecelerating來監(jiān)聽滾動(dòng)結(jié)束,結(jié)束后,scrollview的偏移量為0或者2x,我們通過代碼再次將scrollview的偏移量設(shè)置為x,并將currImageView的圖片修改為otherImageView的圖片,那么我們看到的還是currImageView,只不過展示的是下一張圖片,如圖,又變成了最初的效果
- ,然后設(shè)置自動(dòng)輪播,添加計(jì)時(shí)器,利用setContentOffset方法里面setContentOffset:animated:方法執(zhí)行完畢后不會(huì)調(diào)用scrollview的scrollViewDidEndDecelerating方法,但是會(huì)調(diào)用scrollViewDidEndScrollingAnimation方法,因此我們要在該方法中調(diào)用pauseScroll(即監(jiān)聽減速結(jié)束后由otherImageView切換到currImageView的方法)
- 添加計(jì)時(shí)器:self.timer = [NSTimer timerWithTimeInterval:self.time target:self selector:@selector(nextPage) userInfo:nil repeats:YES];
- 在scrollViewWillBeginDragging中停止計(jì)時(shí)器
- 在scrollViewDidEndDragging中開啟計(jì)時(shí)器
- 判斷外界傳入的是圖片還是路徑,如果是圖片,直接加入圖片數(shù)組中,如果是路徑,先添加一個(gè)占位圖片,然后根據(jù)路徑去下載圖片
- 監(jiān)聽圖片被點(diǎn)擊
- 定義一個(gè)block屬性暴露給外界void(^imageClickBlock)(NSInteger index)
(不會(huì)block的可以用代理,或者看這里) - 設(shè)置currImageView的userInteractionEnabled為YES
- 給currImageView添加一個(gè)點(diǎn)擊的手勢(shì)
- 在手勢(shì)方法里調(diào)用block,并傳入圖片索引
- 定義一個(gè)block屬性暴露給外界void(^imageClickBlock)(NSInteger index)
- NSTimer的兩種形式
- scheduledTimerWithTimeInterval 是創(chuàng)建一個(gè)定時(shí)器,并加入到當(dāng)前運(yùn)行循環(huán)[NSRunLoop currentRunLoop]中
- 其他兩個(gè)([NSTimer timerWithTimeInterval:3 target:self selector:@selector(doSomeThing1) userInfo:nil repeats:YES]; [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:5] interval:3 target:self selector:@selector(doSomeThing2) userInfo:nil repeats:YES];)只是創(chuàng)建定時(shí)器,并未添加到當(dāng)前運(yùn)行循環(huán)中,所以如果是其他兩種方式創(chuàng)建的定時(shí)器則需要手動(dòng)添加到currentRunLoop中
- NSTimer是普通的定時(shí)器,如果系統(tǒng)繁忙,刷新可能會(huì)被延遲。但是CADisplaylink實(shí)時(shí)刷新,跟著屏幕的刷新頻率實(shí)時(shí)刷新,60次/s,與屏幕刷新頻率相同
26. tableView的優(yōu)化
iOS平臺(tái)因?yàn)閁IKit本身的特性,需要將所有的UI操作都放在主線程執(zhí)行,所以有時(shí)候就習(xí)慣將一些線程安全性不確定的邏輯,以及它線程結(jié)束后的匯總工作等等放到了主線程,所以主線程包含大量計(jì)算、IO、繪制都有可能造成卡頓。
- 可以通過監(jiān)控runLoop監(jiān)控監(jiān)控卡頓,調(diào)用方法主要就是在kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting之間,還有kCFRunLoopAfterWaiting之后,也就是如果我們發(fā)現(xiàn)這兩個(gè)時(shí)間內(nèi)耗時(shí)太長(zhǎng),那么就可以判定出此時(shí)主線程卡頓.
- 使用到CFRunLoopObserverRef,通過它可以實(shí)時(shí)獲得這些狀態(tài)值的變化
- 監(jiān)控后另外再開啟一個(gè)線程,實(shí)時(shí)計(jì)算這兩個(gè)狀態(tài)區(qū)域之間的耗時(shí)是否到達(dá)某個(gè)閥值,便能揪出這些性能殺手.
- 監(jiān)控到了卡頓現(xiàn)場(chǎng),當(dāng)然下一步便是記錄此時(shí)的函數(shù)調(diào)用信息,此處可以使用一個(gè)第三方Crash收集組件PLCrashReporter,它不僅可以收集Crash信息也可用于實(shí)時(shí)獲取各線程的調(diào)用堆棧
- 當(dāng)檢測(cè)到卡頓時(shí),抓取堆棧信息,然后在客戶端做一些過濾處理,便可以上報(bào)到服務(wù)器,通過收集一定量的卡頓數(shù)據(jù)后經(jīng)過分析便能準(zhǔn)確定位需要優(yōu)化的邏輯
設(shè)置正確的 reuseidentifer 以重用 cell
盡量將 View 設(shè)置為不透明,包括 cell 本身(backgroundcolor默認(rèn)是透明的),圖層混合靠GPU去渲染,如果透明度設(shè)置為100%,那么GPU就會(huì)忽略下面所有的layer,節(jié)約了很多不必要的運(yùn)算。模擬器上點(diǎn)擊“Debug”菜單,然后選擇“color Blended Layers”,會(huì)把所有區(qū)域分成綠色和紅色,綠色的好,紅色的性能差(經(jīng)過混合渲染的),當(dāng)然也有一些圖片雖然是不透明的,但是也會(huì)顯示紅色,如果檢查代碼沒錯(cuò)的話,一般就是圖片自身的性質(zhì)問題了,直接聯(lián)系美工或后臺(tái)解決就好了。除非必須要用GPU加載的,其他最好要用CPU加載,因?yàn)镃PU一般不會(huì)百分百加載,可以通過CoreGraphics畫出圓角
有時(shí)候美工失誤,圖片大小給錯(cuò)了,引起不必要的圖片縮放(可以找美工去改,當(dāng)然也可以異步去裁剪圖片然后緩存下來),還是使用Instrument的Color Misaligned Images,黃色表示圖片需要縮放,紫色表示沒有像素對(duì)齊。當(dāng)然一般情況下圖片格式不會(huì)給錯(cuò),有些圖片格式是GPU不支持的,就還要?jiǎng)跓〤PU去進(jìn)行格式轉(zhuǎn)換。還有可以通過Color Offscreen-Rendered Yellow來檢測(cè)離屏渲染(就是把渲染結(jié)果臨時(shí)保存,等到用的時(shí)候再取出,這樣相對(duì)于普通渲染更消耗內(nèi)存,使用maskToBounds、設(shè)置shadow,重寫drawRect方法都會(huì)導(dǎo)致離屏渲染)
避免漸變,cornerRadius在默認(rèn)情況下,這個(gè)屬性只會(huì)影響視圖的背景顏色和 border,但是不會(huì)離屏繪制,不影響性能。不用clipsToBounds(過多調(diào)用GPU去離屏渲染),而是讓后臺(tái)加載圖片并處理圓角,并將處理過的圖片賦值給UIImageView。UIImageView 的圓角通過直接截取圖片實(shí)現(xiàn),圓角路徑直接用貝塞爾曲線UIBezierPath繪制(人為指定路徑之后就不會(huì)觸發(fā)離屏渲染),UIGraphicsBeginImageContextWithOptions。UIView的圓角可以使用CoreGraphics畫出圓角矩形,核心是CGContextAddArcToPoint 函數(shù)。它中間的四個(gè)參數(shù)表示曲線的起點(diǎn)和終點(diǎn)坐標(biāo),最后一個(gè)參數(shù)表示半徑。調(diào)用了四次函數(shù)后,就可以畫出圓角矩形。最后再從當(dāng)前的繪圖上下文中獲取圖片并返回,最后把這個(gè)圖片插入到視圖層級(jí)的底部。
“Flash updated Regions”用于標(biāo)記發(fā)生重繪的區(qū)域如果 row 的高度不相同,那么將其緩存下來
如果 cell 顯示的內(nèi)容來自網(wǎng)絡(luò),那么確保這些內(nèi)容是通過異步下載
使用 shadowPath 來設(shè)置陰影,圖層最好不要使用陰影,陰影會(huì)導(dǎo)致離屏渲染(在進(jìn)入屏幕渲染之前,還看不到的時(shí)候會(huì)再渲染一次,盡量不要產(chǎn)生離屏渲染)
減少 subview 的數(shù)量,不要去添加或移除view,要就顯示,不要就隱藏
在 cellForRowAtIndexPath 中盡量做更少的操作,最好是在別的地方算好,這個(gè)方法里只做數(shù)據(jù)的顯示,如果需要做一些處理,那么最好做一次之后將結(jié)果儲(chǔ)存起來.
使用適當(dāng)?shù)臄?shù)據(jù)結(jié)構(gòu)來保存需要的信息,不同的結(jié)構(gòu)會(huì)帶來不同的操作代價(jià)
使用,rowHeight , sectionFooterHeight 和 sectionHeaderHeight 來設(shè)置一個(gè)恒定高度 , 而不是從代理(delegate)中獲取
cell做數(shù)據(jù)綁定的時(shí)候,最好在willDisPlayCell里面進(jìn)行,其他操作在cellForRowAtIndexPath,因?yàn)榍罢呤堑谝豁撚卸嗌贄l就執(zhí)行多少次,后者是第一次加載有多少個(gè)cell就執(zhí)行多少次,而且調(diào)用后者的時(shí)候cell還沒顯示
讀取文件,寫入文件,最好是放到子線程,或先讀取好,在讓tableView去顯示
tableView滾動(dòng)的時(shí)候,不要去做動(dòng)畫(微信的聊天界面做的就很好,在滾動(dòng)的時(shí)候,動(dòng)態(tài)圖就不讓他動(dòng),滾動(dòng)停止的時(shí)候才動(dòng),不然可能會(huì)有點(diǎn)影響流暢度)。在滾動(dòng)的時(shí)候加載圖片,停止拖拽后在減速過程中不加載圖片,減速停止后加載可見范圍內(nèi)圖片
27. 談?wù)剝?nèi)存的優(yōu)化和注意事項(xiàng)(使用Instrument工具的CoreAnimation、GPU Driver、I/O操作,檢查fps數(shù)值)
重用問題:比如UITableViewCell、UICollectionViewCell、UITableViewHeaderFooterViews等設(shè)置正確的reuseIdentifier,充分重用
懶加載控件、頁面:對(duì)于不是立刻使用的數(shù)據(jù),都應(yīng)該使用延遲加載的方式,比如網(wǎng)絡(luò)連接失敗的提示界面,可能一直都用不到
使用Autorelease Pool:在某些循環(huán)創(chuàng)建臨時(shí)變量處理數(shù)據(jù)時(shí),自動(dòng)釋放池以保證能及時(shí)釋放內(nèi)存
不要使用太多的xib/storyboard:載入時(shí)會(huì)將其內(nèi)部的圖片在內(nèi)的所有資源載入內(nèi)存,即使未來很久才會(huì)需要使用,相對(duì)于純代碼寫的延遲加載,在性能和內(nèi)存上就差了很多
數(shù)據(jù)緩存:對(duì)于cell的行高要緩存起來,使用reloadData效率也極高,對(duì)于網(wǎng)絡(luò)數(shù)據(jù),不需要每次都請(qǐng)求的,應(yīng)該緩存起來,可以寫入數(shù)據(jù)庫,也可以通過plist文件存儲(chǔ)
-
選擇正確的數(shù)據(jù)結(jié)構(gòu):針對(duì)不同的業(yè)務(wù)場(chǎng)景選擇最合適的數(shù)據(jù)結(jié)構(gòu)是寫出高效代碼的基礎(chǔ)
- 數(shù)組:有序的一組值,使用索引查詢起來很快,使用值查詢的很慢,插入/刪除 很慢
- 字典:存儲(chǔ)鍵值對(duì)對(duì),用鍵查找比較快
- 集合:無序的一組值,用值來查找很快,插入/刪除很快
gzip/zip壓縮:當(dāng)從服務(wù)器下載相關(guān)附件時(shí),可以通過 zip壓縮后再下載,使得內(nèi)存更小,下載速度也更快
重大開銷對(duì)象:一些objects的初始化很慢,比如NSDateFormatter和 NSCalendar,但是又無可避免的需要使用,通常作為屬性存儲(chǔ)起來,避免反復(fù)使用
避免反復(fù)處理數(shù)據(jù):需要應(yīng)用需要從服務(wù)器加載數(shù)據(jù),常為JSON或者XML格式的數(shù)據(jù),在服務(wù)器端或者客戶端使用相同的數(shù)據(jù)結(jié)構(gòu)很重要
選擇圖片時(shí),要對(duì)圖片進(jìn)行壓縮處理,根據(jù)不同的情況選擇不同的圖片加載方式,-imageNamed:讀取到內(nèi)存后會(huì)緩存下來,適合圖片資源較小,使用很頻繁的圖片;-initWithContentsOfFiles:僅加載圖片而不緩存,適合較大的圖片。若是collectionView中使用大量圖片的時(shí)候,可以用UIVIew.layer.contents=(__bridge id _Nullable)(model.clipedImage.CGImage);這樣就更輕量級(jí)一些
當(dāng)然有時(shí)候也會(huì)用到一些第三方,比如在使用UICollectionView和UITableView的時(shí)候,F(xiàn)acebook有一個(gè)框架叫AsyncDisplayKit,這個(gè)庫就可以很好地提升滾動(dòng)時(shí)流暢性以及圖片異步下載功能(不支持sb和autoLayout,需要手動(dòng)進(jìn)行約束設(shè)置),AsyncDisplayKit用相關(guān)node類,替換了UIView和它的子類,而且是線程安全的。它可以異步解碼圖片,調(diào)整圖片大小以及對(duì)圖片和文本進(jìn)行渲染,把這些操作都放到子線程,滑動(dòng)的時(shí)候就流暢許多。我認(rèn)為這個(gè)庫最方便的就是實(shí)現(xiàn)圖片異步解碼。UIImage顯示之前必須要先解碼完成,而且解碼還是同步的。尤其是在UICollectionView/UITableView 中使用 prototype cell顯示大圖,UIImage的同步解碼在滾動(dòng)的時(shí)候會(huì)有明顯的卡頓。另外一個(gè)很吸引人的點(diǎn)是AsyncDisplayKit可以把view層次結(jié)構(gòu)轉(zhuǎn)成layer。因?yàn)閺?fù)雜的view層次結(jié)構(gòu)開銷很大,如果不需要view特有的功能(例如點(diǎn)擊事件),就可以使用AsyncDisplayKit 的layer backing特性從而獲得一些額外的提升。當(dāng)然這個(gè)庫還處于開發(fā)階段,還有一些地方地方有待完善,比如不支持緩存,我要使用這個(gè)庫的時(shí)候一般是結(jié)合Alamofire和AlamofireImage實(shí)現(xiàn)圖片的緩存
希望此文對(duì)您的求職或夯實(shí)基礎(chǔ)起到作用,感謝閱讀!