來來來!關於iOS基礎總結咱倆好好嘮嘮

2016.05.20 10:24java

塵封已久的學習基礎總結,最近公司項目不是很忙,終於抽空整理出來,現分享出來。node

1.1 談一談GCD和NSOperation的區別?

  • 首先兩者都是多線程相關的概念,固然在使用中也是根據不一樣情境進行不一樣的選擇;
  • GCD是將任務添加到隊列中(串行/併發/主隊列),而且制定任務執行的函數(同步/異步),其性能最好,底層是C語言的API,也更輕量級。iOS4.0之後推出的,針對多核處理器的併發技術,只能設置某一個隊列的優先級,經常使用的功能包括 一次性執行 dispatch_once延遲操做dispatch_after(這裏是延遲推到線程中,而不是在線程中等待,所以好比設置延遲1秒執行,可是一秒後只是推到了線程中,不會馬上執行),調度組等,其高級功能有
    • dispatch_barrier_async柵欄來控制異步操做的順序
    • dispatch_apply充分利用多核進行快速迭代遍歷
    • dispatch_group_t隊列組,添加到隊列組中的任務完成以後會調用dispatch_group_notify 函數,能夠實現相似A、B兩個耗時操做都完成以後,去主線程更新UI的操做
  • NSOperation把操做(異步)添加到隊列中(全局的併發隊列),是OC框架,更加面向對象,是對GCD的封裝,iOS2.0推出,蘋果推出GCD以後,對NSOperation的底層所有重寫,能夠隨時取消已經設定準備要執行的任務,已經執行的除外,能夠設置隊列中每個操做的優先級,其基本功能包括設置最大操做併發數maxConcurrentOperationCount,繼續/暫停/所有取消,能夠快隊列設置操做的依賴關係,經過KVO監聽 NSOperation 對象的屬性,如 isCancelled、isFinished;對象可重用。
    • NSInvocationOperationNSBlockOperation建立方法等這些基礎面試官每每默認你是會的
    • NSOperationQueue只有兩種隊列:主隊列、其餘隊列。其餘隊列包含了串行和併發
    • NSOperation + NSOperationQueue將任務加入到隊列
    • 操做依賴:[operation2 addDependency:operation1];(operation2 依賴於operation1的完成,但這兩個任務要加入到同一個隊列中)

1.2 談談多線程的應用

一般耗時的操做都放在子線程處理,而後到主線程更新UI,如ios

  • 咱們要從數據庫提取數據還要將數據分組後顯示,那麼就會開個子線程來處理,處理完成後纔去刷新UI顯示。
  • 拍照後,會在子線程處理圖片,完成後纔回到主線程來顯示圖片。拍照出來的圖片太大了,所以要作處理。
  • 音頻、視頻處理會在子線程來操做
  • 文件較大時,文件操做會在子線程中處理
  • 作客戶端與服務端數據同步時,會在後臺閒時自動同步

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

  • 經過主線程和子線程切換的時候傳遞參數performSelecter:onThread:withObject:waitUntilDone:

3. 網絡圖片處理問題怎麼解決圖片重複下載問題?(SDWebImage大概實現原理)

  • 這個就須要用到字典,以圖片的下載地址url爲key,下載操做爲value,全部的圖片大概分紅三類:已經下載好的,正在下載的和將要下載的;面試

  • 當一張圖片將要進行下載操做的時候,先判斷緩存中是否有相同的圖片,若是有的話就返回,沒有的話就根據url的md5加密值去沙盒中找,有的話就拿出來用,沒有的話再去以圖片的url爲key去字典中找有沒有正在進行的任務,最後去判斷等待的下載操做任務裏面的字典有無相同key,若是沒有,就本身開啓任務,記錄一下,文件保存的名稱是url的md5值spring

  • 這裏創建了兩個字典 : 1.iconCache:保存緩存的圖片 2.blockOperation 用來保存下載任務 數據庫

    653183AF-3074-47AD-8460-10B5CEF1323C.png

  • 每當進入或退出程序時,會進行圖片文件的管理:超過一星期的文件會被清除,若是設置了最大緩存,超過這個緩存就會刪除最舊的文件,直到當前緩存文件爲最大緩存文件的一半大小;swift

  • 通常app中大部分緩存都是圖片的狀況下,能夠直接調用clear方法進行清除緩存,getSize()方法獲取當前緩存大小。設計模式

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

  • 1> 只有在主線程刷新訪問UI
  • 2> 若是要防止資源搶奪,須要用synchronize進行加鎖保護
  • 3> 若是是異步操做要保證線程安全等問題,儘可能使用GCD(有些函數默認就是安全的)
  • 4> 單例爲何用static dispatch_once?使用dispatch_once能夠簡化代碼而且完全保證線程安全,開發者無需擔憂加鎖或同步。此外,dispatch_once更高效,它沒有使用重量級的同步機制,如果那樣作的話,每次運行代碼前都要獲取鎖。

5. 原子屬性

  • 原子屬性採用的是"多讀單寫"機制的多線程策略;"多讀單寫"縮小了鎖範圍,比互斥鎖的性能好
  • 規定只在主線程更新UI,就是由於若是在多線程中更新,就須要給UI對象加鎖,防止資源搶佔寫入錯誤,可是這樣會下降UI交互的性能,因此ios設計讓全部UI對象都是非線程安全的(不加鎖)

6. 代理的做用、block

  • 代理又叫委託,是一種設計模式(能夠理解爲java中回調監聽機制),代理是對象與對象之間的通訊交互,代理解除了對象之間的耦合性
  • 改變或傳遞控制鏈,容許一個類在某些特定時刻通知到其餘類,而不須要獲取到那些類的指針,能夠減小框架複雜度
  • 代理的屬性常是 weak 的緣由:防止循環引用,以至對象沒法獲得正確的釋放(具體緣由會在下文第十一條闡述)
  • block底層是根據函數指針和結構體結合實現的,block自己就是結構體,更加簡潔,不須要定義繁瑣的協議方法,但通訊事件比較多的話,建議使用Delegate
  • block就是一個數據類型,存放一段代碼,編譯的時候不會執行,只有用到的時候纔會去執行裏面的代碼。聲明的時候使用copy是由於要從棧區拷貝到堆區,在棧區會受到做用域的限制,超出所在的函數就會被銷燬,就沒辦法進行傳值回調等一系列操做了。應注意循環引用,__weak來修飾。若是一個變量是在block外部建立,須要在block內部修改,那麼須要使用__block修飾這個變量(__block能夠在ARC和MRC狀況下使用,能夠修飾對象和基本數據類型,__weak只能在ARC下使用,只能修飾對象,不能修飾基本數據類型)
  • 最經常使用的是使用block做爲參數傳值,不一樣狀況下回調不一樣的代碼(如成功回調失敗回調)

