來來來烈菌!關(guān)于iOS基礎(chǔ)總結(jié)咱倆好好嘮嘮

2016.05.20 10:24

塵封已久的學(xué)習(xí)基礎(chǔ)總結(jié),最近公司項目不是很忙臊诊,終于抽空整理出來,現(xiàn)分享出來斜脂。

1.1 談一談GCD和NSOperation的區(qū)別抓艳?

  • 首先二者都是多線程相關(guān)的概念,當(dāng)然在使用中也是根據(jù)不同情境進行不同的選擇帚戳;
  • GCD是將任務(wù)添加到隊列中(串行/并發(fā)/主隊列)玷或,并且制定任務(wù)執(zhí)行的函數(shù)(同步/異步),其性能最好片任,底層是C語言的API偏友,也更輕量級。iOS4.0以后推出的对供,針對多核處理器的并發(fā)技術(shù)位他,只能設(shè)置某一個隊列的優(yōu)先級,常用的功能包括 一次性執(zhí)行 dispatch_once产场,延遲操作dispatch_after(這里是延遲推到線程中鹅髓,而不是在線程中等待,因此比如設(shè)置延遲1秒執(zhí)行京景,但是一秒后只是推到了線程中迈勋,不會立刻執(zhí)行),調(diào)度組等醋粟,其高級功能有
    • dispatch_barrier_async柵欄來控制異步操作的順序
    • dispatch_apply充分利用多核進行快速迭代遍歷
    • dispatch_group_t隊列組靡菇,添加到隊列組中的任務(wù)完成之后會調(diào)用dispatch_group_notify 函數(shù),可以實現(xiàn)類似A米愿、B兩個耗時操作都完成之后厦凤,去主線程更新UI的操作
  • NSOperation把操作(異步)添加到隊列中(全局的并發(fā)隊列),是OC框架育苟,更加面向?qū)ο蠼瞎模菍CD的封裝,iOS2.0推出违柏,蘋果推出GCD之后博烂,對NSOperation的底層全部重寫,可以隨時取消已經(jīng)設(shè)定準備要執(zhí)行的任務(wù)漱竖,已經(jīng)執(zhí)行的除外禽篱,可以設(shè)置隊列中每一個操作的優(yōu)先級,其基本功能包括設(shè)置最大操作并發(fā)數(shù)maxConcurrentOperationCount馍惹,繼續(xù)/暫停/全部取消躺率,可以快隊列設(shè)置操作的依賴關(guān)系,通過KVO監(jiān)聽 NSOperation 對象的屬性万矾,如 isCancelled悼吱、isFinished;對象可重用良狈。
    • NSInvocationOperationNSBlockOperation創(chuàng)建方法等這些基礎(chǔ)面試官往往默認你是會的
    • NSOperationQueue只有兩種隊列:主隊列后添、其他隊列。其他隊列包含了串行和并發(fā)
    • NSOperation + NSOperationQueue將任務(wù)加入到隊列
    • 操作依賴:[operation2 addDependency:operation1];(operation2 依賴于operation1的完成薪丁,但這兩個任務(wù)要加入到同一個隊列中)

1.2 談?wù)劧嗑€程的應(yīng)用

通常耗時的操作都放在子線程處理遇西,然后到主線程更新UI,如

  • 我們要從數(shù)據(jù)庫提取數(shù)據(jù)還要將數(shù)據(jù)分組后顯示窥突,那么就會開個子線程來處理努溃,處理完成后才去刷新UI顯示。
  • 拍照后阻问,會在子線程處理圖片梧税,完成后才回到主線程來顯示圖片。拍照出來的圖片太大了称近,因此要做處理第队。
  • 音頻、視頻處理會在子線程來操作
  • 文件較大時刨秆,文件操作會在子線程中處理
  • 做客戶端與服務(wù)端數(shù)據(jù)同步時凳谦,會在后臺閑時自動同步

2. 線程之間是如何通信的?

  • 通過主線程和子線程切換的時候傳遞參數(shù)performSelecter:onThread:withObject:waitUntilDone:

3. 網(wǎng)絡(luò)圖片處理問題怎么解決圖片重復(fù)下載問題衡未?(SDWebImage大概實現(xiàn)原理)

  • 這個就需要用到字典尸执,以圖片的下載地址url為key家凯,下載操作為value,所有的圖片大概分成三類:已經(jīng)下載好的如失,正在下載的和將要下載的;

  • 當(dāng)一張圖片將要進行下載操作的時候裆馒,先判斷緩存中是否有相同的圖片,如果有的話就返回,沒有的話就根據(jù)url的md5加密值去沙盒中找瘦锹,有的話就拿出來用颂碘,沒有的話再去以圖片的url為key去字典中找有沒有正在進行的任務(wù)澎胡,最后去判斷等待的下載操作任務(wù)里面的字典有無相同key,如果沒有戚宦,就自己開啟任務(wù)锈嫩,記錄一下,文件保存的名稱是url的md5值

  • 這里建立了兩個字典 :
    1.iconCache:保存緩存的圖片
    2.blockOperation 用來保存下載任務(wù)


    653183AF-3074-47AD-8460-10B5CEF1323C.png
  • 每當(dāng)進入或退出程序時,會進行圖片文件的管理:超過一星期的文件會被清除捐祠,如果設(shè)置了最大緩存,超過這個緩存就會刪除最舊的文件桑李,直到當(dāng)前緩存文件為最大緩存文件的一半大絮庵;

  • 一般app中大部分緩存都是圖片的情況下芙扎,可以直接調(diào)用clear方法進行清除緩存星岗,getSize()方法獲取當(dāng)前緩存大小。

4. 多線程安全的幾種解決方法戒洼?

  • 1> 只有在主線程刷新訪問UI
  • 2> 如果要防止資源搶奪俏橘,需要用synchronize進行加鎖保護
  • 3> 如果是異步操作要保證線程安全等問題,盡量使用GCD(有些函數(shù)默認就是安全的)
  • 4> 單例為什么用static dispatch_once?使用dispatch_once可以簡化代碼并且徹底保證線程安全圈浇,開發(fā)者無需擔(dān)心加鎖或同步寥掐。此外靴寂,dispatch_once更高效,它沒有使用重量級的同步機制召耘,若是那樣做的話百炬,每次運行代碼前都要獲取鎖。

5. 原子屬性

  • 原子屬性采用的是"多讀單寫"機制的多線程策略污它;"多讀單寫"縮小了鎖范圍,比互斥鎖的性能好
  • 規(guī)定只在主線程更新UI,就是因為如果在多線程中更新,就需要給UI對象加鎖,防止資源搶占寫入錯誤,但是這樣會降低UI交互的性能,所以ios設(shè)計讓所有UI對象都是非線程安全的(不加鎖)