7. 談談你對runTime運行時機制的瞭解(注意哦,這個很重要的)

  • runtime是一套比較底層的純C語言API,屬於一個C語言庫,包含了不少底層的C語言的API
  • 平時編寫的OC代碼,在程序運行過程當中,其實都是轉成了runtime的C語言代碼,runtime是OC的幕後工做者,底層語言,例如:
    • OC--> [[WPFPerson alloc] init]
    • runtime-->objc_msgSend(objc_msgSend("WPFPerson", "alloc"), "init")
  • 利用runtime能夠實現一些很是底層的操做(用OC很差實現)
    • 在程序運行過程當中,動態建立一個類(好比KVO底層實現:檢測isa指針,發現是新建了一個類,固然Xcode7.0之前的版本才能夠監聽到isa指針)
    • 遍歷一個類的全部成員變量、方法,訪問私有變量(先經過runtime的class_getInstanceVariable獲取成員變量,再經過class_getIvar獲取它的值)
    • 在程序運行過程當中,動態爲某個類添加屬性\方法,修改屬性值\方法,好比產品經理須要跟蹤記錄APP中按鈕的點擊次數和頻率等數據,能夠經過集成按鈕或者類別實現,可是帶來的問題好比別人不必定去實例化你寫的子類,或者其餘類別也實現了點擊方法致使不肯定會調用哪個,runtime能夠這樣解決:在按鈕的分類裏面,重寫load方法,新建監控按鈕點擊的方法,先用class_addMethod方法,判斷其返回的bool值,若是添加成功,就用class_replaceMethod將原來的方法移除,若是添加失敗,就用method_exchangeImplementations方法進行替換
    • 攔截並替換方法,好比因爲某種緣由,咱們要改變這個方法的實現,可是又不能動它的源碼(好比一些開源庫出現問題的時候,這時候runtime就能夠出場了)-->先增長一個tool類,而後寫一個咱們本身實現的方法-change,經過runtime的class_getInstanceMethod獲取兩個方法,在用class_replaceMethod方法進行替換。防止數組越界的方法:數組越界的時候報錯的方法是add_object,作一個邏輯判斷,越界的時候經過class_replaceMethod交換掉add_object(至關於重寫了這個方法)
  • 相關應用
    • NSCoding(歸檔和解檔),若是一個模型有不少個屬性,那麼須要對每一個屬性都實現一遍encodeObject和decodeObjectForKey方法,十分麻煩,可是若是使用class_copyIvarList獲取全部屬性,而後循環遍歷,使用[ivarName substringFromIndex:1]去掉成員變量下劃線
    • 字典轉模型:像幾個出名的開源庫JSONModel、MJExtension等都是經過這種方式實現的(利用runtime的class_copyIvarList獲取屬性數組,遍歷模型對象的全部成員屬性,根據屬性名找到字典中key值進行賦值,固然這種方法只能解決NSString、NSNumber等,若是含有NSArray或NSDictionary,還要進行第二步轉換,若是是字典數組,須要遍歷數組中的字典,利用objectWithDict方法將字典轉化爲模型,在將模型放到數組中,最後把這個模型數組賦值給以前的字典數組)
  • Method Swizzling:OC中調用方法事實上就是向對象發送消息,而查找消息的惟一依據就是selector的名字,所以可使用runtime運行時機制動態交換方法。在+load方法裏面調換,由於method swizzling的影響範圍是全局的,因此應該放在最保險的地方來處理,+load方法可以保證能在類初始化的時候必定能被調用,能夠保證統一性,若是是在使用的時候纔去調用,可能達不到全局處理的效果;使用dispatch_once保證只交換一次。[objc_getClass("__NSArrayM") swizzleSelector:@selector(addObject:) withSwizzledSelector:@selector(wpf_safeAddObject:)];使用場景:addObject方法添加的值爲nil的時候會崩潰。調用objectAtIndex:時出現崩潰提示empty數組問題

8. 談談你對Run Loop的理解

  • RunLoop是多線程的一個很重要的機制,就是一個線程一次只能執行一個任務,執行完任務後就會退出線程。主線程會經過do-while死循環讓程序持續等待下一個任務不退出。經過mach_msg()讓runloop沒事時進入trap狀態,節省CPU資源。非主線程一般來講就是爲了執行某個任務而建立的,執行完就會歸還資源,所以默認不開啓RunLoop
  • 實質上,對於子線程的runloop是默認不存在的,由於蘋果採用了懶加載的方式,若是沒有手動調用[NSRunLoop currentRunLoop]的話,就不會去查詢當前線程的RunLoop,也不會建立、加載
  • 固然若是子線程處理完某個任務後不退出,須要繼續等待接受事件,須要啓動的時候也能夠手動啓動,好比說添加定時器的時候就要手動開始RunLoop

如何處理事件數組

  • 界面刷新: 當UI改變( Frame變化、 UIView/CALayer 的繼承結構變化等)時,或手動調用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法後,這個 UIView/CALayer 就被標記爲待處理。 蘋果註冊了一個用來監聽BeforeWaiting和Exit的Observer,在它的回調函數裏會遍歷全部待處理的 UIView/CAlayer 以執行實際的繪製和調整,並更新 UI 界面。瀏覽器

  • 手勢識別: 若是上一步的 _UIApplicationHandleEventQueue() 識別到是一個guesture手勢,會調用Cancel方法將當前的touchesBegin/Move/End 系列回調打斷。隨後系統將對應的 UIGestureRecognizer 標記爲待處理。 蘋果註冊了一個 Observer 監測 BeforeWaiting (Loop即將進入休眠) 事件,其回調函數爲 _UIGestureRecognizerUpdateObserver(),其內部會獲取全部剛被標記爲待處理的 GestureRecognizer,並執行GestureRecognizer的回調。 當有 UIGestureRecognizer 的變化(建立/銷燬/狀態改變)時,這個回調都會進行相應處理。

  • 網絡請求:最底層是CFSocket層,而後是CFNetwork將其封裝,而後是NSURLConnection對CFNetwork進行面向對象的封裝。當網絡開始傳輸時,NSURLConnection建立了兩個新線程:com.apple.NSURLConnectionLoader和com.apple.CFSocket.private。其中CFSocket線程是處理底層socket鏈接的。NSURLConnectionLoader這個線程內部會使用RunLoop來接受底層socket的事件,並添加到上層的Delegate

應用

  • 滑動與圖片刷新:當tableView的cell上有須要從網絡獲取的圖片的時候,滾動tableView,異步線程回去加載圖片,加載完成後主線程會設置cell的圖片,可是會形成卡頓。能夠設置圖片的任務在CFRunloopDefaultMode下進行,當滾動tableView的時候,Runloop切換到UITrackingRunLoopMode,不去設置圖片,而是而是當中止的時候,再去設置圖片。(在viewDidLoad中調用self.imageView performSelector@selector(setImage) withObject:...afterDelay:...inModes@[NSDefayltRunLoopMode])

  • 常駐子線程,保持子線程一直處理事件 爲了保證線程長期運轉,能夠在子線程中加入RunLoop,而且給Runloop設置item,防止Runloop自動退出

10. 關於Socket,談談TCP/IP 和 UDP的理解

  • Socket是一個用於傳輸網絡數據的工具,TCP/IP 和 UDP都是傳輸協議,用於定義網絡數據傳輸的格式,屬於長鏈接
  • TCP/IP 側重可靠傳輸,傳輸速度慢,不會丟失數據,安全,聊天和下載文件時用到
  • UDP:側重快速傳輸,傳輸速度快,容易丟失數據包,不安全。局域網遊戲和網絡遊戲,視頻聊天的時候用到
  • TCP更安全是由於有一個三次握手:第一次握手(客戶端發送syn包到服務器,並進入SYN_SEND狀態,等待服務器確認),第二次握手(服務器收到syn包,必須確認客戶的SYN包,同時本身發送一個SYN+ACK包,此時服務器進入SYN_RECV狀態),第三次握手(客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK,發送完畢後服務器和客戶端都進入ESTABLISHED狀態,完成三次握手),三次握手以後纔開始正式傳輸數據。
  • 那麼如今即時通信更適合用TCP仍是UDP?
    • 早期使用MSN是使用TCP協議的,QQ使用採用UDP的,但並不表明UDP就不安全,由於能夠手動對UDP的數據收發進行驗證(好比發送方對每一個數據包進行編號而後由接收方進行驗證),正是由於這個,QQ的傳遞速度是遠遠快於MSN,可能這也是戰勝MSN的其中一個緣由吧
  • Http:超文本傳輸協議,用於定義網絡數據傳輸的格式(短連接)http1.0以前不支持短鏈接,1.1以後默認就是長鏈接,只要在服務器和客戶端同時設置Connection爲keep-alive便可
    • 長鏈接是爲了複用,長鏈接指的是TCP鏈接,也就是爲了複用TCP鏈接,也就是說多個HTTP請求能夠複用一個TCP鏈接,節省了不少TCP鏈接創建和斷開的消耗
    • 好比請求了一個網頁,這個網頁確定還包含了CSS、JS等一系列資源,若是是短鏈接的話,每次打開一個網頁,基本要創建幾個甚至幾十個TCP鏈接,浪費了大量資源
    • 長鏈接不是永久鏈接,若是一段時間內,具體的時間長短,是能夠在header當中進行設置的,也就是所謂的超時時間,這個鏈接沒有HTTP請求發出的話,那麼這個長鏈接就會被斷掉
  • socket鏈接是長鏈接,客戶端與服務器保持通道,雙方能夠主動發送數據,通常多用於即時通信,遊戲,默認超時時間是30秒,默認大小是8k(一個數據包大小)

11. 談一談內存管理

  • iOS的內存管理分爲 MRC 和 ARC,管理的是堆區動態產生的對象,基本數據類型就不是內存管理的範圍
  • 內存管理的核心概念是引用計數器:當對象被alloc、copy、new的時候,引用計數器+1,當被release的時候引用計數器—1,爲0的時候就會被系統回收,調用dealloc方法
  • 說道內存管理,就必須說說@property的內存管理參數:
    • assign --> 針對於基本數據類型的簡單賦值操做
    • retain --> release 一次舊對象 retain 一次新對象 (適用於OC對象類型)
    • copy --> release 一次舊對象 拷貝一個新對象出來(通常修飾字符串和block)
    • weak--> 表示一種非擁有關係,設置該屬性時既不釋放新值,也不保留舊值,和assign相似,可是目標對象釋放時,屬性值也會自動清空
  • 如何避免內存泄露 --> 使用Analyze進行代碼的靜態分析
  • 固然使用block的時候最應該注意下循環引用,使用Leaks檢測內存泄露,顯示綠色的勾告知內存處理的不錯,實際上內存得不到釋放。通常個人方法是在控制器聲明週期的viewDidAppear和dealloc方法裏面打印日誌[[self class] description],若是沒有打印出來,就說明沒有被釋放。使用__weak __typeof(self) weakSelf = self;解決。有一次我是直接使用成員變量,而不是屬性,_age,我覺得這樣沒有使用self就能夠了,可是後來測試發現仍是形成循環引用了,由於_age是控制器的成員變量,也就是強引用了控制器,也要改爲弱引用__block __weak __typeof(_currentModel) weakModel = _currentModel;

11.1 爲何用 weak 修飾的變量會自動置爲 nil?

  • runtime 對註冊類,會進行佈局,將 weak 修飾的對象放到一個hash表中,key值是該對象的內存地址,value是該對象
  • 當該對象retainCount爲0時,執行dealloc,根據該地址去weak的hash表中查詢到該對象,從而設置爲nil(向nil發送消息是安全的)

12. 常見的數據持久化有哪些

  • 偏好設置(preference),利用NSUserDefaults
    • 用來保存應用程序設置和屬性、用戶保存的數據。用戶再次打開程序或開機後這些數據仍然存在
    • NSUserDefaults能夠存儲的數據類型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。若是要存儲其餘類型,須要先轉化爲前面的類型,才能用NSUserDefault存儲
    • 偏好設置是專門用來保存應用程序的配置信息的,通常不要在偏好設置中保存其餘數據
    • 偏好設置會將全部數據保存到同一個文件中。即preference目錄下的一個以此應用包名來命名的plist文件。
//1.得到NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向文件中寫入內容
[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實現歸檔、利用NSKeyedUnarchiver反接的那個
    • 歸檔及時將內存中的對象寫入到磁盤文件中,歸檔也叫序列化,解檔就是講磁盤中文件中的對象讀取出來
    • 必須遵循NSCoding協議,只要遵循了NSCoding協議的對象均可以經過它實現序列化,兩個協議方法必須實現
// 反歸檔
  - (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"];
  }
複製代碼
* 歸檔,把對象歸檔時須要調用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];
  }
複製代碼

這五種持久化操做不一樣點

  • 從存儲數據大小來看,歸檔、偏好設置、屬性列表三種方法適合存儲數據量較小的數據,數據庫、CoreData方法適合存儲數據量較大的數據

  • 從加密性來看,其中歸檔會將數據進行加密,而偏好設置是直接保存到屬性列表中,不會對數據進行加密

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