6. 代理的作用剖踊、block

  • 代理又叫委托,是一種設(shè)計模式(可以理解為java中回調(diào)監(jiān)聽機制)衫贬,代理是對象與對象之間的通信交互德澈,代理解除了對象之間的耦合性
  • 改變或傳遞控制鏈,允許一個類在某些特定時刻通知到其他類固惯,而不需要獲取到那些類的指針梆造,可以減少框架復(fù)雜度
  • 代理的屬性常是 weak 的原因:防止循環(huán)引用,以致對象無法得到正確的釋放(具體原因會在下文第十一條闡述)
  • block底層是根據(jù)函數(shù)指針和結(jié)構(gòu)體結(jié)合實現(xiàn)的葬毫,block本身就是結(jié)構(gòu)體镇辉,更加簡潔,不需要定義繁瑣的協(xié)議方法贴捡,但通信事件比較多的話忽肛,建議使用Delegate
  • block就是一個數(shù)據(jù)類型,存放一段代碼栈暇,編譯的時候不會執(zhí)行麻裁,只有用到的時候才會去執(zhí)行里面的代碼。聲明的時候使用copy是因為要從棧區(qū)拷貝到堆區(qū)源祈,在棧區(qū)會受到作用域的限制煎源,超出所在的函數(shù)就會被銷毀,就沒辦法進行傳值回調(diào)等一系列操作了香缺。應(yīng)注意循環(huán)引用手销,__weak來修飾。如果一個變量是在block外部創(chuàng)建图张,需要在block內(nèi)部修改锋拖,那么需要使用__block修飾這個變量(__block可以在ARC和MRC情況下使用,可以修飾對象和基本數(shù)據(jù)類型祸轮,__weak只能在ARC下使用兽埃,只能修飾對象,不能修飾基本數(shù)據(jù)類型)
  • 最常用的是使用block作為參數(shù)傳值适袜,不同情況下回調(diào)不同的代碼(如成功回調(diào)失敗回調(diào))

7. 談?wù)勀銓unTime運行時機制的了解(注意哦柄错,這個很重要的)

  • runtime是一套比較底層的純C語言API,屬于一個C語言庫,包含了很多底層的C語言的API
  • 平時編寫的OC代碼售貌,在程序運行過程中给猾,其實都是轉(zhuǎn)成了runtime的C語言代碼,runtime是OC的幕后工作者颂跨,底層語言敢伸,例如:
    • OC--> [[WPFPerson alloc] init]
    • runtime-->objc_msgSend(objc_msgSend("WPFPerson", "alloc"), "init")
  • 利用runtime可以實現(xiàn)一些非常底層的操作(用OC不好實現(xiàn))
    • 在程序運行過程中,動態(tài)創(chuàng)建一個類(比如KVO底層實現(xiàn):檢測isa指針恒削,發(fā)現(xiàn)是新建了一個類池颈,當(dāng)然Xcode7.0以前的版本才可以監(jiān)聽到isa指針)
    • 遍歷一個類的所有成員變量、方法钓丰,訪問私有變量(先通過runtime的class_getInstanceVariable獲取成員變量饶辙,再通過class_getIvar獲取它的值)
    • 在程序運行過程中,動態(tài)為某個類添加屬性\方法斑粱,修改屬性值\方法,比如產(chǎn)品經(jīng)理需要跟蹤記錄APP中按鈕的點擊次數(shù)和頻率等數(shù)據(jù)脯爪,可以通過集成按鈕或者類別實現(xiàn)则北,但是帶來的問題比如別人不一定去實例化你寫的子類,或者其他類別也實現(xiàn)了點擊方法導(dǎo)致不確定會調(diào)用哪一個痕慢,runtime可以這樣解決:在按鈕的分類里面尚揣,重寫load方法,新建監(jiān)控按鈕點擊的方法掖举,先用class_addMethod方法快骗,判斷其返回的bool值,如果添加成功塔次,就用class_replaceMethod將原來的方法移除方篮,如果添加失敗,就用method_exchangeImplementations方法進行替換
    • 攔截并替換方法励负,比如由于某種原因藕溅,我們要改變這個方法的實現(xiàn),但是又不能動它的源碼(比如一些開源庫出現(xiàn)問題的時候继榆,這時候runtime就可以出場了)-->先增加一個tool類巾表,然后寫一個我們自己實現(xiàn)的方法-change,通過runtime的class_getInstanceMethod獲取兩個方法略吨,在用class_replaceMethod方法進行替換集币。防止數(shù)組越界的方法:數(shù)組越界的時候報錯的方法是add_object,做一個邏輯判斷翠忠,越界的時候通過class_replaceMethod交換掉add_object(相當(dāng)于重寫了這個方法)
  • 相關(guān)應(yīng)用
    • NSCoding(歸檔和解檔)鞠苟,如果一個模型有很多個屬性害驹,那么需要對每個屬性都實現(xiàn)一遍encodeObject和decodeObjectForKey方法胳赌,十分麻煩,但是如果使用class_copyIvarList獲取所有屬性,然后循環(huán)遍歷荐健,使用[ivarName substringFromIndex:1]去掉成員變量下劃線
    • 字典轉(zhuǎn)模型:像幾個出名的開源庫JSONModel、MJExtension等都是通過這種方式實現(xiàn)的(利用runtime的class_copyIvarList獲取屬性數(shù)組吼驶,遍歷模型對象的所有成員屬性翼抠,根據(jù)屬性名找到字典中key值進行賦值,當(dāng)然這種方法只能解決NSString扼鞋、NSNumber等申鱼,如果含有NSArray或NSDictionary,還要進行第二步轉(zhuǎn)換云头,如果是字典數(shù)組捐友,需要遍歷數(shù)組中的字典,利用objectWithDict方法將字典轉(zhuǎn)化為模型溃槐,在將模型放到數(shù)組中匣砖,最后把這個模型數(shù)組賦值給之前的字典數(shù)組)
  • Method Swizzling:OC中調(diào)用方法事實上就是向?qū)ο蟀l(fā)送消息,而查找消息的唯一依據(jù)就是selector的名字昏滴,因此可以使用runtime運行時機制動態(tài)交換方法猴鲫。在+load方法里面調(diào)換,因為method swizzling的影響范圍是全局的谣殊,所以應(yīng)該放在最保險的地方來處理拂共,+load方法能夠保證能在類初始化的時候一定能被調(diào)用,可以保證統(tǒng)一性姻几,如果是在使用的時候才去調(diào)用宜狐,可能達不到全局處理的效果;使用dispatch_once保證只交換一次蛇捌。[objc_getClass("__NSArrayM") swizzleSelector:@selector(addObject:)
    withSwizzledSelector:@selector(wpf_safeAddObject:)];使用場景:addObject方法添加的值為nil的時候會崩潰抚恒。調(diào)用objectAtIndex:時出現(xiàn)崩潰提示empty數(shù)組問題

8. 談?wù)勀銓un Loop的理解

  • RunLoop是多線程的一個很重要的機制,就是一個線程一次只能執(zhí)行一個任務(wù)豁陆,執(zhí)行完任務(wù)后就會退出線程柑爸。主線程會通過do-while死循環(huán)讓程序持續(xù)等待下一個任務(wù)不退出。通過mach_msg()讓runloop沒事時進入trap狀態(tài)盒音,節(jié)省CPU資源表鳍。非主線程通常來說就是為了執(zhí)行某個任務(wù)而創(chuàng)建的,執(zhí)行完就會歸還資源祥诽,因此默認不開啟RunLoop
  • 實質(zhì)上譬圣,對于子線程的runloop是默認不存在的,因為蘋果采用了懶加載的方式雄坪,如果沒有手動調(diào)用[NSRunLoop currentRunLoop]的話厘熟,就不會去查詢當(dāng)前線程的RunLoop,也不會創(chuàng)建、加載
  • 當(dāng)然如果子線程處理完某個任務(wù)后不退出绳姨,需要繼續(xù)等待接受事件登澜,需要啟動的時候也可以手動啟動,比如說添加定時器的時候就要手動開始RunLoop