13. KVC 和 KVO

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

    • 給私有變量(該變量不對外開放)賦值:[Person setValue: @"19" ForKeyPath:@"age"]
    • 字典轉模型:setValuesForKeyWithDictionary
    • 取出私有變量:[Person valueForKey:@"age"]
    • 沒有找到對應的key會崩潰:重寫setValueForUndefinedKey
  • KVC缺點:一旦使用KVC,編譯器沒法檢查出錯誤,即不會對設置的鍵、鍵路徑進行錯誤檢查,且執行效率低於自定義的setter和getter方法,由於使用KVC鍵值編值,必須先解析字符串,而後設置或訪問對象的實例變量

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

    • 給監聽的屬性設置一個觀察者:
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
複製代碼
* 當person的name的值發生改變時,就會執行該方法
複製代碼
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
  do something....
}
複製代碼
  • 當一個類的屬性被觀察的時候,系統會經過runtime動態的建立一個該類的派生類,而且會在這個類中重寫基類被觀察的屬性的setter方法,並且系統將這個類的isa指針指向了派生類(NSNotifying_類名),從而實現了給監聽的屬性賦值時調用的是派生類的setter方法。重寫的setter方法會在調用原setter方法先後,通知觀察對象值得改變。

14. @synthesize和@dynamic區別是什麼

  • 這兩個關鍵字都是@property對應的詞
  • @synthesize 語義是若是沒有手動實現setter和getter方法,那麼編譯器會自動幫你加上這兩個方法
  • @dynamic告訴編譯器,屬性的setter和getter由用戶本身實現,不自動生成(readOnly只實現getter便可),可是若是沒有本身實現,編譯的時候不會報錯,運行的時候就會報錯,這就是所謂的動態綁定

15. 談談時間響應鏈的通常順序

  • 第一步:事件的產生:首先找到最合適的從UIApplication到keyWindow依次找到view,由上到下
    • 發生觸摸事件後,系統會將該事件加入到一個由UIApplication管理的事件隊列中,爲何是隊列而不是棧,由於隊列先進先出,先產生的事件優先處理才合理
    • UIApplication 會從事件隊列中取出最前面的事件,並將事件分發下去以便處理,一般,先把事件發送給應用程序的主窗口(keywindow)
    • 主窗口會在視圖層級結構中找到一個最合適的視圖來處理觸摸事件(hitTest:方法遍歷當前view的全部子類,返回最合適的view)
    • 找到合適的視圖控件後,就會用視圖控件的touches 方法來作具體的事件處理
  • 因爲是從父視圖到子視圖,所以若是父view不接收事件,自視圖也沒法接收事件
  • 若是肯定父控件是最合適的view,那麼父控件的子控件的hitTest:withEvent: 方法也會被調用
  • 想讓誰成爲最合適的view就重寫本身的父控件的hitTest:withEvent: 方法,返回指定的控件
  • 第二步:事件的響應:由下到上
    • 先看inital view可否處理這個事件,若是不能依次往上傳遞(如self或superView的userInterface/enable屬性爲NO,透明度小於0.1等緣由),一直傳遞到該視圖的VC/window/application ,若是application還不能處理,就將該事件拋棄
    • 在事件的響應過程當中,若是某控件實現了 touches 方法,則這個事件由該控件來接收,若是調用了[super touches] 方法,就會將事件順着響應者鏈條往上傳遞,傳遞給上一個響應者

16.1 post和get方式的區別

  • GET請求的數據會負載URL以後,即把數據放在HTTP協議頭中,以?區分URL和傳輸數據,參數之間以&相連,英文字母/數字,原樣發送,若是是空格,轉化爲+,若是是中文,把字符串用BASE64加密;POST就是把提交的數據放在HTTP包的包體中

  • GET通常用於提交少許數據(最多提交1k,瀏覽器限制),POST用於提交大量數據(理論上無限制,收服務器限制)

  • GET 無反作用,POST 有反作用

  • GET提交的數據能夠在瀏覽器歷史記錄中看到,安全性很差,別人能夠拿到帳號密碼,POST不會

  • Get是向服務器發索取數據的一種請求,而POST是向服務器發提交數據的一種請求,只是發送機制不一樣

  • GET不能夠設置書籤,POST能夠設置書籤

  • POST支持更多編碼類型且不對數據類型限制

  • 什麼狀況下用POST:

    • 請求的結果具備持續性反作用,如數據庫添加新的數據行
    • 若使用get方法,則表單上手機的數據可能讓URL過長
    • 要傳送的數據不是採用7位的ASCII編碼
  • 什麼狀況下用GET:

    • 請求是爲了查找資源,HTML表單數據僅用來幫助搜索
    • 請求結果無持續反作用性的反作用
    • 手機的數據及HTML表單內的輸入字段名稱的總長不超過1024個字符

16.2 POST和PUT區別

  • POST請求的url表示處理該封閉實體的資源,該資源多是個數據接收過程、某種協議的網關、或者接收註解的獨立實體。

  • PUT請求中的url表示請求中封閉的實體-用戶代理知道url的目標,而且服務器沒法將請求應用到其餘資源。若是服務器但願該請求應用到另外一個url,就必須發送一個301響應;用戶代理可經過本身的判斷來決定是否轉發該請求。

  • POST是用來提交數據的。提交的數據放在HTTP請求的正文裏,目的在於提交數據並用於服務器端的存儲,而不容許用戶過多的更改相應數據(主要是相對於在url 修改要麻煩不少)。

  • PUT操做是冪等的。所謂冪等是指無論進行多少次操做,結果都同樣。好比我用PUT修改一篇文章,而後在作一樣的操做,每次操做後的結果並無不一樣

  • POST操做既不是安全的,也不是冪等的,好比常見的POST重複加載問題:當咱們屢次發出一樣的POST請求後,其結果是建立出了若干的資源。

  • 安全和冪等的意義在於:當操做沒有達到預期的目標時,咱們能夠不停的重試,而不會對資源產生反作用。從這個意義上說,POST操做每每是有害的,但不少時候咱們仍是不得不使用它。

  • 還有一點須要注意的就是,建立操做可使用POST,也可使用PUT,區別在於POST 是做用在一個集合資源之上的,而PUT操做是做用在集合的一個具體資源之上的,再通俗點說,若是URL能夠在客戶端肯定,那麼就使用PUT,若是是在服務端肯定,那麼就使用POST,好比說不少資源使用數據庫自增主鍵做爲標識信息,而建立的資源的標識信息究竟是什麼只能由服務端提供,這個時候就必須使用POST。

17. 深複製和淺複製

  • 非集合類對immutable對象進行copy操做,是指針複製,mutableCopy操做時內容複製
  • 非集合類對mutable對象進行copy和mutableCopy都是內容複製
  • 在集合類對象中,對immutable對象進行copy,是指針複製,mutableCopy是內容複製
  • 在集合類對象中,對mutable對象進行copy和mutableCopy都是內容複製。可是:集合對象的內容複製僅限於對象自己,對象元素仍然是指針複製
  • copy出來的對象都是不可變的,mutableCopy出來的對象都是可變的
  • NSString *str = @"string"; str = @"newString"; 打印對象地址,發現是發生變化的,須要把@"newStirng"當作一個新的對象,將這段對象的內存地址賦值給str

18. 關於項目中動畫的使用

  • 序列幀動畫:self.imageView.animationImages = array;
  • [UIView animateWithDuration] + CGAffinetransform
  • 核心動畫CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position.y"]; anim.fromValue toValue repeatCount [btn.layer addAnimation]
  • 關鍵幀動畫CAKeyframeAnimation,anim.values = array,添加到layer上
  • 組動畫CAAnimationGroup,將以上動畫組合起來
  • 轉場動畫:CATransition,設置duration和type,而後添加到layer上。利用UIView 的類方法實現轉場動畫 [UIView transitionWithView: duration: options: animations:^{ } completion:nil];
  • UIDynamicAnimator仿真者 、 UISnapBehavior吸附行爲,設置damping來調節震動幅度 、 UIPushBehavior推進行爲 、 UICollisionBehavior碰撞邊緣檢測行爲 、 UIAttachmentBehavior附着行爲 、 UIGravityBehavior重力行爲
  • POPSpringAnimation
    • springBounciness[0,20]越大振幅越大。
    • springSpeed速度

20.0 關於Swift與OC的不一樣

實際上是個面試官八個不懂Swift,並且通常不懂得就問個Swift與OC的不一樣。。題主也只是在自學Swift,等到3.0出了以後再深刻研究,並且項目中可能也要開始從混編逐漸向Swift靠攏了。

  • Swift是一門更加現代化的語言,可是目前還在成長階段,更新改動比較大,雖說其底層思想不變,變的是API和接口,可是每次更新完Xcode看到本身的Swift項目仍是有些淡淡的憂傷,並且目前Swift開發都要轉成OC的runtime,包略大,所以題主認爲成熟項目最好仍是採用OC

  • 先記住一句話:OC底層面向對象,而Swift底層更加面向協議

  • 咱們已經見識過Apple使用了大量協議,好比在tableView當中,咱們能夠經過協議來告訴Apple須要多少個表視圖單元格,而不是每時每刻都要繼承UITableViewController

  • 在這裏以MVVM做爲測試用例:好比如今須要創建一個相似設置界面的tableView,每一個cell須要一個label和一個switch,自定義SwitchWithTextTableViewCell,在其內部創建一個configure方法中對label的title,titleFont,titleColor,switch的switchOn和switchColor等進行初始化,但這種方式很是累贅,好比添加一個副標題,就須要額外添加三個屬性

  • 可是利用協議SwitchWithTextCellProtocol,讓視圖模型實現這個協議,而後在這裏設置全部的屬性

protocol SwitchWithTextCellProtocol {
    var title: String { get }
    var titleFont: UIFont { get }
    var titleColor: UIColor { get }

    var switchOn: Bool { get }
    var switchColor: UIColor { get }

    func onSwitchTogglenOn(onL Bool)
}
複製代碼
  • 經過swift2.0重點餓協議擴展,就能夠經過默認值來作一些處理了,若是對於大多數單元格來講,能夠肯定某一種顏色的話,就能夠對其創建擴展,而後設置顏色便可,全部實現此協議的視圖就沒有必要再去設置這個顏色了

  • 如今,個人configure方法裏面只要實現此協議的值就能夠了

// 這個方法只須要一個參數,相比於以前的多個參數簡便了不少
class SwitchWithTextTableViewCell: UITableViewCell {
    func configure(withDelegate delagate: SwitchWithTextCellProtocol) {
        // 在這裏配置方法
    }
}
複製代碼
  • 如今的視圖模型
struct MinionModeViewController: SwitchWithTextCellProtocol {
    var title = "excellent!!"
    var switchOn = true

    var switchColor: UIColor {
        return .yellowColor()
    }

    func onSwitchToggleOn(on: Bool) {
        if on {
            print("The Minions are here to stay!")
        } else {
            print("The Minions went out to play!")
        }
    }
}
複製代碼
  • 如今,cellForRowAtIndexPath()也變得很是簡明瞭
let cell = tableView.dequeueReuseableCellWithIdentifier("SwitchWithTextTableViewCell", forIndexPath: indexPath) as! SwitchWithTextTableViewCell

cell.configure(withDelegate: MinionModeViewModel())

return cell
複製代碼

再把模型放在視圖模型層級,一遍對其進行跟蹤,再視圖模型中傳遞這些信息,這樣單元格就能夠生成了

  • 可是在這個基礎上,還能夠再作進一步的深化,就是創建兩個協議,一個做爲實際編碼的數據源,好比標題內容之類的實際數據,一個做爲單元格委託,存儲顏色、字體之類的並無包含實際數據的信息,也就是仿照Apple中UITableView等集合視圖之類的地方,按照這種思惟去創建單元格存儲和單元格委託
protocol SwitchWithTextCellDataSource {
    var title: String { get }
    var switchOn: Bool { get }
}

protocol SwitchWithTextCellDelegate {
    func onSwitchTogglenOn(on: Bool)

    var switchColor: UIColor { get }
    var textColor: UIColor { get }
    var font: UIFont { get }
}
複製代碼
  • 接下來,再讓configure方法同時接受這兩個協議。由於委託能夠所有在協議擴展中使用默認值進行配置,好比說字體、顏色之類的信息,這樣在理論上就能夠不用向裏面傳遞任何東西進去,只須要建立一個模型就能夠了
// SwitchWithTextTableViewCell
func configure(withDataSource dataSource: SwitchWithTextCellDataSource, delegate: SwitchWithTextCellDelegate?) {
    // 在這裏配置視圖
}
複製代碼
  • 而後就要開始經過擴展來改進視圖模型了,使用一個實際數據源的代碼塊,而後給定要傳遞的視圖當中的原始信息
struct MinionModeViewModel: SwiftWithTextCellDataSource {
    var title = "Minion Mode!!"
    var switchOn = true
}
複製代碼
  • 接下來會在一個單獨的視圖模型的部分使用處理字體、顏色之類的委託,而後在其中進行相關的配置
extension MinionModeViewModel: SwitchWithTextCellDelegate {

    var switchColor: UIColor {
        return .yellowColor()
    }

    func onSwitchToggleOn(on: Bool) {
        if on {
            print("The Minions are here to stay!")
        } else {
            print("The Minions went out to play!")
        }
    }
}
複製代碼
  • 最終,表格視圖單元格變得很是簡單