如何處理事件

  • 界面刷新: 當(dāng)UI改變( Frame變化飘庄、 UIView/CALayer 的繼承結(jié)構(gòu)變化等)時脑蠕,或手動調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,這個 UIView/CALayer 就被標(biāo)記為待處理跪削。 蘋果注冊了一個用來監(jiān)聽BeforeWaiting和Exit的Observer谴仙,在它的回調(diào)函數(shù)里會遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實際的繪制和調(diào)整,并更新 UI 界面碾盐。

    • setNeedsLayout:刷新布局(不會立刻刷新晃跺,等下次runloop)
    • setNeedsDisplay:調(diào)用drawRect方法,重繪UI(不會立刻刷新毫玖,等下次runloop)
    • layoutIfNeeded:立刻刷新布局
  • 手勢識別: 如果上一步的 _UIApplicationHandleEventQueue() 識別到是一個guesture手勢掀虎,會調(diào)用Cancel方法將當(dāng)前的touchesBegin/Move/End 系列回調(diào)打斷。隨后系統(tǒng)將對應(yīng)的 UIGestureRecognizer 標(biāo)記為待處理付枫。 蘋果注冊了一個 Observer 監(jiān)測 BeforeWaiting (Loop即將進入休眠) 事件涩盾,其回調(diào)函數(shù)為 _UIGestureRecognizerUpdateObserver(),其內(nèi)部會獲取所有剛被標(biāo)記為待處理的 GestureRecognizer励背,并執(zhí)行GestureRecognizer的回調(diào)。 當(dāng)有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時砸西,這個回調(diào)都會進行相應(yīng)處理叶眉。

  • 網(wǎng)絡(luò)請求:最底層是CFSocket層,然后是CFNetwork將其封裝芹枷,然后是NSURLConnection對CFNetwork進行面向?qū)ο蟮姆庋b衅疙。當(dāng)網(wǎng)絡(luò)開始傳輸時,NSURLConnection創(chuàng)建了兩個新線程:com.apple.NSURLConnectionLoader和com.apple.CFSocket.private鸳慈。其中CFSocket線程是處理底層socket連接的饱溢。NSURLConnectionLoader這個線程內(nèi)部會使用RunLoop來接受底層socket的事件,并添加到上層的Delegate

應(yīng)用

  • 滑動與圖片刷新:當(dāng)tableView的cell上有需要從網(wǎng)絡(luò)獲取的圖片的時候走芋,滾動tableView绩郎,異步線程回去加載圖片,加載完成后主線程會設(shè)置cell的圖片翁逞,但是會造成卡頓肋杖。可以設(shè)置圖片的任務(wù)在CFRunloopDefaultMode下進行挖函,當(dāng)滾動tableView的時候状植,Runloop切換到UITrackingRunLoopMode,不去設(shè)置圖片,而是而是當(dāng)停止的時候津畸,再去設(shè)置圖片振定。(在viewDidLoad中調(diào)用self.imageView performSelector@selector(setImage) withObject:...afterDelay:...inModes@[NSDefayltRunLoopMode]

  • 常駐子線程,保持子線程一直處理事件 為了保證線程長期運轉(zhuǎn)肉拓,可以在子線程中加入RunLoop后频,并且給Runloop設(shè)置item,防止Runloop自動退出

10. 關(guān)于Socket帝簇,談?wù)凾CP/IP 和 UDP的理解

  • Socket是一個用于傳輸網(wǎng)絡(luò)數(shù)據(jù)的工具徘郭,TCP/IP 和 UDP都是傳輸協(xié)議,用于定義網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)母袷缴ル龋瑢儆陂L連接
  • TCP/IP 側(cè)重可靠傳輸残揉,傳輸速度慢,不會丟失數(shù)據(jù)芋浮,安全抱环,聊天和下載文件時用到
  • UDP:側(cè)重快速傳輸,傳輸速度快纸巷,容易丟失數(shù)據(jù)包镇草,不安全。局域網(wǎng)游戲和網(wǎng)絡(luò)游戲瘤旨,視頻聊天的時候用到
  • TCP更安全是因為有一個三次握手:
    • 第一次握手(客戶端發(fā)送syn包到服務(wù)器梯啤,并進入SYN_SEND狀態(tài),等待服務(wù)器確認)
    • 第二次握手(服務(wù)器收到syn包存哲,必須確認客戶的SYN包因宇,同時自己發(fā)送一個SYN+ACK包(或否認應(yīng)答NACK),此時服務(wù)器進入SYN_RECV狀態(tài))
    • 第三次握手(客戶端收到服務(wù)器的SYN+ACK包祟偷,向服務(wù)器發(fā)送確認包ACK察滑,發(fā)送完畢后服務(wù)器和客戶端都進入ESTABLISHED狀態(tài),完成三次握手)修肠,三次握手之后才開始正式傳輸數(shù)據(jù)贺辰。
    • TCP有順序控制的功能:通過序列號來確認發(fā)送的數(shù)據(jù)。比如第一次握手傳1000嵌施,第二次握手服務(wù)端返回3000饲化,那么就代表服務(wù)端確認收到了2000個字節(jié)
  • 那么現(xiàn)在即時通訊更適合用TCP還是UDP?
    • 早期使用MSN是使用TCP協(xié)議的吗伤,QQ使用采用UDP的滓侍,但并不代表UDP就不安全,因為可以手動對UDP的數(shù)據(jù)收發(fā)進行驗證(比如發(fā)送方對每個數(shù)據(jù)包進行編號然后由接收方進行驗證)牲芋,正是因為這個撩笆,QQ的傳遞速度是遠遠快于MSN捺球,可能這也是打敗MSN的其中一個原因吧
  • Http:超文本傳輸協(xié)議,用于定義網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)母袷?短鏈接)http1.0之前不支持短連接夕冲,1.1之后默認就是長連接氮兵,只要在服務(wù)器和客戶端同時設(shè)置Connection為keep-alive即可
    • 長連接是為了復(fù)用,長連接指的是TCP連接歹鱼,也就是為了復(fù)用TCP連接泣栈,也就是說多個HTTP請求可以復(fù)用一個TCP連接,節(jié)省了很多TCP連接建立和斷開的消耗
    • 比如請求了一個網(wǎng)頁弥姻,這個網(wǎng)頁肯定還包含了CSS南片、JS等一系列資源,如果是短連接的話庭敦,每次打開一個網(wǎng)頁疼进,基本要建立幾個甚至幾十個TCP連接,浪費了大量資源
    • 長連接不是永久連接秧廉,如果一段時間內(nèi)伞广,具體的時間長短,是可以在header當(dāng)中進行設(shè)置的疼电,也就是所謂的超時時間嚼锄,這個連接沒有HTTP請求發(fā)出的話,那么這個長連接就會被斷掉
  • socket連接是長連接蔽豺,客戶端與服務(wù)器保持通道区丑,雙方可以主動發(fā)送數(shù)據(jù),一般多用于即時通訊修陡,游戲刊苍,默認超時時間是30秒,默認大小是8k(一個數(shù)據(jù)包大斜粑觥)