// SettingViewController
let viewModel = MinionModeViewModel()
cell.configure(withDataSource:viewModel, delegate: viewModel)

return cell
複製代碼

僅僅須要建立視圖模型,而後將其傳遞到配置方法當中,最後返回單元格,就能夠了

20.1 Swift2.0中的 Minxin 和 Trait

  • 在遊戲開發中一般會有一個很龐大的層級關係,以及一系列的繼承,好比各類怪,繼承在這裏顯得十分有意義,可是隨着層級的擴展,這個項目就會變得凌亂起來

  • 好比說須要設計一個能夠射擊的怪物,但這時候塔防頂部的大炮也會射擊,就須要把「射擊輔助類」提取出來,可是若是一直這樣提取子類,代碼後面會一團亂麻

  • 將這個代碼重構,再也不去提取可以射擊或者可以加血的子類,而是將其提取爲協議,經過協議擴展來實現這個功能,代碼更加簡潔,更利於理解

// 一看這個對象的類型,就知道他有哪些功能,而不是一個個去查找她的實現
class ZapMonster: GameObject, GunTraint, HealthTraint, MovementTraint {
    ...
}
複製代碼
  • 雖說這種設計模式是遊戲方面的,可是咱們平時的代碼也能夠參考這種設計模式:這樣就不須要讓實際的單元格實現這個協議了,只須要將其根更普遍的TextPresentable 聯繫在一塊兒就能夠了,這樣,任何擁有標籤的視圖,而不只僅是單元格,均可以實現這個協議來彎沉相關的功能。這樣就能夠說這個標籤有什麼樣的溫恩,什麼樣的顏色,以及什麼樣的字體
protocol TextPresentable {
    var text: String { get }
    var textColor: UIColor { get }
    var font: UIFont { get }
}

protocol SwitchPresentable {
    var switchOn: Bool { get }
    var switchColor: UIColor { get }

    func onSwitchToggleOn(on: Bool)
}
複製代碼

這種狀況下,好比須要一個圖片框,只要一個iamgeProtocol就能夠了,設計師要求改全部標籤的顏色的話一行代碼就能夠搞定

  • 如今單元格的模樣
class SwitchWithTextTableViewCell<T where T: TextPresentable, T: SwitchPresentable>: UITableViewCell {
    private var delegate: T?

    // T是視圖模型
    func configure(withDelegate delegate: T) {
        // 在這裏配置視圖
    }
}
複製代碼

在這種狀況下,它沒有實現這些協議,可是會期待某種實現這些協議的東西傳遞進去,所以咱們使用了泛型,這個單元格期待了一個實現了TextPresentableProtocol 的委託。就咱們而言,傳遞進去的將是一個實現了這些協議的東西就能夠了,如今要基於這些信息在單元格當中配置全部的東西了,如今就能夠基於浙西而信息在單元格中配置全部的東西了

extension MinionModeViewModel: TextPresentable {
    var text: String { return "Minion Mode" }
    var textColor: UIColor { return .blackColor() }
    var font: UIFont { return .systemFontOfsize(17.0) }
}
複製代碼
  • 咱們的視圖模型將會有一個TextPresentable代碼,在其中能夠配置文本、顏色、字體,而且因爲全部的這些協議擴展中都已經有默認值了,甚至不須要視圖模型去實現這些具體的內容

  • 最後,視圖模型當中的代碼就只須要dequeue相應的單元格。而後經過視圖模型對其進行配置,而後返回單元格便可

21. 優化tableViewCell高度

  • 一種是針對全部 Cell 具備固定高度的狀況,經過:self.tableView.rowHeight = 88; 指定了一個全部 cell 都是 88 高度的 UITableView,對於定高需求的表格,強烈建議使用這種(而非下面的)方式保證沒必要要的高度計算和調用。

  • 另外一種方式就是實現 UITableViewDelegate 中的:heightForRowAtIndexPath:須要注意的是,實現了這個方法後,rowHeight 的設置將無效。因此,這個方法適用於具備多種 cell 高度的 UITableView。

  • iOS7以後出了了estimatedRowHeight,面對不一樣高度的cell,只要給一個預估的值就能夠了,先給一個預估值,而後邊滑動邊計算,可是缺點就是

    • 設置估算高度之後,tableView的contentSize.height是根據cell高度預估值和cell的個數來計算的,致使導航條處於很不穩定的狀態,由於contentSize.height會逐漸由預估高度變爲實際高度,不少狀況下肉眼是能夠看到導航條跳躍的
    • 若是是設計很差的上拉加載或下拉刷新,有可能使表格滑動跳躍
    • 估算高度設計初衷是好的,讓加載速度更快,可是損失了流暢性,與其損失流暢性,我寧願讓用戶加載界面的時候多等那零點幾秒
  • iOS8 WWDC 中推出了 self-sizing cell 的概念,旨在讓 cell 本身負責本身的高度計算,使用 frame layout 和 auto layout 均可以享受到:

    • self.tableView.estimatedRowHeight = 213; self.tableView.rowHeight = UITableViewAutomaticDimension; 若是不加上估算高度的設置,自動算高就失效了
    • 這個自動算高在 push 到下一個頁面或者轉屏時會出現高度特別詭異的狀況,不過如今的版本修復了。
  • 相同的代碼在 iOS7 和 iOS8 上滑動順暢程度徹底不一樣,iOS8 莫名奇妙的卡。很大一部分緣由是 iOS8 上的算高機制大不相同,從 WWDC 也卻是能找到點解釋,cell 被認爲隨時均可能改變高度(如從設置中調整動態字體大小),因此每次滑動出來後都要從新計算高度。

    • dequeueReusableCellWithIdentifier:forIndexPath: 相比不帶 「forIndexPath」 的版本會多調用一次高度計算
    • iOS7 計算高度後有」緩存「機制,不會重複計算;而 iOS8 不論什麼時候都會從新計算 cell 高度
  • 使用 UITableView+FDTemplateLayoutCell(百度知道負責人孫源) 無疑是解決算高問題的最佳實踐之一,既有 iOS8 self-sizing 功能簡單的 API,又能夠達到 iOS7 流暢的滑動效果,還保持了最低支持 iOS6

  • FDTemplateLayoutCell 的高度預緩存是一個優化功能,利用RunLoop空閒時間執行預緩存任務計算,當用戶正在滑動列表時顯然不該該執行計算任務影響滑動體驗。

    • 當用戶正在滑動 UIScrollView 時,RunLoop 將切換到 UITrackingRunLoopMode 接受滑動手勢和處理滑動事件(包括減速和彈簧效果),此時,其餘 Mode (除 NSRunLoopCommonModes 這個組合 Mode)下的事件將所有暫停執行,來保證滑動事件的優先處理,這也是 iOS 滑動順暢的重要緣由
    • 註冊 RunLoopObserver 能夠觀測當前 RunLoop 的運行狀態,並在狀態機切換時收到通知:
      • RunLoop開始
      • RunLoop即將處理Timer
      • RunLoop即將處理Source
      • RunLoop即將進入休眠狀態
      • RunLoop即將從休眠狀態被事件喚醒
      • RunLoop退出
    • 分解成多個RunLoop Source任務,假設列表有 20 個 cell,加載後展現了前 5 個,那麼開啓估算後 table view 只計算了這 5 個的高度,此時剩下 15 個就是「預緩存」的任務,而咱們並不但願這 15 個計算任務在同一個 RunLoop 迭代中同步執行,這樣會卡頓 UI,因此應該把它們分別分解到 15 個 RunLoop 迭代中執行,這時就須要手動向 RunLoop 中添加 Source 任務(由應用發起和處理的是 Source 0 任務)