11. 談一談內(nèi)存管理

  • iOS的內(nèi)存管理分為 MRC 和 ARC,管理的是堆區(qū)動態(tài)產(chǎn)生的對象啥纸,基本數(shù)據(jù)類型就不是內(nèi)存管理的范圍
  • 內(nèi)存管理的核心概念是引用計數(shù)器:當(dāng)對象被alloc号杏、copy、new的時候斯棒,引用計數(shù)器+1盾致,當(dāng)被release的時候引用計數(shù)器—1,為0的時候就會被系統(tǒng)回收荣暮,調(diào)用dealloc方法
  • 說道內(nèi)存管理庭惜,就必須說說@property的內(nèi)存管理參數(shù):
    • assign --> 針對于基本數(shù)據(jù)類型的簡單賦值操作
    • retain --> release 一次舊對象 retain 一次新對象 (適用于OC對象類型)
    • copy --> release 一次舊對象 拷貝一個新對象出來(一般修飾字符串和block)
    • weak--> 表示一種非擁有關(guān)系,設(shè)置該屬性時既不釋放新值穗酥,也不保留舊值护赊,和assign類似惠遏,但是目標(biāo)對象釋放時,屬性值也會自動清空
  • 如何避免內(nèi)存泄露 --> 使用Analyze進行代碼的靜態(tài)分析
  • 當(dāng)然使用block的時候最應(yīng)該注意下循環(huán)引用骏啰,使用Leaks檢測內(nèi)存泄露节吮,顯示綠色的勾告知內(nèi)存處理的不錯,實際上內(nèi)存得不到釋放判耕。一般我的方法是在控制器聲明周期的viewDidAppear和dealloc方法里面打印日志[[self class] description]透绩,如果沒有打印出來,就說明沒有被釋放壁熄。使用__weak __typeof(self) weakSelf = self;解決帚豪。有一次我是直接使用成員變量,而不是屬性草丧,_age狸臣,我以為這樣沒有使用self就可以了,但是后來測試發(fā)現(xiàn)還是造成循環(huán)引用了方仿,因為_age是控制器的成員變量固棚,也就是強引用了控制器,也要改成弱引用__block __weak __typeof(_currentModel) weakModel = _currentModel;

11.1 為什么用 weak 修飾的變量會自動置為 nil仙蚜?

  • runtime 對注冊類此洲,會進行布局,將 weak 修飾的對象放到一個hash表中委粉,key值是該對象的內(nèi)存地址呜师,value是該對象
  • 當(dāng)該對象retainCount為0時,執(zhí)行dealloc贾节,根據(jù)該地址去weak的hash表中查詢到該對象汁汗,從而設(shè)置為nil(向nil發(fā)送消息是安全的)

12. 常見的數(shù)據(jù)持久化有哪些

  • 偏好設(shè)置(preference),利用NSUserDefaults
    • 用來保存應(yīng)用程序設(shè)置和屬性栗涂、用戶保存的數(shù)據(jù)知牌。用戶再次打開程序或開機后這些數(shù)據(jù)仍然存在
    • NSUserDefaults可以存儲的數(shù)據(jù)類型包括:NSData、NSString斤程、NSNumber角寸、NSDate壶笼、NSArray夏跷、NSDictionary。如果要存儲其他類型算撮,需要先轉(zhuǎn)化為前面的類型疚脐,才能用NSUserDefault存儲
    • 偏好設(shè)置是專門用來保存應(yīng)用程序的配置信息的亿柑,一般不要在偏好設(shè)置中保存其他數(shù)據(jù)
    • 偏好設(shè)置會將所有數(shù)據(jù)保存到同一個文件中。即preference目錄下的一個以此應(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實現(xiàn)歸檔、利用NSKeyedUnarchiver反接的那個
    • 歸檔及時將內(nèi)存中的對象寫入到磁盤文件中式矫,歸檔也叫序列化乡摹,解檔就是講磁盤中文件中的對象讀取出來
    • 必須遵循NSCoding協(xié)議,只要遵循了NSCoding協(xié)議的對象都可以通過它實現(xiàn)序列化采转,兩個協(xié)議方法必須實現(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"];
  }
* 歸檔聪廉,把對象歸檔時需要調(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ù)大小來看,歸檔故慈、偏好設(shè)置板熊、屬性列表三種方法適合存儲數(shù)據(jù)量較小的數(shù)據(jù),數(shù)據(jù)庫察绷、CoreData方法適合存儲數(shù)據(jù)量較大的數(shù)據(jù)

  • 從加密性來看干签,其中歸檔會將數(shù)據(jù)進行加密,而偏好設(shè)置是直接保存到屬性列表中拆撼,不會對數(shù)據(jù)進行加密

  • 從存儲類型來看容劳,屬性列表只能存放固定的七種類型(可在plist文件中看到),歸檔對存儲類型無限制

13. KVC 和 KVO

  • KVC(key-value-coding鍵值編碼闸度,跟多情況下會簡化程序代碼)的常見用法:

    • 給私有變量(該變量不對外開放)賦值:[Person setValue: @"19" ForKeyPath:@"age"]
    • 字典轉(zhuǎn)模型:setValuesForKeyWithDictionary
    • 取出私有變量:[Person valueForKey:@"age"]
    • 沒有找到對應(yīng)的key會崩潰:重寫setValueForUndefinedKey
  • KVC缺點:一旦使用KVC竭贩,編譯器無法檢查出錯誤,即不會對設(shè)置的鍵莺禁、鍵路徑進行錯誤檢查留量,且執(zhí)行效率低于自定義的setter和getter方法,因為使用KVC鍵值編值哟冬,必須先解析字符串楼熄,然后設(shè)置或訪問對象的實例變量

  • 通過KVO(key-value-observing,典型的觀察者模式浩峡,被觀察的對象必須使用KVC鍵值編碼來修改它的實例變量可岂,這樣才能被觀察者觀察到)監(jiān)聽person對象中name屬性發(fā)生改變

    • 給監(jiān)聽的屬性設(shè)置一個觀察者:
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
* 當(dāng)person的name的值發(fā)生改變時,就會執(zhí)行該方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
  do something....
}
  • 當(dāng)一個類的屬性被觀察的時候,系統(tǒng)會通過runtime動態(tài)的創(chuàng)建一個該類的派生類翰灾,并且會在這個類中重寫基類被觀察的屬性的setter方法缕粹,而且系統(tǒng)將這個類的isa指針指向了派生類(NSNotifying_類名),從而實現(xiàn)了給監(jiān)聽的屬性賦值時調(diào)用的是派生類的setter方法预侯。重寫的setter方法會在調(diào)用原setter方法前后,通知觀察對象值得改變峰锁。

14. @synthesize和@dynamic區(qū)別是什么

  • 這兩個關(guān)鍵字都是@property對應(yīng)的詞
  • @synthesize 語義是如果沒有手動實現(xiàn)setter和getter方法萎馅,那么編譯器會自動幫你加上這兩個方法;如果同時重寫了某屬性的setter和getter,系統(tǒng)就不會自動生成ivar虹蒋,需要手動創(chuàng)建ivar或者使用@synthesize將ivar和property關(guān)聯(lián)起來
  • @dynamic告訴編譯器糜芳,屬性的setter和getter由用戶自己實現(xiàn)飒货,不自動生成(readOnly只實現(xiàn)getter即可),但是如果沒有自己實現(xiàn)峭竣,編譯的時候不會報錯塘辅,運行的時候就會報錯,這就是所謂的動態(tài)綁定