23. 爲何AFN顯示圖片不如SDWebImage流暢?一樣是從網絡上下載圖片而不是從緩存取圖片?

  • 由於SDWebImage有一個decoder
  • UIImage的imageWithData函數是每次畫圖的時候纔將Data解壓成ARGB的圖像
  • 因此每次畫圖的時候,會有一個解壓操做,這樣效率很低,可是隻有瞬時的內存需求
  • 爲了提升效率經過SDWebImageDecoder將包裝在Data的資源解壓,而後畫在另一張圖片上,這樣新的圖片就再也不須要重複解壓了
  • 這是典型的空間換時間的作法

25. 我是怎樣用兩個imageView實現了無線輪播!

  1. 創建一個scrollView,設置contentsize爲3*kWidth,contentOffSet爲kWidth
  2. 接下來使用代理方法scrollViewDidScroll來監聽scrollview的滾動,定義一個枚舉變量來記錄滾動的方向
  3. 使用KVO來監聽direction屬性值的改變-->[self addObserver:self forKeyPath:@"direction" options:NSKeyValueObservingOptionNew context:nil];
  4. 經過observeValueForKeyPath判斷滾動的方向,當偏移量大於x,表示左移,則將otherImageView加在右邊,偏移量小於x,表示右移,則將otherImageView加在左邊。同時判斷設置對應的索引,圖片
  5. 經過代理方法scrollViewDidEndDecelerating來監聽滾動結束,結束後,scrollview的偏移量爲0或者2x,咱們經過代碼再次將scrollview的偏移量設置爲x,並將currImageView的圖片修改成otherImageView的圖片,那麼咱們看到的仍是currImageView,只不過展現的是下一張圖片,如圖,又變成了最初的效果
  6. ,而後設置自動輪播,添加計時器,利用setContentOffset方法裏面setContentOffset:animated:方法執行完畢後不會調用scrollview的scrollViewDidEndDecelerating方法,可是會調用scrollViewDidEndScrollingAnimation方法,所以咱們要在該方法中調用pauseScroll(即監聽減速結束後由otherImageView切換到currImageView的方法)
  7. 添加計時器:self.timer = [NSTimer timerWithTimeInterval:self.time target:self selector:@selector(nextPage) userInfo:nil repeats:YES];
  8. 在scrollViewWillBeginDragging中中止計時器
  9. 在scrollViewDidEndDragging中開啓計時器
  10. 判斷外界傳入的是圖片仍是路徑,若是是圖片,直接加入圖片數組中,若是是路徑,先添加一個佔位圖片,而後根據路徑去下載圖片
  11. 監聽圖片被點擊
    • 定義一個block屬性暴露給外界void(^imageClickBlock)(NSInteger index) (不會block的能夠用代理,或者看這裏)
    • 設置currImageView的userInteractionEnabled爲YES
    • 給currImageView添加一個點擊的手勢
    • 在手勢方法裏調用block,並傳入圖片索引
  12. NSTimer的兩種形式
    • scheduledTimerWithTimeInterval 是建立一個定時器,並加入到當前運行循環[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];)只是建立定時器,並未添加到當前運行循環中,因此若是是其餘兩種方式建立的定時器則須要手動添加到currentRunLoop中
  • NSTimer是普通的定時器,若是系統繁忙,刷新可能會被延遲。可是CADisplaylink實時刷新,跟着屏幕的刷新頻率實時刷新,60次/s,與屏幕刷新頻率相同

26. tableView的優化