15. 談?wù)剷r間響應(yīng)鏈的一般順序

  • 第一步:事件的產(chǎn)生:首先找到最合適的從UIApplication到keyWindow依次找到view皆撩,由上到下
    • 發(fā)生觸摸事件后扣墩,系統(tǒng)會將該事件加入到一個由UIApplication管理的事件隊列中(先進先出)
    • UIApplication 會從事件隊列中取出最前面的事件,并將事件分發(fā)下去以便處理扛吞,通常呻惕,先把事件發(fā)送給應(yīng)用程序的主窗口(keywindow)
    • 主窗口會在視圖層級結(jié)構(gòu)中找到一個最合適的視圖來處理觸摸事件
      • hitTest:方法遍歷當(dāng)前view的所有子類,返回最合適的view
      • 如self的userInterface/enable屬性為NO滥比,透明度小于0.1/hidden亚脆,則self及其子view都不會進入響應(yīng)者鏈,hitTest方法直接return nil
    • 找到合適的視圖控件后盲泛,就會用視圖控件的touches 方法來做具體的事件處理
  • 由于是從父視圖到子視圖濒持,因此如果父view不接收事件,子視圖也無法接收事件
  • 如果確定父控件是最合適的view寺滚,那么父控件的子控件的hitTest:withEvent: 方法也會被調(diào)用
  • 想讓誰成為最合適的view就重寫自己的父控件的hitTest:withEvent: 方法柑营,返回指定的控件
  • 第二步:事件的響應(yīng):由下到上
    • 先看inital view能否處理這個事件,如果不能依次往上傳遞玛迄,一直傳遞到該視圖的VC/window/application 由境,如果application還不能處理,就將該事件拋棄
    • 在事件的響應(yīng)過程中蓖议,如果某控件實現(xiàn)了 touches 方法虏杰,則這個事件由該控件來接收,如果調(diào)用了[super touches] 方法勒虾,就會將事件順著響應(yīng)者鏈條往上傳遞纺阔,傳遞給上一個響應(yīng)者

16.1 post和get方式的區(qū)別

  • GET請求的數(shù)據(jù)會負載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ù)可以在瀏覽器歷史記錄中看到挡毅,安全性不好,別人可以拿到賬號密碼暴构,POST不會

  • Get是向服務(wù)器發(fā)索取數(shù)據(jù)的一種請求跪呈,而POST是向服務(wù)器發(fā)提交數(shù)據(jù)的一種請求,只是發(fā)送機制不同

  • GET不可以設(shè)置書簽丹壕,POST可以設(shè)置書簽

  • POST支持更多編碼類型且不對數(shù)據(jù)類型限制

  • 什么情況下用POST:

    • 請求的結(jié)果具有持續(xù)性副作用庆械,如數(shù)據(jù)庫添加新的數(shù)據(jù)行
    • 若使用get方法,則表單上手機的數(shù)據(jù)可能讓URL過長
    • 要傳送的數(shù)據(jù)不是采用7位的ASCII編碼
  • 什么情況下用GET:

    • 請求是為了查找資源菌赖,HTML表單數(shù)據(jù)僅用來幫助搜索
    • 請求結(jié)果無持續(xù)副作用性的副作用
    • 手機的數(shù)據(jù)及HTML表單內(nèi)的輸入字段名稱的總長不超過1024個字符

16.2 POST和PUT區(qū)別

  • POST請求的url表示處理該封閉實體的資源缭乘,該資源可能是個數(shù)據(jù)接收過程、某種協(xié)議的網(wǎng)關(guān)琉用、或者接收注解的獨立實體堕绩。

  • PUT請求中的url表示請求中封閉的實體-用戶代理知道url的目標(biāo),并且服務(wù)器無法將請求應(yīng)用到其他資源邑时。如果服務(wù)器希望該請求應(yīng)用到另一個url奴紧,就必須發(fā)送一個301響應(yīng);用戶代理可通過自己的判斷來決定是否轉(zhuǎn)發(fā)該請求晶丘。

  • POST是用來提交數(shù)據(jù)的黍氮。提交的數(shù)據(jù)放在HTTP請求的正文里,目的在于提交數(shù)據(jù)并用于服務(wù)器端的存儲浅浮,而不允許用戶過多的更改相應(yīng)數(shù)據(jù)(主要是相對于在url 修改要麻煩很多)沫浆。

  • PUT操作是冪等的。所謂冪等是指不管進行多少次操作滚秩,結(jié)果都一樣专执。比如我用PUT修改一篇文章,然后在做同樣的操作郁油,每次操作后的結(jié)果并沒有不同

  • POST操作既不是安全的本股,也不是冪等的,比如常見的POST重復(fù)加載問題:當(dāng)我們多次發(fā)出同樣的POST請求后桐腌,其結(jié)果是創(chuàng)建出了若干的資源拄显。

  • 安全和冪等的意義在于:當(dāng)操作沒有達到預(yù)期的目標(biāo)時,我們可以不停的重試案站,而不會對資源產(chǎn)生副作用躬审。從這個意義上說,POST操作往往是有害的,但很多時候我們還是不得不使用它盒件。

  • 還有一點需要注意的就是,創(chuàng)建操作可以使用POST舱禽,也可以使用PUT炒刁,區(qū)別在于POST 是作用在一個集合資源之上的,而PUT操作是作用在集合的一個具體資源之上的誊稚,再通俗點說翔始,如果URL可以在客戶端確定,那么就使用PUT里伯,如果是在服務(wù)端確定城瞎,那么就使用POST,比如說很多資源使用數(shù)據(jù)庫自增主鍵作為標(biāo)識信息疾瓮,而創(chuàng)建的資源的標(biāo)識信息到底是什么只能由服務(wù)端提供脖镀,這個時候就必須使用POST。

17. 深復(fù)制和淺復(fù)制

  • 非集合類對immutable對象進行copy操作狼电,是指針復(fù)制,mutableCopy操作時內(nèi)容復(fù)制
  • 非集合類對mutable對象進行copy和mutableCopy都是內(nèi)容復(fù)制
  • 在集合類對象中,對immutable對象進行copy,是指針復(fù)制,mutableCopy是內(nèi)容復(fù)制
  • 在集合類對象中色洞,對mutable對象進行copy和mutableCopy都是內(nèi)容復(fù)制秋秤。但是:集合對象的內(nèi)容復(fù)制僅限于對象本身抠藕,對象元素仍然是指針復(fù)制
  • copy出來的對象都是不可變的,mutableCopy出來的對象都是可變的
  • NSString *str = @"string"; str = @"newString"; 打印對象地址,發(fā)現(xiàn)是發(fā)生變化的,需要把@"newStirng"當(dāng)做一個新的對象战虏,將這段對象的內(nèi)存地址賦值給str

18. 關(guān)于項目中動畫的使用

  • 序列幀動畫:self.imageView.animationImages = array;
  • [UIView animateWithDuration] + CGAffinetransform
  • 核心動畫CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position.y"]; anim.fromValue toValue repeatCount [btn.layer addAnimation]
  • 關(guān)鍵幀動畫CAKeyframeAnimation燕耿,anim.values = array怯晕,添加到layer上
  • 組動畫CAAnimationGroup,將以上動畫組合起來
  • 轉(zhuǎn)場動畫:CATransition缸棵,設(shè)置duration和type舟茶,然后添加到layer上。利用UIView 的類方法實現(xiàn)轉(zhuǎn)場動畫
    [UIView transitionWithView: duration: options: animations:^{ } completion:nil];
  • UIDynamicAnimator仿真者 、 UISnapBehavior吸附行為吧凉,設(shè)置damping來調(diào)節(jié)震動幅度 隧出、 UIPushBehavior推動行為 、 UICollisionBehavior碰撞邊緣檢測行為 阀捅、 UIAttachmentBehavior附著行為 胀瞪、 UIGravityBehavior重力行為
  • POPSpringAnimation
    • springBounciness[0,20]越大振幅越大。
    • springSpeed速度

21. 優(yōu)化tableViewCell高度

  • 一種是針對所有 Cell 具有固定高度的情況饲鄙,通過:self.tableView.rowHeight = 88;
    指定了一個所有 cell 都是 88 高度的 UITableView凄诞,對于定高需求的表格,強烈建議使用這種(而非下面的)方式保證不必要的高度計算和調(diào)用忍级。

  • 另一種方式就是實現(xiàn) UITableViewDelegate 中的:heightForRowAtIndexPath:需要注意的是帆谍,實現(xiàn)了這個方法后,rowHeight 的設(shè)置將無效轴咱。所以汛蝙,這個方法適用于具有多種 cell 高度的 UITableView。

  • iOS7之后出了了estimatedRowHeight朴肺,面對不同高度的cell窖剑,只要給一個預(yù)估的值就可以了戈稿,先給一個預(yù)估值西土,然后邊滑動邊計算,但是缺點就是

    • 設(shè)置估算高度以后鞍盗,tableView的contentSize.height是根據(jù)cell高度預(yù)估值和cell的個數(shù)來計算的翠储,導(dǎo)致導(dǎo)航條處于很不穩(wěn)定的狀態(tài),因為contentSize.height會逐漸由預(yù)估高度變?yōu)閷嶋H高度橡疼,很多情況下肉眼是可以看到導(dǎo)航條跳躍的
    • 如果是設(shè)計不好的上拉加載或下拉刷新援所,有可能使表格滑動跳躍
    • 估算高度設(shè)計初衷是好的,讓加載速度更快欣除,但是損失了流暢性住拭,與其損失流暢性,我寧愿讓用戶加載界面的時候多等那零點幾秒
  • iOS8 WWDC 中推出了 self-sizing cell 的概念历帚,旨在讓 cell 自己負責(zé)自己的高度計算滔岳,使用 frame layout 和 auto layout 都可以享受到:

    • self.tableView.estimatedRowHeight = 213;
      self.tableView.rowHeight = UITableViewAutomaticDimension;
      如果不加上估算高度的設(shè)置,自動算高就失效了
    • 這個自動算高在 push 到下一個頁面或者轉(zhuǎn)屏?xí)r會出現(xiàn)高度特別詭異的情況挽牢,不過現(xiàn)在的版本修復(fù)了谱煤。
  • 相同的代碼在 iOS7 和 iOS8 上滑動順暢程度完全不同,iOS8 莫名奇妙的卡禽拔。很大一部分原因是 iOS8 上的算高機制大不相同,從 WWDC 也倒是能找到點解釋刘离,cell 被認為隨時都可能改變高度(如從設(shè)置中調(diào)整動態(tài)字體大惺也妗),所以每次滑動出來后都要重新計算高度硫惕。

    • dequeueReusableCellWithIdentifier:forIndexPath: 相比不帶 “forIndexPath” 的版本會多調(diào)用一次高度計算
    • iOS7 計算高度后有”緩存“機制茧痕,不會重復(fù)計算;而 iOS8 不論何時都會重新計算 cell 高度
  • 使用 UITableView+FDTemplateLayoutCell 無疑是解決算高問題的最佳實踐之一恼除,既有 iOS8 self-sizing 功能簡單的 API踪旷,又可以達到 iOS7 流暢的滑動效果,還保持了最低支持 iOS6

  • FDTemplateLayoutCell 的高度預(yù)緩存是一個優(yōu)化功能豁辉,利用RunLoop空閑時間執(zhí)行預(yù)緩存任務(wù)計算令野,當(dāng)用戶正在滑動列表時顯然不應(yīng)該執(zhí)行計算任務(wù)影響滑動體驗踱启。

    • 當(dāng)用戶正在滑動 UIScrollView 時棉安,RunLoop 將切換到 UITrackingRunLoopMode 接受滑動手勢和處理滑動事件(包括減速和彈簧效果),此時枫匾,其他 Mode (除 NSRunLoopCommonModes 這個組合 Mode)下的事件將全部暫停執(zhí)行灰追,來保證滑動事件的優(yōu)先處理,這也是 iOS 滑動順暢的重要原因
    • 注冊 RunLoopObserver 可以觀測當(dāng)前 RunLoop 的運行狀態(tài)狗超,并在狀態(tài)機切換時收到通知:
      • RunLoop開始
      • RunLoop即將處理Timer
      • RunLoop即將處理Source
      • RunLoop即將進入休眠狀態(tài)
      • RunLoop即將從休眠狀態(tài)被事件喚醒
      • RunLoop退出
    • 分解成多個RunLoop Source任務(wù)弹澎,假設(shè)列表有 20 個 cell,加載后展示了前 5 個努咐,那么開啟估算后 table view 只計算了這 5 個的高度苦蒿,此時剩下 15 個就是“預(yù)緩存”的任務(wù),而我們并不希望這 15 個計算任務(wù)在同一個 RunLoop 迭代中同步執(zhí)行渗稍,這樣會卡頓 UI佩迟,所以應(yīng)該把它們分別分解到 15 個 RunLoop 迭代中執(zhí)行,這時就需要手動向 RunLoop 中添加 Source 任務(wù)(由應(yīng)用發(fā)起和處理的是 Source 0 任務(wù))

23. 為什么AFN顯示圖片不如SDWebImage流暢竿屹?同樣是從網(wǎng)絡(luò)上下載圖片而不是從緩存取圖片报强?

  • 因為SDWebImage有一個decoder
  • UIImage的imageWithData函數(shù)是每次畫圖的時候才將Data解壓成ARGB的圖像
  • 所以每次畫圖的時候,會有一個解壓操作拱燃,這樣效率很低秉溉,但是只有瞬時的內(nèi)存需求
  • 為了提高效率通過SDWebImageDecoder將包裝在Data的資源解壓,然后畫在另外一張圖片上碗誉,這樣新的圖片就不再需要重復(fù)解壓了
  • 這是典型的空間換時間的做法