iOS平臺由於UIKit自己的特性,須要將全部的UI操做都放在主線程執行,因此有時候就習慣將一些線程安全性不肯定的邏輯,以及它線程結束後的彙總工做等等放到了主線程,因此主線程包含大量計算、IO、繪製都有可能形成卡頓。

  • 能夠經過監控runLoop監控監控卡頓,調用方法主要就是在kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting之間,還有kCFRunLoopAfterWaiting以後,也就是若是咱們發現這兩個時間內耗時太長,那麼就能夠斷定出此時主線程卡頓.
  • 使用到CFRunLoopObserverRef,經過它能夠實時得到這些狀態值的變化
  • 監控後另外再開啓一個線程,實時計算這兩個狀態區域之間的耗時是否到達某個閥值,便能揪出這些性能殺手.
  • 監控到了卡頓現場,固然下一步即是記錄此時的函數調用信息,此處可使用一個第三方Crash收集組件PLCrashReporter,它不只能夠收集Crash信息也可用於實時獲取各線程的調用堆棧
  • 當檢測到卡頓時,抓取堆棧信息,而後在客戶端作一些過濾處理,即可以上報到服務器,經過收集必定量的卡頓數據後通過分析便能準肯定位須要優化的邏輯
  1. 設置正確的 reuseidentifer 以重用 cell

  2. 儘可能將 View 設置爲不透明,包括 cell 自己(backgroundcolor默認是透明的),圖層混合靠GPU去渲染,若是透明度設置爲100%,那麼GPU就會忽略下面全部的layer,節約了不少沒必要要的運算。模擬器上點擊「Debug」菜單,而後選擇「color Blended Layers」,會把全部區域分紅綠色和紅色,綠色的好,紅色的性能差(通過混合渲染的),固然也有一些圖片雖然是不透明的,可是也會顯示紅色,若是檢查代碼沒錯的話,通常就是圖片自身的性質問題了,直接聯繫美工或後臺解決就行了。除非必需要用GPU加載的,其餘最好要用CPU加載,由於CPU通常不會百分百加載,能夠經過CoreGraphics畫出圓角

  3. 有時候美工失誤,圖片大小給錯了,引發沒必要要的圖片縮放(能夠找美工去改,固然也能夠異步去裁剪圖片而後緩存下來),仍是使用Instrument的Color Misaligned Images,黃色表示圖片須要縮放,紫色表示沒有像素對齊。固然通常狀況下圖片格式不會給錯,有些圖片格式是GPU不支持的,就還要勞煩CPU去進行格式轉換。還有能夠經過Color Offscreen-Rendered Yellow來檢測離屏渲染(就是把渲染結果臨時保存,等到用的時候再取出,這樣相對於普通渲染更消耗內存,使用maskToBounds、設置shadow,重寫drawRect方法都會致使離屏渲染) 避免漸變,cornerRadius在默認狀況下,這個屬性只會影響視圖的背景顏色和 border,可是不會離屏繪製,不影響性能。不用clipsToBounds(過多調用GPU去離屏渲染),而是讓後臺加載圖片並處理圓角,並將處理過的圖片賦值給UIImageView。UIImageView 的圓角經過直接截取圖片實現,圓角路徑直接用貝塞爾曲線UIBezierPath繪製(人爲指定路徑以後就不會觸發離屏渲染),UIGraphicsBeginImageContextWithOptions。UIView的圓角可使用CoreGraphics畫出圓角矩形,核心是CGContextAddArcToPoint 函數。它中間的四個參數表示曲線的起點和終點座標,最後一個參數表示半徑。調用了四次函數後,就能夠畫出圓角矩形。最後再從當前的繪圖上下文中獲取圖片並返回,最後把這個圖片插入到視圖層級的底部。 「Flash updated Regions」用於標記發生重繪的區域

  4. 若是 row 的高度不相同,那麼將其緩存下來

  5. 若是 cell 顯示的內容來自網絡,那麼確保這些內容是經過異步下載

  6. 使用 shadowPath 來設置陰影,圖層最好不要使用陰影,陰影會致使離屏渲染(在進入屏幕渲染以前,還看不到的時候會再渲染一次,儘可能不要產生離屏渲染)

  7. 減小 subview 的數量,不要去添加或移除view,要就顯示,不要就隱藏

  8. 在 cellForRowAtIndexPath 中儘可能作更少的操做,最好是在別的地方算好,這個方法裏只作數據的顯示,若是須要作一些處理,那麼最好作一次以後將結果儲存起來.

  9. 使用適當的數據結構來保存須要的信息,不一樣的結構會帶來不一樣的操做代價

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

  11. cell作數據綁定的時候,最好在willDisPlayCell裏面進行,其餘操做在cellForRowAtIndexPath,由於前者是第一頁有多少條就執行多少次,後者是第一次加載有多少個cell就執行多少次,並且調用後者的時候cell還沒顯示

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

  13. tableView滾動的時候,不要去作動畫(微信的聊天界面作的就很好,在滾動的時候,動態圖就不讓他動,滾動中止的時候才動,否則可能會有點影響流暢度)。在滾動的時候加載圖片,中止拖拽後在減速過程當中不加載圖片,減速中止後加載可見範圍內圖片

27. 談談內存的優化和注意事項(使用Instrument工具的CoreAnimation、GPU Driver、I/O操做,檢查fps數值)

  • 重用問題:好比UITableViewCell、UICollectionViewCell、UITableViewHeaderFooterViews等設置正確的reuseIdentifier,充分重用

  • 懶加載控件、頁面:對於不是馬上使用的數據,都應該使用延遲加載的方式,好比網絡鏈接失敗的提示界面,可能一直都用不到

  • 使用Autorelease Pool:在某些循環建立臨時變量處理數據時,自動釋放池以保證能及時釋放內存

  • 不要使用太多的xib/storyboard:載入時會將其內部的圖片在內的全部資源載入內存,即便將來好久纔會須要使用,相對於純代碼寫的延遲加載,在性能和內存上就差了不少

  • 數據緩存:對於cell的行高要緩存起來,使用reloadData效率也極高,對於網絡數據,不須要每次都請求的,應該緩存起來,能夠寫入數據庫,也能夠經過plist文件存儲

  • 選擇正確的數據結構:針對不一樣的業務場景選擇最合適的數據結構是寫出高效代碼的基礎

    • 數組:有序的一組值,使用索引查詢起來很快,使用值查詢的很慢,插入/刪除 很慢
    • 字典:存儲鍵值對對,用鍵查找比較快
    • 集合:無序的一組值,用值來查找很快,插入/刪除很快
  • gzip/zip壓縮:當從服務器下載相關附件時,能夠經過 zip壓縮後再下載,使得內存更小,下載速度也更快

  • 重大開銷對象:一些objects的初始化很慢,好比NSDateFormatter和 NSCalendar,可是又無可避免的須要使用,一般做爲屬性存儲起來,避免反覆使用

  • 避免反覆處理數據:須要應用須要從服務器加載數據,常爲JSON或者XML格式的數據,在服務器端或者客戶端使用相同的數據結構很重要

  • 選擇圖片時,要對圖片進行壓縮處理,根據不一樣的狀況選擇不一樣的圖片加載方式,-imageNamed:讀取到內存後會緩存下來,適合圖片資源較小,使用很頻繁的圖片;-initWithContentsOfFiles:僅加載圖片而不緩存,適合較大的圖片。如果collectionView中使用大量圖片的時候,能夠用UIVIew.layer.contents=(__bridge id _Nullable)(model.clipedImage.CGImage);這樣就更輕量級一些

  • 固然有時候也會用到一些第三方,好比在使用UICollectionView和UITableView的時候,Facebook有一個框架叫AsyncDisplayKit,這個庫就能夠很好地提高滾動時流暢性以及圖片異步下載功能(不支持sb和autoLayout,須要手動進行約束設置),AsyncDisplayKit用相關node類,替換了UIView和它的子類,並且是線程安全的。它能夠異步解碼圖片,調整圖片大小以及對圖片和文本進行渲染,把這些操做都放到子線程,滑動的時候就流暢許多。我認爲這個庫最方便的就是實現圖片異步解碼。UIImage顯示以前必需要先解碼完成,並且解碼仍是同步的。尤爲是在UICollectionView/UITableView 中使用 prototype cell顯示大圖,UIImage的同步解碼在滾動的時候會有明顯的卡頓。另一個很吸引人的點是AsyncDisplayKit能夠把view層次結構轉成layer。由於複雜的view層次結構開銷很大,若是不須要view特有的功能(例如點擊事件),就可使用AsyncDisplayKit 的layer backing特性從而得到一些額外的提高。固然這個庫還處於開發階段,還有一些地方地方有待完善,好比不支持緩存,我要使用這個庫的時候通常是結合Alamofire和AlamofireImage實現圖片的緩存

但願此文對您的求職或夯實基礎起到做用,感謝閱讀!

相關文章
相關標籤/搜索