25. 我是怎樣用兩個imageView實現(xiàn)了無線輪播召嘶!

  1. 建立一個scrollView,設(shè)置contentsize為3*kWidth哮缺,contentOffSet為kWidth
  2. 接下來使用代理方法scrollViewDidScroll來監(jiān)聽scrollview的滾動弄跌,定義一個枚舉變量來記錄滾動的方向
  3. 使用KVO來監(jiān)聽direction屬性值的改變-->[self addObserver:self forKeyPath:@"direction" options:NSKeyValueObservingOptionNew context:nil];
  4. 通過observeValueForKeyPath判斷滾動的方向,當(dāng)偏移量大于x尝苇,表示左移铛只,則將otherImageView加在右邊埠胖,偏移量小于x,表示右移格仲,則將otherImageView加在左邊押袍。同時判斷設(shè)置對應(yīng)的索引,圖片
  5. 通過代理方法scrollViewDidEndDecelerating來監(jiān)聽滾動結(jié)束凯肋,結(jié)束后谊惭,scrollview的偏移量為0或者2x,我們通過代碼再次將scrollview的偏移量設(shè)置為x侮东,并將currImageView的圖片修改為otherImageView的圖片圈盔,那么我們看到的還是currImageView,只不過展示的是下一張圖片悄雅,如圖驱敲,又變成了最初的效果
  6. ,然后設(shè)置自動輪播宽闲,添加計時器众眨,利用setContentOffset方法里面setContentOffset:animated:方法執(zhí)行完畢后不會調(diào)用scrollview的scrollViewDidEndDecelerating方法,但是會調(diào)用scrollViewDidEndScrollingAnimation方法容诬,因此我們要在該方法中調(diào)用pauseScroll(即監(jiān)聽減速結(jié)束后由otherImageView切換到currImageView的方法)
  7. 添加計時器:self.timer = [NSTimer timerWithTimeInterval:self.time target:self selector:@selector(nextPage) userInfo:nil repeats:YES];
  8. 在scrollViewWillBeginDragging中停止計時器
  9. 在scrollViewDidEndDragging中開啟計時器
  10. 判斷外界傳入的是圖片還是路徑娩梨,如果是圖片,直接加入圖片數(shù)組中览徒,如果是路徑狈定,先添加一個占位圖片,然后根據(jù)路徑去下載圖片
  11. 監(jiān)聽圖片被點擊
    • 定義一個block屬性暴露給外界void(^imageClickBlock)(NSInteger index)
      (不會block的可以用代理习蓬,或者看這里)
    • 設(shè)置currImageView的userInteractionEnabled為YES
    • 給currImageView添加一個點擊的手勢
    • 在手勢方法里調(diào)用block纽什,并傳入圖片索引
  12. NSTimer的兩種形式
    • scheduledTimerWithTimeInterval 是創(chuàng)建一個定時器,并加入到當(dāng)前運行循環(huán)[NSRunLoop currentRunLoop]中
    • 其他兩個([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)建定時器躲叼,并未添加到當(dāng)前運行循環(huán)中芦缰,所以如果是其他兩種方式創(chuàng)建的定時器則需要手動添加到currentRunLoop中
  • NSTimer是普通的定時器,如果系統(tǒng)繁忙枫慷,刷新可能會被延遲饺藤。但是CADisplaylink實時刷新,跟著屏幕的刷新頻率實時刷新流礁,60次/s涕俗,與屏幕刷新頻率相同

26. tableView的優(yōu)化

iOS平臺因為UIKit本身的特性,需要將所有的UI操作都放在主線程執(zhí)行神帅,所以有時候就習(xí)慣將一些線程安全性不確定的邏輯再姑,以及它線程結(jié)束后的匯總工作等等放到了主線程,所以主線程包含大量計算找御、IO元镀、繪制都有可能造成卡頓绍填。

  • 可以通過監(jiān)控runLoop監(jiān)控監(jiān)控卡頓,調(diào)用方法主要就是在kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting之間,還有kCFRunLoopAfterWaiting之后,也就是如果我們發(fā)現(xiàn)這兩個時間內(nèi)耗時太長,那么就可以判定出此時主線程卡頓.
  • 使用到CFRunLoopObserverRef,通過它可以實時獲得這些狀態(tài)值的變化
  • 監(jiān)控后另外再開啟一個線程,實時計算這兩個狀態(tài)區(qū)域之間的耗時是否到達某個閥值,便能揪出這些性能殺手.
  • 監(jiān)控到了卡頓現(xiàn)場,當(dāng)然下一步便是記錄此時的函數(shù)調(diào)用信息,此處可以使用一個第三方Crash收集組件PLCrashReporter,它不僅可以收集Crash信息也可用于實時獲取各線程的調(diào)用堆棧
  • 當(dāng)檢測到卡頓時,抓取堆棧信息,然后在客戶端做一些過濾處理,便可以上報到服務(wù)器,通過收集一定量的卡頓數(shù)據(jù)后經(jīng)過分析便能準確定位需要優(yōu)化的邏輯
  1. 設(shè)置正確的 reuseidentifer 以重用 cell

  2. 盡量將 View 設(shè)置為不透明,包括 cell 本身(backgroundcolor默認是透明的)栖疑,圖層混合靠GPU去渲染,如果透明度設(shè)置為100%讨永,那么GPU就會忽略下面所有的layer,節(jié)約了很多不必要的運算遇革。模擬器上點擊“Debug”菜單卿闹,然后選擇“color Blended Layers”,會把所有區(qū)域分成綠色和紅色,綠色的好,紅色的性能差(經(jīng)過混合渲染的)萝快,當(dāng)然也有一些圖片雖然是不透明的锻霎,但是也會顯示紅色,如果檢查代碼沒錯的話揪漩,一般就是圖片自身的性質(zhì)問題了旋恼,直接聯(lián)系美工或后臺解決就好了。除非必須要用GPU加載的奄容,其他最好要用CPU加載冰更,因為CPU一般不會百分百加載,可以通過CoreGraphics畫出圓角

  3. 有時候美工失誤昂勒,圖片大小給錯了蜀细,引起不必要的圖片縮放(可以找美工去改,當(dāng)然也可以異步去裁剪圖片然后緩存下來)叁怪,還是使用Instrument的Color Misaligned Images审葬,黃色表示圖片需要縮放深滚,紫色表示沒有像素對齊奕谭。當(dāng)然一般情況下圖片格式不會給錯,有些圖片格式是GPU不支持的痴荐,就還要勞煩CPU去進行格式轉(zhuǎn)換血柳。還有可以通過Color Offscreen-Rendered Yellow來檢測離屏渲染(就是把渲染結(jié)果臨時保存,等到用的時候再取出生兆,這樣相對于普通渲染更消耗內(nèi)存难捌,使用maskToBounds、設(shè)置shadow鸦难,重寫drawRect方法都會導(dǎo)致離屏渲染)
    避免漸變根吁,cornerRadius在默認情況下,這個屬性只會影響視圖的背景顏色和 border合蔽,但是不會離屏繪制击敌,不影響性能。不用clipsToBounds(過多調(diào)用GPU去離屏渲染)拴事,而是讓后臺加載圖片并處理圓角沃斤,并將處理過的圖片賦值給UIImageView圣蝎。UIImageView 的圓角通過直接截取圖片實現(xiàn),圓角路徑直接用貝塞爾曲線UIBezierPath繪制(人為指定路徑之后就不會觸發(fā)離屏渲染)衡瓶,UIGraphicsBeginImageContextWithOptions徘公。UIView的圓角可以使用CoreGraphics畫出圓角矩形,核心是CGContextAddArcToPoint 函數(shù)哮针。它中間的四個參數(shù)表示曲線的起點和終點坐標(biāo)关面,最后一個參數(shù)表示半徑。調(diào)用了四次函數(shù)后诚撵,就可以畫出圓角矩形缭裆。最后再從當(dāng)前的繪圖上下文中獲取圖片并返回,最后把這個圖片插入到視圖層級的底部寿烟。
    “Flash updated Regions”用于標(biāo)記發(fā)生重繪的區(qū)域

  4. 如果 row 的高度不相同,那么將其緩存下來

  5. 如果 cell 顯示的內(nèi)容來自網(wǎng)絡(luò),那么確保這些內(nèi)容是通過異步下載

  6. 使用 shadowPath 來設(shè)置陰影澈驼,圖層最好不要使用陰影,陰影會導(dǎo)致離屏渲染(在進入屏幕渲染之前,還看不到的時候會再渲染一次,盡量不要產(chǎn)生離屏渲染)

  7. 減少 subview 的數(shù)量,不要去添加或移除view筛武,要就顯示缝其,不要就隱藏

  8. 在 cellForRowAtIndexPath 中盡量做更少的操作,最好是在別的地方算好,這個方法里只做數(shù)據(jù)的顯示徘六,如果需要做一些處理,那么最好做一次之后將結(jié)果儲存起來.

  9. 使用適當(dāng)?shù)臄?shù)據(jù)結(jié)構(gòu)來保存需要的信息,不同的結(jié)構(gòu)會帶來不同的操作代價

  10. 使用,rowHeight , sectionFooterHeight 和 sectionHeaderHeight 來設(shè)置一個恒定高度 , 而不是從代理(delegate)中獲取

  11. cell做數(shù)據(jù)綁定的時候内边,最好在willDisPlayCell里面進行,其他操作在cellForRowAtIndexPath待锈,因為前者是第一頁有多少條就執(zhí)行多少次漠其,后者是第一次加載有多少個cell就執(zhí)行多少次,而且調(diào)用后者的時候cell還沒顯示

  12. 讀取文件,寫入文件,最好是放到子線程,或先讀取好,在讓tableView去顯示

  13. tableView滾動的時候,不要去做動畫(微信的聊天界面做的就很好,在滾動的時候,動態(tài)圖就不讓他動,滾動停止的時候才動,不然可能會有點影響流暢度)竿音。在滾動的時候加載圖片和屎,停止拖拽后在減速過程中不加載圖片,減速停止后加載可見范圍內(nèi)圖片

27. 談?wù)剝?nèi)存的優(yōu)化和注意事項(使用Instrument工具的CoreAnimation春瞬、GPU Driver柴信、I/O操作,檢查fps數(shù)值)

  • 重用問題:比如UITableViewCell宽气、UICollectionViewCell随常、UITableViewHeaderFooterViews等設(shè)置正確的reuseIdentifier,充分重用

  • 懶加載控件萄涯、頁面:對于不是立刻使用的數(shù)據(jù)绪氛,都應(yīng)該使用延遲加載的方式,比如網(wǎng)絡(luò)連接失敗的提示界面涝影,可能一直都用不到

  • 使用Autorelease Pool:在某些循環(huán)創(chuàng)建臨時變量處理數(shù)據(jù)時枣察,自動釋放池以保證能及時釋放內(nèi)存

  • 不要使用太多的xib/storyboard:載入時會將其內(nèi)部的圖片在內(nèi)的所有資源載入內(nèi)存,即使未來很久才會需要使用袄琳,相對于純代碼寫的延遲加載询件,在性能和內(nèi)存上就差了很多

  • 數(shù)據(jù)緩存:對于cell的行高要緩存起來燃乍,使用reloadData效率也極高,對于網(wǎng)絡(luò)數(shù)據(jù)宛琅,不需要每次都請求的刻蟹,應(yīng)該緩存起來,可以寫入數(shù)據(jù)庫嘿辟,也可以通過plist文件存儲

  • 選擇正確的數(shù)據(jù)結(jié)構(gòu):針對不同的業(yè)務(wù)場景選擇最合適的數(shù)據(jù)結(jié)構(gòu)是寫出高效代碼的基礎(chǔ)

    • 數(shù)組:有序的一組值舆瘪,使用索引查詢起來很快,使用值查詢的很慢红伦,插入/刪除 很慢
    • 字典:存儲鍵值對對英古,用鍵查找比較快
    • 集合:無序的一組值,用值來查找很快昙读,插入/刪除很快
  • gzip/zip壓縮:當(dāng)從服務(wù)器下載相關(guān)附件時召调,可以通過 zip壓縮后再下載,使得內(nèi)存更小蛮浑,下載速度也更快

  • 重大開銷對象:一些objects的初始化很慢唠叛,比如NSDateFormatter和 NSCalendar,但是又無可避免的需要使用沮稚,通常作為屬性存儲起來艺沼,避免反復(fù)使用

  • 避免反復(fù)處理數(shù)據(jù):需要應(yīng)用需要從服務(wù)器加載數(shù)據(jù),常為JSON或者XML格式的數(shù)據(jù)蕴掏,在服務(wù)器端或者客戶端使用相同的數(shù)據(jù)結(jié)構(gòu)很重要

  • 選擇圖片時障般,要對圖片進行壓縮處理,根據(jù)不同的情況選擇不同的圖片加載方式盛杰,-imageNamed:讀取到內(nèi)存后會緩存下來挽荡,適合圖片資源較小,使用很頻繁的圖片饶唤;-initWithContentsOfFiles:僅加載圖片而不緩存徐伐,適合較大的圖片贯钩。若是collectionView中使用大量圖片的時候募狂,可以用UIVIew.layer.contents=(__bridge id _Nullable)(model.clipedImage.CGImage);這樣就更輕量級一些

  • 當(dāng)然有時候也會用到一些第三方角雷,比如在使用UICollectionView和UITableView的時候祸穷,F(xiàn)acebook有一個框架叫AsyncDisplayKit,這個庫就可以很好地提升滾動時流暢性以及圖片異步下載功能(不支持sb和autoLayout勺三,需要手動進行約束設(shè)置)雷滚,AsyncDisplayKit用相關(guān)node類,替換了UIView和它的子類,而且是線程安全的吗坚。它可以異步解碼圖片祈远,調(diào)整圖片大小以及對圖片和文本進行渲染呆万,把這些操作都放到子線程,滑動的時候就流暢許多车份。我認為這個庫最方便的就是實現(xiàn)圖片異步解碼谋减。UIImage顯示之前必須要先解碼完成,而且解碼還是同步的扫沼。尤其是在UICollectionView/UITableView 中使用 prototype cell顯示大圖出爹,UIImage的同步解碼在滾動的時候會有明顯的卡頓。另外一個很吸引人的點是AsyncDisplayKit可以把view層次結(jié)構(gòu)轉(zhuǎn)成layer缎除。因為復(fù)雜的view層次結(jié)構(gòu)開銷很大严就,如果不需要view特有的功能(例如點擊事件),就可以使用AsyncDisplayKit 的layer backing特性從而獲得一些額外的提升器罐。當(dāng)然這個庫還處于開發(fā)階段梢为,還有一些地方地方有待完善,比如不支持緩存轰坊,我要使用這個庫的時候一般是結(jié)合Alamofire和AlamofireImage實現(xiàn)圖片的緩存

希望此文對您的求職或夯實基礎(chǔ)起到作用抖誉,感謝閱讀!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末衰倦,一起剝皮案震驚了整個濱河市袒炉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌樊零,老刑警劉巖我磁,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異驻襟,居然都是意外死亡夺艰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門沉衣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來郁副,“玉大人,你說我怎么就攤上這事豌习〈婊眩” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵肥隆,是天一觀的道長既荚。 經(jīng)常有香客問我,道長栋艳,這世上最難降的妖魔是什么恰聘? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上晴叨,老公的妹妹穿的比我還像新娘凿宾。我一直安慰自己,他們只是感情好兼蕊,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布菌湃。 她就那樣靜靜地躺著,像睡著了一般遍略。 火紅的嫁衣襯著肌膚如雪惧所。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天绪杏,我揣著相機與錄音下愈,去河邊找鬼。 笑死蕾久,一個胖子當(dāng)著我的面吹牛势似,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播僧著,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼履因,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了盹愚?” 一聲冷哼從身側(cè)響起栅迄,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎皆怕,沒想到半個月后毅舆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡愈腾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年憋活,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虱黄。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡悦即,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出橱乱,到底是詐尸還是另有隱情辜梳,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布仅醇,位于F島的核電站冗美,受9級特大地震影響魔种,放射性物質(zhì)發(fā)生泄漏析二。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叶摄。 院中可真熱鬧属韧,春花似錦、人聲如沸蛤吓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽会傲。三九已至锅棕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間淌山,已是汗流浹背裸燎。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泼疑,地道東北人德绿。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像退渗,于是被迫代替她去往敵國和親移稳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 作者si1ence2016.05.20 10:24* http://www.reibang.com/p/bc3f8...
    Kiddz閱讀 1,198評論 0 12
  • 1.1 談一談GCD和NSOperation的區(qū)別? 首先二者都是多線程相關(guān)的概念翻翩,當(dāng)然在使用中也是根據(jù)不同情境進...
    John_LS閱讀 1,312評論 0 12
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,139評論 30 470
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理几蜻,服務(wù)發(fā)現(xiàn),斷路器体斩,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 等待總是漫長的 如這空蕩蕩的道軌梭稚,無精打采 欲用長焦距拍攝,璄頭里空無一物 如果是晚點絮吵,調(diào)度長應(yīng)該受罰 讓我傻傻的...
    狼吻與蝶花閱讀 304評論 11 13