備戰金三銀四,2021最全100道高頻iOS面試題分享上(含答案)

原做者:執筆續春秋前端

iOS面試題

本面試題爲我的使用版本,如後續流傳出去,請轉發的朋友務必註釋一下,答案正確性有待商榷,本人的答案不表明權威,僅僅是我的理解。 文章內部有寫混亂,將就着看吧。另外大部分圖片加載不出來,,MARKDown格式也不太統一(各平臺不同),因爲博主太懶不想改,不過不影響最終效果,ios

1、硬技術篇

1.對象方法和類方法的區別?

  • 對象方法能個訪問成員變量。
  • 類方法中不能直接調用對象方法,想要調用對象方法,必須建立或者傳入對象。
  • 類方法能夠和對象方法重名。

引申1. 若是在類方法中調用self 會有什麼問題?

  • 在 實例方法中self不能夠調用類方法,此時的self不是Class。
  • 在類方法中self能夠調用其餘類方法。
  • 在類方法中self不能夠調用實例方法。
  • 總結:類方法中的self,是class/ 實例方法中self是對象的首地址。

引伸2. 講一下對象,類對象,元類,跟元類結構體的組成以及他們是如何相關聯的?

  • 對象的結構體當中存放着isa指針和成員變量,isa指針指向類對象
  • 類對象的isa指針指向元類,元類的isa指針指向NSObject的元類
  • 類對象和元類的結構體有isa,superClass,cache等等

做爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個個人iOS交流羣:834688868,無論你是大牛仍是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 你們一塊兒交流學習成長!程序員

引伸3. 爲何對象方法中沒有保存對象結構體裏面,而是保存在類對象的結構體裏面?

  • 方法是每一個對象相互能夠共用的,若是每一個對象都存儲一份方法列表太浪費內存,因爲對象的isa是指向類對象的,當調用的時候, 直接去類對象中去查找就能夠了,節約了不少內存空間。

引伸4. 類方法存在哪裏? 爲何要有元類的存在?

  • 全部的類自身也是一個對象,咱們能夠向這個對象發送消息(即調用類方法)。 爲了調用類方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結構體。這就引出了meta-class的概念,元類中保存了建立類對象以及類方法所需的全部信息。

引伸5. 什麼是野指針?

  • 野指針就是指向一個被釋放或者被收回的對象,可是對指向該對象的指針沒有作任何修改,以致於該指針讓指向已經回收後的內存地址。
  • 其中訪問野指針是沒有問題的,使用野指針的時候會出現崩潰Crash!樣例以下
__unsafe_unretained UIView *testObj = [[UIView alloc] init];
   NSLog(@"testObj 指針指向的地址:%p 指針自己的地址:%p", testObj, &testObj);
   [testObj setNeedsLayout];
   // 能夠看到NSlog打印不會閃退,調用[testObj setNeedsLayout];會閃退

複製代碼

引伸6. 如何檢測野指針?

這是網友總結的,有興趣的能夠看下:www.jianshu.com/p/9fd4dc046… 本人,也就是看看樂呵,其原理啥的,見仁見智吧。開發行業太j8難了!web

引伸7. 致使Crash的緣由有哪些?

一、找不到方法的實現unrecognized selector sent to instance 二、KVC形成的crash 三、EXC_BAD_ACCESS 四、KVO引發的崩潰 五、集合類相關崩潰 六、多線程中的崩潰 七、Socket長鏈接,進入後臺沒有關閉 八、Watch Dog超時形成的crash 九、後臺返回NSNull致使的崩潰,多見於Java作後臺服務器開發語言面試

引伸8. 不使用第三方,如何知道已經上線的App崩潰問題, 具體到哪個類的哪個方法的?

大體實現方式以下。數據庫

  • 使用NSSetUncaughtExceptionHandler能夠統計閃退的信息。
  • 將統計到的信息以data的形式 利用網絡請求發給後臺
  • 在後臺收集信息,進行排查
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.

        NSSetUncaughtExceptionHandler(&my_uncaught_exception_handler);
        return YES;
    }

    static void my_uncaught_exception_handler (NSException *exception) {
        //這裏能夠取到 NSException 信息
        NSLog(@"***********************************************");
        NSLog(@"%@",exception);
        NSLog(@"%@",exception.callStackReturnAddresses);
        NSLog(@"%@",exception.callStackSymbols);
        NSLog(@"***********************************************");
    }

複製代碼

實現方式如: blog.csdn.net/u013896628/…數組

iOS中內省的幾個方法?

  • isMemberOfClass //對象是不是某個類型的對象
  • isKindOfClass //對象是不是某個類型或某個類型子類的對象
  • isSubclassOfClass //某個類對象是不是另外一個類型的子類
  • isAncestorOfObject //某個類對象是不是另外一個類型的父類
  • respondsToSelector //是否能響應某個方法
  • conformsToProtocol //是否遵循某個協議

引伸 2. ==、 isEqualToString、isEqual區別?

  • == ,比較的是兩個指針的值 (內存地址是否相同)。
  • isEqualToString, 比較的是兩個字符串是否相等。
  • isEqual 判斷兩個對象在類型和值上是否都同樣。

引伸 3. class方法和object_getClass方法有什麼區別?

  • 實例class方法直接返回object_getClass(self)
  • 類class直接返回self
  • 而object_getClass(類對象),則返回的是元類

3.深拷貝和淺拷貝

  • 所謂深淺指的是是否建立了一個新的對象(開闢了新的內存地址)仍是僅僅作了指針的複製。
  • copy和mutableCopy針對的是可變和不可變,凡涉及copy結果均變成不可變,mutableCopy均變成可變。
  • mutableCopy均是深複製。
  • copy操做不可變的是淺複製,操做可變的是深賦值。

4.NSString類型爲何要用copy修飾 ?

  • 主要是防止NSString被修改,若是沒有修改的說法用Strong也行。
  • 當NSString的賦值來源是NSString時,strong和copy做用相同。
  • 當NSString的賦值來源是NSMutableString,copy會作深拷貝,從新生成一個新的對象,修改賦值來源不會影響NSString的值。

5.iOS中block 捕獲外部局部變量實際上發生了什麼?__block 中又作了什麼?

  • block 捕獲的是當前在block內部執行的外部局部變量的瞬時值, 爲何說瞬時值呢? 看一下C++源碼中得知, 其內部代碼在捕獲的同時瀏覽器

  • 其實block底層生成了一個和外部變量相同名稱的屬性值若是內部修改值,其實修改的是捕獲以前的值,其捕獲的內部的值因代碼只作了一次捕獲,並無作再一次的捕獲,因此block裏面不能夠修改值。緩存

  • 若是當前捕獲的爲對象類型,其block內部能夠認爲從新建立了一個指向當前對象內存地址的指針(堆),操控內部操做的東西均爲同一塊內存地址,因此能夠修改當前內部的對象裏面的屬性,可是不能直接修改當前的指針(沒法直接修改棧中的內容)(即從新生成一個新的內存地址)。其原理和捕獲基本數據類型一致。安全

  • 說白了, block內部能夠修改的是堆中的內容, 但不能直接修改棧中的任何東西。


  • 若是加上__block 在運行時建立了一個外部變量的「副本」屬性,把棧中的內存地址放到了堆中進而在block內部也能修改外部變量的值。

6.iOS Block爲何用copy修飾?

  • block 是一個對象
  • MRC的時候 block 在建立的時候,它的內存比較奇葩,非得分配到棧上,而不是在傳統的堆上,它自己的做用於就屬於建立的時候(見光死,夭折),一旦在建立時候的做用於外面調用它會致使崩潰。
  • 因此,利用copy把本來在棧上的複製到堆裏面,就保住了它。
  • **ARC的時候 因爲ARC中已經看不到棧中的block了。用strong和copy 同樣 隨意, 用copy是遵循其傳統, **

7. 爲何分類中不能建立屬性Property(runtime除外)?

  • 分類的實現原理是將category中的方法,屬性,協議數據放在category_t結構體中,而後將結構體內的方法列表拷貝到類對象的方法列表中。 Category能夠添加屬性,可是並不會自動生成成員變量及set/get方法。由於category_t結構體中並不存在成員變量。經過以前對對象的分析咱們知道成員變量是存放在實例對象中的,而且編譯的那一刻就已經決定好了。而分類是在運行時纔去加載的。那麼咱們就沒法再程序運行時將分類的成員變量中添加到實例對象的結構體中。所以分類中不能夠添加成員變量。

  • 在往深一點的回答就是 類在內存中的位置是編譯時期決定的, 以後再修改代碼也不會改變內存中的位置,class_ro_t 的屬性在運行期間就不能再改變了, 再添加方法是會修改class_rw_t 的methods 而不是class_ro_t 中的 baseMethods

引申:分類能夠添加那些內容?
  • 實例方法,類方法,協議,屬性
引申:Category 的實現原理?
  • Category 在剛剛編譯完成的時候, 和原來的類是分開的,只有在程序運行起來的時候, 經過runtime合併在一塊兒。
引伸 使用runtime Associate方法關聯的對象,須要在主對象dealloc的時候釋放麼?
  • 不須要,被關聯的對象的生命週期內要比對象自己釋放晚不少, 它們會在被 NSObject -dealloc 調用的 object_dispose() 方法中釋放。
引伸 可否向編譯後獲得的類中增長實例變量, 可否向運行時建立的類中添加實力變量?
  • 不能再編譯後獲得的類中增長實例變量。由於編譯後的類已經註冊在runtime中, 類結構體中objc_ivar_list 實例變量的鏈表和objc_ivar_list 實例變量的內存大小已經肯定,因此不能向存在的類中添加實例變量
  • 能在運行時建立的類中添加實力變量。調用class_addIvar 函數
引伸 主類執行了foo方法,分類也執行了foo方法,在執行的地方執行了foo方法,主類的foo會被覆蓋麼? 若是想只想執行主類的foo方法,如何去作?
  • 主類的方法被分類的foo覆蓋了,其實分類並無覆蓋主類的foo方法,只是分類的方法排在方法列表前面,主類的方法列表被擠到了後面, 調用的時候會首先找到第一次出現的方法。
  • 若是想要只是執行主類的方法,可逆序遍歷方法列表,第一次遍歷到的foo方法就是主類的方法
- (void)foo{   
  [類 invokeOriginalMethod:self selector:_cmd];
}

+ (void)invokeOriginalMethod:(id)target selector:(SEL)selector {
    uint count;
    Method *list = class_copyMethodList([target class], &count);
    for ( int i = count - 1 ; i >= 0; i--) {
        Method method = list[i];
        SEL name = method_getName(method);
        IMP imp = method_getImplementation(method);
        if (name == selector) {
            ((void (*)(id, SEL))imp)(target, name);
            break;
        }
    }
    free(list);
}

複製代碼

8. load 和 initilze 的調用狀況,以及子類的調用順序問題?

  • initialize 這個方法是第一次給某給類發送消息的時候調用,而且只會調用一次。 若是某一個類一直沒有被用到,此方法也不會執行。
  • initialize先初始化父類, 在初始化子類,子類的initialize 會覆蓋父類的方法。
  • 分類中實現了initialize會覆蓋原本的initialize方法,若是多個分類都執行了initialize ,那麼只是執行最後編譯的那個。

  • load當程序被加載的時候就會調用, 其加載順序爲, 若是子類實現類load 先執行父類 -> 在執行子類,而分類的在最後執行。
  • 若是子類不實現load,父類的load就不會被執行。
  • load是線程安全的,其內部使用了鎖,因此咱們應該避免在load方法中線程阻塞。
  • load在分類中,重寫了load方法, 不會影響其主類的方法。即不會覆蓋本類的load方法
  • 當有多個類的時候,每一個類的load的執行順序和編譯順序一致。

9. 什麼是線程安全?

  • 多條線程同時訪問一段代碼,不會形成數據混亂的狀況

10. 你接觸到的項目,哪些場景運用到了線程安全?

答: 舉例說明,12306 同一列火車的車票, 同一時間段多人搶票! 如何解決 互斥鎖使用格式

synchronized(鎖對象) { // 須要鎖定的代碼  }
注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的

Tips: 互斥鎖的優缺點
優勢:能有效防止因多線程搶奪資源形成的數據安全問題
缺點:須要消耗大量的CPU資源

互斥鎖的使用前提:多條線程搶奪同一塊資源 
相關專業術語:線程同步,多條線程按順序地執行任務
互斥鎖,就是使用了線程同步技術

Objective-C中的原子和非原子屬性
OC在定義屬性時有nonatomic和atomic兩種選擇
atomic:原子屬性,爲setter/getter方法都加鎖(默認就是atomic)
nonatomic:非原子屬性,不加鎖

atomic加鎖原理:
property (assign, atomic) int age;
 - (void)setAge:(int)age
{ 
    @synchronized(self) {  
       _age = age;
    }
}

- (int)age {
    int age1 = 0;
    @synchronized(self) {
        age1 = _age;
    }
}

原子和非原子屬性的選擇
nonatomic和atomic對比
atomic:線程安全,須要消耗大量的資源
nonatomic:非線程安全,適合內存小的移動設備

iOS開發的建議
全部屬性都聲明爲nonatomic
儘可能避免多線程搶奪同一塊資源
儘可能將加鎖、資源搶奪的業務邏輯交給服務器端處理,減少移動客戶端的壓力

atomic就必定能保證線程安全麼?
不能,還須要更深層的鎖定機制才能夠,由於一個線程在連續屢次讀取某條屬性值的時候,與此同時別的線程也在改寫值,這樣仍是會讀取到不一樣的屬性值!  或者 一個線程在獲取當前屬性的值, 另一個線程把這個屬性釋放調了, 有可能形成崩潰

複製代碼
  • 另外整理了一份 2021年《大廠最新常問iOS面試題+答案》,有須要,直接加iOS技術交流羣:834688868,免費獲取;羣內更有內推機會!
![image](//upload-images.jianshu.io/upload_images/18569867-6d3f854fb97ebc5d.png?imageMogr2/auto-orient/strip|imageView2/2/w/720/format/webp)
複製代碼

11. 你實現過單例模式麼? 你能用幾種實現方案?

1. 運用GCD:
import "Manager.h"
implementation Manager
+ (Manager *)sharedManager {
  static dispatch_once_t onceToken;
  static Manager * sharedManager;
  dispatch_once(&onceToken, ^{
    sharedManager=[[Manager alloc] init];
  });
  return sharedManager;
}
end
註明:dispatch_once這個函數,它能夠保證整個應用程序生命週期中某段代碼只被執行一次!

2. 不使用GCD的方式:
static Manager *manager;
implementation Manager
+ (Manager *)defaultManager {
    if(!manager)
        manager=[[self allocWithZone:NULL] init];
    return  manager;
}
end

3. 正常的完整版本
+(id)shareInstance{
     static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
      if(_instance == nil)
            _instance = [MyClass alloc] init]; 
    });
     return _instance;
}

//重寫allocWithZone,裏面實現跟方法一,方法二一致就行.
+(id)allocWithZone:(struct _NSZone *)zone{
   return [self shareInstance];
} 

//保證copy時相同
-(id)copyWithZone:(NSZone *)zone{  
    return _instance;  
} 
//  方法3建立的目的的是  爲了方式開發者在調用單例的時候並無用shareInstance方法來建立 而是用的alloc  或者copy的形式建立形成單例不一致的狀況

//   

複製代碼

引伸1. 單例是怎麼銷燬的?

//必須把static dispatch_once_t onceToken; 這個拿到函數體外,成爲全局的.
+ (void)attempDealloc {
    onceToken = 0; // 只有置成0,GCD纔會認爲它從未執行過.它默認爲0,這樣才能保證下次再次調用shareInstance的時候,再次建立對象.
    _sharedInstance = nil;
}

dispatch_once_t 的工做原理是,static修飾會默認將其初始化爲0, 當且僅當其爲0的時候dispatch_once(&onceToken, ^{})這個函數才能被調用, 若是執行了這個函數  這個dispatch_once_t 靜態變成- 1了  就永遠不會被調用

複製代碼

引伸2. 不使用dispatch_once 如何 實現單例

1.第一種方式,重寫+allocWithZone:方法;
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static id instance = nil;
    @synchronized (self) { // 互斥鎖
        if (instance == nil) {
            instance = [super allocWithZone:zone];
        }
    }
    return instance;
}

2.第二種方式,不用重寫+allocWithZone:方法,而是直接用@synchronized 來保證線程安全,其它與上面這個方法同樣;
+ (instancetype)sharedSingleton {
    static id instance = nil;
    @synchronized (self) {
        if (!instance) {
            instance = [[self alloc] init];
        }
    }
    return instance;
}

複製代碼

12. 項目開發中,你用單例都作了什麼?

答 :整個程序公用一份資源的時候 例如 :

  • 設置單例類訪問應用的配置信息
  • 用戶的我的信息登陸後用的NSUserDefaults存儲,對登陸類進一步採用單例封裝方便全局訪問
  • 防止一個單例對 應用 多處 對贊成本地數據庫存進行操做

13.APNS的基本原理

  • 基本
  • 第一階段:應用程序的服務器端把要發送的消息、目的iPhone的標識打包,發給APNS。
  • 第二階段:APNS在自身的已註冊Push服務的iPhone列表中,查找有相應標識的iPhone,並把消息發送到iPhone。
  • 第三階段:iPhone把發來的消息傳遞給相應的應用程序,而且按照設定彈出Push通知。
  • 詳細說明

首先是註冊

  • Device(設備)鏈接APNs服務器並攜帶設備序列號(UUID)
  • 鏈接成功,APNs通過打包和處理產生devicetoken並返回給註冊的Device(設備)
  • Device(設備)攜帶獲取的devicetoken發送到咱們本身的應用服務器
  • 完成須要被推送的Device(設備)在APNs服務器和咱們本身的應用服務器的註冊

推送過程

  • 一、首先手機裝有當前的app,而且保持有網絡的狀況下,APNs服務器會驗證devicetoken,成功那個以後會處於一個長鏈接。 (這裏會有面試問? 若是app也註冊成功了, 也下載了,也贊成了打開推送功能, 這個時候在把App刪除了, 還能接受推送了麼? )
  • 二、當咱們推送消息的時候,咱們的服務器按照指定格式進行打包,結合devicetoken 一塊兒發送給APNs服務器,
  • 三、APNs 服務器將新消息推送到iOS 設備上,而後在設備屏幕上顯示出推送的消息。
  • 四、iOS設備收到推送消息後, 會通知給咱們的應用程序並給予提示

// 推送過程以下圖

image

14. RunLoop的基礎知識

  • RunLoop模式有哪些?

答 : iOS中有五種RunLoop模式

NSDefaultRunLoopMode (默認模式,有事件響應的時候,會阻塞舊事件)
NSRunLoopCommonModes (普通模式,不會影響任何事件)
UITrackingRunLoopMode (只能是有事件的時候纔會響應的模式)

還有兩種系統級別的模式
一個是app剛啓動的時候會執行一次
另一個是系統檢測app各類事件的模式

複製代碼
  • RunLoop的基本執行原理

答 : 本來系統就有一個runloop在檢測App內部的行爲或事件,當輸入源(用戶的直接或者間接的操做)有「執行操做」的時候, 系統的runloop會監聽輸入源的狀態, 進而在系統內部作一些對應的相應操做。 處理完成後,會自動回到睡眠狀態, 等待下一次被喚醒,

  • RunLoop和線程的關係

  • RunLoop的做用就是用來管理線程的, 當線程的RunLoop開啓以後,線程就會在執行完成任務後,進入休眠狀態,隨時等待接收新的任務,而不是退出。

  • 爲何只有主線程的runloop是開啓的

  • 程序開啓以後,要一直運行,不會退出。 說白了就是爲了讓程序不死


如何保證一個線程永遠不死(常駐線程)

// 先建立一個線程用於測試
     NSThread *thread = [[NSThread alloc]  initWithTarget:self selector:@selector(play) object:nil];
    [thread start];

    // 保證一個線程永遠不死
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] -forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];

    // 在合適的地方處理線程的事件處理
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:NO];

複製代碼

15. weak屬性?

1\. 說說你理解weak屬性?

複製代碼
1.實現weak後,爲何對象釋放後會自動爲nil?

runtime 對註冊的類, 會進行佈局,
對於 weak 對象會放入一個 hash 表中。 
用 weak 指向的對象內存地址做爲 key,
Value是weak指針的地址數組。
當釋放的時候,其內部會經過當前的key找到全部的weak指針指向的數組
而後遍歷這個數組把其中的數據設置爲nil。

稍微詳細的說:在內部底層源碼也同時和當前對象相關聯得SideTable, 其內部有三個屬性, 一個是一把自旋鎖,一個是引用計數器相關,一個是維護weak生命得屬性得表
**SideTable**這個結構體同樣的東西,能夠花半個小時看一眼。

複製代碼

延伸

  • objc中向一個nil對象發送消息將會發生什麼? 首先 在尋找對象化的isa指針時就是0地址返回了, 因此不會有任何錯誤, 也不會錯誤

  • objc在向一個對象發送消息時,發生了什麼?

- 首先是經過obj 的isa指針找到對應的class
  - 先去操做對象中的緩存方法列表中objc_cache中去尋找 當前方法,若是找到就直接實現對應IMP
  - 若是在緩存中找不到,則在class中找到對用的Method list中對用foo
  - 若是class中沒有找到對應的foo, 就會去superClass中去找
  - 若是找到了對應的foo, 就會實現foo對應的IMP

  緩存方法列表, 就是每次執行這個方法的時候都會作如此繁瑣的操做這樣太過於消耗性能,因此出現了一個objc_cache,這個會把當前調用過的類中的方法作一個緩存, 當前method_name做爲key, method_IMP做爲Value,當再一次接收到消息的時候,直接經過objc_cache去找到對應的foo的IMP便可, 避免每一次都去遍歷objc_method_list

若是一直沒有找到方法, 就會專用消息轉發機制,機制以下

// 動態方法解析和轉發
上面的例子若是foo函數一直沒有被找到,一般狀況下,會出現報錯,可是在報錯以前,OC的運行時給了咱們三次補救的機會

- Method resolution
- Fast forwarding
- Normal forwarding

1. Runtime 會發送 +resolveInstanceMethod: 或者 +resolveClassMethod: 嘗試去 resolve(重啓) 這個消息;
2. 若是 resolve 方法返回 NO,Runtime 就發送 -forwardingTargetForSelector: 容許你把這個消息轉發給另外一個對象;
3. 若是沒有新的目標對象返回, Runtime 就會發送 -methodSignatureForSelector: 和 -forwardInvocation: 消息。你能夠發送 -invokeWithTarget: 消息來手動轉發消息或者發送 -doesNotRecognizeSelector: 拋出異常。

複製代碼

16.UIView和CALayer是什麼關係?

- 二者最明顯的區別是 View能夠接受並處理事件,而 Layer 不能夠;
    - 每一個 UIView 內部都有一個 CALayer 在背後提供內容的繪製和顯示,而且 UIView 的尺寸樣式都由內部的 Layer 所提供。二者都有樹狀層級結構,layer 內部有 SubLayers,View 內部有 SubViews.可是 Layer 比 View 多了個AnchorPoint
    - 在 View顯示的時候,UIView 作爲 Layer 的 CALayerDelegate,View 的顯示內容由內部的 CALayer 的 display 
CALayer 是默認修改屬性支持隱式動畫的,在給 UIView 的 Layer 作動畫的時候,View 做爲 Layer 的代理,Layer 經過 actionForLayer:forKey:向 View請求相應的 action(動畫行爲)
    - layer 內部維護着三分 layer tree,分別是 presentLayer Tree(動畫樹),modeLayer Tree(模型樹), Render Tree (渲染樹),在作 iOS動畫的時候,咱們修改動畫的屬性,在動畫的實際上是 Layer 的 presentLayer的屬性值,而最終展現在界面上的實際上是提供 View的modelLayer

複製代碼

16. @synthesize 和 @dynamic 分別有什麼做用

- @property有兩個對應的詞,一個是 @synthesize,一個是 @dynamic。若是 @synthesize和 @dynamic都沒寫,那麼默認的就是@syntheszie var = _var;
- @synthesize 的語義是若是你沒有手動實現 setter 方法和 getter 方法,那麼編譯器會自動爲你加上這兩個方法。

- @dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶本身實現,不自動生成。(固然對於 readonly 的屬性只需提供 getter 便可)。假如一個屬性被聲明爲 @dynamic var,而後你沒有提供 @setter方法和 @getter 方法,編譯的時候沒問題,可是當程序運行到 instance.var = someVar,因爲缺 setter 方法會致使程序崩潰;或者當運行到 someVar = var 時,因爲缺 getter 方法一樣會致使崩潰。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。

複製代碼

17. static有什麼做用?

static關鍵字能夠修飾函數和變量,做用以下:

**隱藏**

經過static修飾的函數或者變量,在該文件中,全部位於這條語句以後的函數均可以訪問,而其餘文件中的方法和函數則不行

**靜態變量**

類方法不能夠訪問實例變量(函數),經過static修飾的實例變量(函數),能夠被類   方法訪問;

**持久**

static修飾的變量,能且只能被初始化一次;

**默認初始化**

static修飾的變量,默認初始化爲0;

複製代碼

18. objc在向一個對象發送消息時,發生了什麼?

- objc_msgSend(recicver, selecter..)

複製代碼

19. runloop是來作什麼的?runloop和線程有什麼關係?主線程默認開啓了runloop麼?子線程呢?

1\. runloop與線程是一一對應的,一個runloop對應一個核心的線程,爲何說是核心的,是由於runloop是能夠嵌套的,可是核心的只能有一個,他們的關係保存在一個全局的字典裏。
2\. runloop是來管理線程的,當線程的runloop被開啓後,線程會在執行完任務後進入休眠狀態,有了任務就會被喚醒去執行任務。runloop在第一次獲取時被建立,在線程結束時被銷燬。
3\. 對於主線程來講,runloop在程序一啓動就默認建立好了。
4\. 對於子線程來講, runloop是懶加載的,只有當咱們使用的時候纔會建立,因此在子線程用定時器要注意:確保子線程的runloop被開啓,否則定時器不會回調。  

複製代碼

20. 如何手動觸發一個value的KVO

鍵值觀察通知依賴於 NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一個被觀察屬性發生改變以前, willChangeValueForKey: 必定會被調用,這就 會記錄舊的值。而當改變發生後, didChangeValueForKey: 會被調用,繼而 observeValueForKey:ofObject:change:context: 也會被調用。若是能夠手動實現這些調用,就能夠實現「手動觸發」了。

引伸 0 如何給系統KVO設置篩選條件?

  • 舉例:取消Person類age屬性的默認KVO,設置age大於18時,手動觸發KVO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"age"]) {
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}

- (void)setAge:(NSInteger)age {
    if (age >= 18) {
        [self willChangeValueForKey:@"age"];
        _age = age;
        [self didChangeValueForKey:@"age"];
    }else {
        _age = age;
    }
}

複製代碼

引伸 1.經過KVC修改屬性會觸發KVO麼?直接修改爲員變量呢 ?

  • 會觸發KVO。即便沒有聲明屬性,只有成員變量,只要accessInstanceVariablesDirectly返回的是YES,容許訪問其成員變量,那麼無論有沒有調用setter方法,經過KVC修改爲員變量的值,都能觸發KVO。這也說明經過KVC內部實現了willChangeValueForKey:方法和didChangeValueForKey:方法
  • 直接修改爲員變量不會觸發KVO。直接修改爲員變量內部並無作處理只是單純的賦值,因此不會觸發。

引伸 kvc的底層實現?

  • 賦值方法setValue:forKey:的原理

(1)首先會按照順序依次查找setKey:方法和_setKey:方法,只要找到這兩個方法當中的任何一個就直接傳遞參數,調用方法;

(2)若是沒有找到setKey:和_setKey:方法,那麼這個時候會查看accessInstanceVariablesDirectly方法的返回值,若是返回的是NO(也就是不容許直接訪問成員變量),那麼會調用setValue:forUndefineKey:方法,並拋出異常「NSUnknownKeyException」;

(3)若是accessInstanceVariablesDirectly方法返回的是YES,也就是說能夠訪問其成員變量,那麼就會按照順序依次查找 _key、_isKey、key、isKey這四個成員變量,若是查找到了,就直接賦值;若是依然沒有查到,那麼會調用setValue:forUndefineKey:方法,並拋出異常「NSUnknownKeyException」。

  • 取值方法valueForKey:的原理

(1)首先會按照順序依次查找getKey:、key、isKey、_key:這四個方法,只要找到這四個方法當中的任何一個就直接調用該方法;

(2)若是沒有找到,那麼這個時候會查看accessInstanceVariablesDirectly方法的返回值,若是返回的是NO(也就是不容許直接訪問成員變量),那麼會調用valueforUndefineKey:方法,並拋出異常「NSUnknownKeyException」;

(3)若是accessInstanceVariablesDirectly方法返回的是YES,也就是說能夠訪問其成員變量,那麼就會按照順序依次查找 _key、_isKey、key、isKey這四個成員變量,若是找到了,就直接取值;若是依然沒有找到成員變量,那麼會調用valueforUndefineKey方法,並拋出異常「NSUnknownKeyException」。

21. ViewController生命週期

按照執行順序排列:
1. initWithCoder:經過nib文件初始化時觸發。
2. awakeFromNib:nib文件被加載的時候,會發生一個awakeFromNib的消息到nib文件中的每一個對象。      
3. loadView:開始加載視圖控制器自帶的view。
4. viewDidLoad:視圖控制器的view被加載完成。  
5. viewWillAppear:視圖控制器的view將要顯示在window上。
6. updateViewConstraints:視圖控制器的view開始更新AutoLayout約束。
7. viewWillLayoutSubviews:視圖控制器的view將要更新內容視圖的位置。
8. viewDidLayoutSubviews:視圖控制器的view已經更新視圖的位置。
9. viewDidAppear:視圖控制器的view已經展現到window上。 
10. viewWillDisappear:視圖控制器的view將要從window上消失。
11. viewDidDisappear:視圖控制器的view已經從window上消失。

複製代碼

22.網絡協議

  • TCP三次握手和四次揮手?

三次握手

**1.**客戶端向服務端發起請求連接,首先發送SYN報文,SYN=1,seq=x,而且客戶端進入SYN_SENT狀態 **2.**服務端收到請求連接,服務端向客戶端進行回覆,併發送響應報文,SYN=1,seq=y,ACK=1,ack=x+1,而且服務端進入到SYN_RCVD狀態 **3.**客戶端收到確認報文後,向服務端發送確認報文,ACK=1,ack=y+1,此時客戶端進入到ESTABLISHED,服務端收到用戶端發送過來的確認報文後,也進入到ESTABLISHED狀態,此時連接建立成功

- 哎!
- 嗯
- 給你 

複製代碼

爲何須要三次握手: 爲了防止已失效的鏈接請求報文段忽然又傳送到了服務端,於是產生錯誤。假設這是一個早已失效的報文段,但server收到此失效的鏈接請求報文段後,就誤認爲是client再次發出的一個新的鏈接請求。因而就向client發出確認報文段,贊成創建鏈接。假設不採用「三次握手」,那麼只要server發出確認,新的鏈接就創建了。因爲如今client並無發出創建鏈接的請求,所以不會理睬server的確認,也不會向server發送數據。但server卻覺得新的運輸鏈接已經創建,並一直等待client發來數據。這樣,server的不少資源就白白浪費掉了。

四次揮手

**1.**客戶端向服務端發起關閉連接,並中止發送數據 **2.**服務端收到關閉連接的請求時,向客戶端發送迴應,我知道了,而後中止接收數據 **3.**當服務端發送數據結束以後,向客戶端發起關閉連接,並中止發送數據 **4.**客戶端收到關閉連接的請求時,向服務端發送迴應,我知道了,而後中止接收數據

- 哎!
- 嗯
- 關了
- 好的

複製代碼

爲何須要四次揮手: 由於TCP是全雙工通訊的,在接收到客戶端的關閉請求時,還可能在向客戶端發送着數據,所以不能再回應關閉連接的請求時,同時發送關閉連接的請求

引伸

  1. HTTP和HTTPS有什麼區別?

    • HTTP協議是一種使用明文數據傳輸的網絡協議。
    • HTTPS協議能夠理解爲HTTP協議的升級,就是在HTTP的基礎上增長了數據加密。在數據進行傳輸以前,對數據進行加密,而後再發送到服務器。這樣,就算數據被第三者所截獲,可是因爲數據是加密的,因此你的我的信息讓然是安全的。這就是HTTP和HTTPS的最大區別。
  2. HTTPS的加密方式?

    • Https採用對稱加密和非對稱加密結合的方式來進行通訊。

    • Https不是應用層的新協議,而是Http通訊接口用SSL和TLS來增強加密和認證機制。

      • 對稱加密: 加密和解密都是同一個鑰匙
      • 非對稱加密:密鑰承兌出現,分爲公鑰和私鑰,公鑰加密須要私鑰解密,私鑰加密須要公鑰解密

HTTP和HTTPS的創建鏈接的過程?

HTTP

  • 創建連接完畢之後客戶端會發送響應給服務器
  • 服務端接受請求而且作出響應發送給客戶端
  • 客戶端收到響應而且解析響應給客戶

HTTPS

  • 在使用HTTPS是須要保證服務端配置了正確的對應的安全證書
  • 客戶端發送請求到服務器
  • 服務端返回公鑰和證書到客戶端
  • 客戶端接受後,會驗證證書的安全性,若是經過則會隨機生成一個隨機數,用公鑰對其解密, 發送到服務端
  • 服務端接受到這個加密後的隨機數後,會用私鑰對其進行揭祕,獲得真正的隨機數,而後調用這個隨機數看成私鑰對須要發送的數據進行對稱加密。
  • 客戶端接收到加密後的數據使用私鑰(以前生成的隨機值)對數據進行解密,而且解析數據呈現給客戶

HTTP協議中GET和POST的區別

  • GET在特定的瀏覽器和服務器對URL的長度是有限制的。 可是理論上是沒有限制的

  • POST不是經過URL進行傳值,理論上不受限制。

  • GET會把請求參數拼接到URL後面, 不安全,

  • POST把參數放到請求體裏面, 會比GET相對安全一點, 可是因爲能夠窺探數據, 因此也不安全, 想更安全用加密。

  • GET比POST的請求速度快。緣由:Post請求的過程, 會現將請求頭髮送給服務器確認,而後才真正的發送數據, 而Get請求 過程會在連接創建後會將請求頭和數據一塊兒發送給服務器。 中間少了一步。 因此get比post 快

  • post的請求過程

  • 三次握手以後 第三次會把post請求頭髮送

  • 服務器返回100 continue響應

  • 瀏覽器開始發送數據

  • 服務器返回200 ok響應


  • get請求過程
  • 三次握手以後 第三次會發送get請求頭和數據
  • 服務器返回200 ok響應

23. 有沒有使用過performSelector?

  • 這題主要是想問的是有沒有動態添加過方法
  • 話很少說上代碼
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *p = [[Person alloc] init];

    // 默認person,沒有實現eat方法,能夠經過performSelector調用,可是會報錯。
    // 動態添加方法就不會報錯
    [p performSelector:@selector(eat)];
}

@end

@implementation Person

// **這裏真是奇葩, 實在想不到何時纔有這種使用場景, 我再外面找不到方法, 我再當前類裏面直接在寫一個方法就好咯,幹嗎要在這裏寫這個玩意, 還要寫一個C語言的東西, 既然面試想問, 那咱就要會!**

// void(*)()
// 默認方法都有兩個隱式參數,
void eat(id self,SEL sel)
{
    NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

// 當一個對象調用未實現的方法,會調用這個方法處理,而且會把對應的方法列表傳過來.
// 恰好能夠用來判斷,未實現的方法是否是咱們想要動態添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(eat)) {
        // 動態添加eat方法

        // 第一個參數:給哪一個類添加方法
        // 第二個參數:添加方法的方法編號
        // 第三個參數:添加方法的函數實現(函數地址)
        // 第四個參數:函數的類型,(返回值+參數類型) v:void @:對象->self :表示SEL->_cmd
        class_addMethod(self, @selector(eat), eat, "v@:");
    }
    return [super resolveInstanceMethod:sel];
}
@end

複製代碼
  • 固然面試的時候也可能問你這個
// 延時操做 和GCD的after 一個效果
[p performSelector:@selector(eat) withObject:nil afterDelay:4];

複製代碼
  • 你覺得完了? 錯了,大機率面試官會問你,*** 上面這段代碼放在子線程中 是什麼樣子的?爲何?**

    —首先 上面這個方法其實就是內部建立了一個NSTimer定時器,而後這個定時器會添加在當前的RunLoop中因此上面代碼放到子線程中不會有任何定時器相關方法被執行,若是想要執行,開啓當前線程便可 即

[[NSRunLoop currentRunLoop] run];

複製代碼
// 完整調用
 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        //  [[NSRunLoop currentRunLoop] run]; 放在上面執行時不能夠的,由於當前只是開啓了runloop 裏面沒有任何事件(source,timer,observer)也是開啓失敗的
         [self performSelector:@selector(test) withObject:nil afterDelay:2];
         [[NSRunLoop currentRunLoop] run];
});

// 由此我自行又作了一個測試, 把        
[self performSelector:@selector(test)];
在子線程調用,是沒有任何問題的。

// 我又測試了一下,
 [self performSelector:@selector(test) withObject:nil afterDelay:2];
 這個方法在主線程執行  打印線程是1

在子線程中調用打印線程 非1

複製代碼
  • 而後面試官開始飄了, 開始問你關於NSTimer相關問題?怎麼辦? 答: 搞他!

引伸 NSTimer在子線程執行?

  • NSTimer直接在在子線程是不會被調用的, 想要執行請開啓當前的Runloop 。具體開啓方案上面題有說,不贅述。

引伸 爲何說NSTimer不許確?

  • NSTimer的觸發時間到的時候,runloop若是在阻塞狀態,觸發時間就會推遲到下一個runloop週期 減小偏差的方法 代碼以下
// 在子線程中開啓NStimer,或者更改當前Runloop的Mode 爲NSRunLoopCommonModes
[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];

// 利用CADisplayLink (iOS設備的屏幕刷新頻率是固定的,CADisplayLink在正常狀況下會在每次刷新結束都被調用,精確度至關高)
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(logInfo)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

// 利用GCD
NSTimeInterval interval = 1.0;
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
    NSLog(@"GCD timer test");
});
dispatch_resume(_timer);

複製代碼

24. 爲何AFN3.0中須要設置self.operationQueue.maxConcurrentOperationCount = 1;而AF2.0卻不須要?

  • 功能不同, 2.x是基於NSURLConnection的,其內部實現要在異步併發,因此不能設置1。 3.0 是基於NSURLSession其內部是須要串行的鑑於一些多線程數據訪問的安全性考慮, 設置這個達到串行回調的效果。

AFNetworking 2.0 和3.0 的區別?

  • AFN3.0剔除了全部的NSURLConnection請求的API
  • AFN3.0使用NSOperationQueue代替AFN2.0的常駐線程

2.x版本常駐線程的分析

  • 在請求完成後咱們須要對數據進行一些序列化處理,或者錯誤處理。若是咱們在主線中處理這些事情很明顯是不合理的。不只會致使UI的卡頓,甚至受到默認的RunLoopModel的影響,咱們在滑動tableview的時候,會致使時間的處理中止。

  • 這裏時候咱們就須要一個子線程來處理事件和網絡請求的回調了。可是,子線程在處理完事件後就會自動結束生命週期,這個時候後面的一些網絡請求得回調咱們就沒法接收了。因此咱們就須要開啓子線程的RunLoop來保存線程的常駐。

  • 固然咱們能夠每次發起一個請求就開啓一條子線程,可是這個想一下就知道開銷有多大了。因此這個時候保活一條線程來對請求得回調處理是比較好的一個方案。

3.x版本不在常駐線程的分析?

  • 在3.x的AFN版本中使用的是NSURLSession進行封裝。對比於NSURLConnection,NSURLSession不須要在當前的線程等待網絡回調,而是可讓開發者本身設定須要回調的隊列。

  • 因此在3.x版本中AFN使用了NSOperationQueue對網絡回調的管理,而且設置maxConcurrentOperationCount爲1,保證了最大的併發數爲1,也就是說讓網絡請求串行執行。避免了多線程環境下的資源搶奪問題。

25. autoreleasePool 在什麼時候被釋放?

  • ARC中全部的新生對象都是 自動加autorelese的, @atuorelesepool 大部分時候解決了瞬時內存暴增的問題 。
  • MRC中的狀況 關鍵詞變了NSAutoreleasePool。
//來自Apple文檔,見參考
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) { 
  @autoreleasepool { 
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:urlencoding:NSUTF8StringEncoding error:&error]; 
}

// 若是循環次數很是多,並且循環體裏面的對象都是臨時建立使用的,就能夠用@autoreleasepool 包起來,讓每次循環結束時,能夠及時釋放臨時對象的內存

// for 和 for in 裏面是沒有自動包裝@autoreleasepool着的,而下面的方法是由@autoreleasepool自動包圍的
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // 這裏被一個局部@autoreleasepool包圍着
}];

複製代碼
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString* str = [[[NSString alloc] initWithString:@"666"] autorelease];
[pool drain];

// 其做用於爲drain 和 init 之間

複製代碼
  • 迴歸正題@autoReleasePool什麼時間釋放?
    • 一個被autoreleasepool包裹生成得對象,都會在其建立生成以後自動添加autorelease, 而後被autorelease對象得釋放時機 就是在當前runloop循環結束的時候自動釋放的
    • 參考連接:blog.sunnyxx.com/2014/10/15/…

子線程中的autorelease變量何時釋放?

  • 子線程中會默認包裹一個autoreleasepool的, 釋放時機是當前線程退出的時候。

autoreleasepool是如何實現的?

  • @autoreleasepool{} 本質上是一個結構體:
  • autoreleasepool會被轉換成__AtAutoreleasePool
  • __AtAutoreleasePool 裏面有兩個函數objc_autoreleasePoolPush(),objc_autoreleasePoolPop().,其實一些列下來以後實際上調用得是AutoreleasePoolPage類中得push 和 pop兩個類方法
  • push就是壓棧操做,
  • pop就是出棧操做於此同時對其對象發送release消息進行釋放

26. iOS界面渲染機制? [這是很大的一個模塊,裏面牽扯不少東西, 耐心看下去]

  • 先簡單解釋一下渲染機制

首先iOS渲染視圖的核心是Core Animation,其渲染層次依次爲:圖層樹->呈現樹->渲染樹

  • 一共三個階段

  • CPU階段(進行Frame佈局,準備視圖和圖層之間的層級關係)

  • OpenGL ES階段(iOS8之後改爲Metal), (渲染服務把上面提供的圖層上色,生成各類幀)

  • GPU階段 (把上面操做的東西進行一些列的操做,最終展現到屏幕上面)

  • 稍微詳細說明

  • 首先一個視圖由CPU進行Frame佈局,準備視圖和圖層的層及關係。

  • CUP會將處理視圖和圖層的層級關係打包,經過IPC(進程間的通訊)通道提交給渲染服務(OpenGL和GPU)

  • 渲染服務首先將圖層交給OpenGL進行紋理生成和着色,生成先後幀緩存,再根據硬件的刷新幀率,通常以設備的VSync信號和CADisplayLink(相似一個刷新UI專用的定時器)爲標準,進行先後幀緩存的切換

  • 最後,將最終 要顯示在畫面上的後幀緩存交給GPU,進行採集圖片和形狀,運行變換, 應用紋理混合,最終顯示在屏幕上。

程序卡頓的緣由?

  • 正常渲染流程
  • CPU計算完成以後交給GPU,來個同步信號Vsync 將內容渲染到屏幕上
  • 非正常(卡頓/掉幀)的流程
  • CPU計算時間正常或者慢,GPU渲染時間長了, 這時候Vsync信號, 因爲沒有繪製徹底,CUP開始計算下一幀,當下一幀正常繪製成功以後,把當前沒有繪製完成的幀丟棄, 顯示了下一幀,因而這樣就形成了卡頓。 須要注意的是:Vsync時間間隔是固定的, 好比60幀率大的Vsync 是每16ms就執行一個一次,相似定時器同樣

這裏會出現一個面試題!!! 題目以下:

  • 從第一次打開App到徹底開始展示出UI,中間發生了什麼? 或者App是怎麼渲染某一個View的?
  • 回答就是上面的稍微詳細說明,若是要求更詳細, 能夠繼續深究一下。

在科普一下 1.Core Animation Core Animation 在 RunLoop 中註冊了一個 Observer,監聽了 BeforeWaiting 和 Exit 事件。這個 Observer 的優先級是 2000000,低於常見的其餘 Observer。當一個觸摸事件到來時,RunLoop 被喚醒,App 中的代碼會執行一些操做,好比建立和調整視圖層級、設置 UIView 的 frame、修改 CALayer 的透明度、爲視圖添加一個動畫;這些操做最終都會被 CALayer 捕獲,並經過 CATransaction 提交到一箇中間狀態去(CATransaction 的文檔略有提到這些內容,但並不完整)。當上面全部操做結束後,RunLoop 即將進入休眠(或者退出)時,關注該事件的 Observer 都會獲得通知。這時 CA 註冊的那個 Observer 就會在回調中,把全部的中間狀態合併提交到 GPU 去顯示;若是此處有動畫,CA 會經過 DisplayLink 等機制屢次觸發相關流程。

2.CPU渲染職能

  • 佈局計算:若是視圖層級過於複雜,當試圖呈現或者修改的時候,計算圖層幀率就會消耗一部分時間,
  • 視圖懶加載: iOS只會當視圖控制器的視圖顯示到屏幕上纔會加載它,這對內存使用和程序啓動時間頗有好處,可是當呈現到屏幕以前,按下按鈕致使的許多工做都不會被及時響應。好比,控制器從數據局中獲取數據, 或者視圖從一個xib加載,或者涉及iO圖片顯示都會比CPU正常操做慢得多。
  • 解壓圖片:PNG或者JPEG壓縮以後的圖片文件會比同質量的位圖小得多。可是在圖片繪製到屏幕上以前,必須把它擴展成完整的未解壓的尺寸(一般等同於圖片寬 x 長 x 4個字節)。爲了節省內存,iOS一般直到真正繪製的時候纔去解碼圖片。根據你加載圖片的方式,第一次對 圖層內容賦值的時候(直接或者間接使用 UIImageView )或者把它繪製到 Core Graphics中,都須要對它解壓,這樣的話,對於一個較大的圖片,都會佔用必定的時間。
  • Core Graphics繪製:若是對視圖實現了drawRect:或drawLayer:inContext:方法,或者 CALayerDelegate 的 方法,那麼在繪製任何東 西以前都會產生一個巨大的性能開銷。爲了支持對圖層內容的任意繪製,Core Animation必須建立一個內存中等大小的寄宿圖片。而後一旦繪製結束以後, 必須把圖片數據經過IPC傳到渲染服務器。在此基礎上,Core Graphics繪製就會變得十分緩慢,因此在一個對性能十分挑剔的場景下這樣作十分很差。
  • 圖層打包:當圖層被成功打包,發送到渲染服務器以後,CPU仍然要作以下工做:爲了顯示 屏幕上的圖層,Core Animation必須對渲染樹種的每一個可見圖層經過OpenGL循環 轉換成紋理三角板。因爲GPU並不知曉Core Animation圖層的任何結構,因此必須 要由CPU作這些事情。這裏CPU涉及的工做和圖層個數成正比,因此若是在你的層 級關係中有太多的圖層,就會致使CPU沒一幀的渲染,即便這些事情不是你的應用 程序可控的。

3.GPU渲染職能 GPU會根據生成的先後幀緩存數據,根據實際狀況進行合成,其中形成GPU渲染負擔的通常是:離屏渲染,圖層混合,延遲加載。

這裏又會出現一個面試題!!! 一個UIImageView添加到視圖上之後,內部如何渲染到手機上的?

圖片顯示分爲三個步驟: 加載、解碼、渲染、 一般,咱們程序員的操做只是加載,至於解碼和渲染是由UIKit內部進行的。 例如:UIImageView顯示在屏幕上的時候須要UIImage對象進行數據源的賦值。而UIImage持有的數據是未解碼的壓縮數據,當賦值的時候,圖像數據會被解碼變成RGB顏色數據,最終渲染到屏幕上。


看完上面的又來問題了! 關於UITableView優化的問題?(真他媽子子孫孫無窮盡也~) 先說形成UITableView滾動時候卡頓的的緣由有哪些?

  • 隱式繪製 CGContext
  • 文本CATextLayer 和 UILabel
  • 光柵化 shouldRasterize
  • 離屏渲染
  • 可伸縮圖片
  • shadowPath
  • 混合和過分繪製
  • 減小圖層數量
  • 裁切
  • 對象回收
  • Core Graphics繪製
  • -renderInContext: 方法

在說關於UITableView的優化問題!

基礎的

  • 重用機制(緩存池)
  • 少用有透明度的View
  • 儘可能避免使用xib
  • 儘可能避免過多的層級結構
  • iOS8之後出的預估高度
  • 減小離屏渲染操做(圓角、陰影啥的)

  • **** 解釋一下爲何減小離屏渲染操做?****

  • 須要建立新的緩衝區

  • 整個過程須要屢次切換上下文環境, 顯示從當前的屏幕切換到離屏,等待離屏渲染結束後,將離屏緩衝區的渲染結果 顯示到屏幕有上, 又要將上下文環境從離屏切換到當前屏幕,

  • 那些操做會觸發離屏渲染?

  • 光柵化 layer.shouldRasterize = YES

  • 遮罩layer.mask

  • 圓角layer.maskToBounds = Yes,Layer.cornerRadis 大於0

  • 陰影layer.shadowXXX

進階的

  • 緩存cell的高度(提早計算好cell的高度,緩存進當前的模型裏面)
  • 異步繪製
  • 滑動的時候,按需加載

高階的

  • 你想不到 居然不推薦用UILabel。哈哈哈~ 至於爲何 看下面的連接吧

至於上面的那些基礎的,涉及到渲染級別的本身說的時候悠着點,面試官若是想搞你的話,考一考你最上面的那些,CUP和GUP,以及openGL相關, 在考一下你進程通訊IPC,以及VSync信號啥的, 這些東西太雞兒高深了,沒點匠心 這東西還真搞不了,要想研究能夠看看YYKit的做者寫的一篇關於頁面流暢的文章:blog.ibireme.com/2015/11/12/…

卡頓檢測的方法

  • 卡頓就是主線程阻塞的時間問題,能夠添加Observer到主線程Runloop中,經過監聽Runloop狀態切換的耗時,以達到監聽卡頓的目的

繼續

既然都是圖形繪製了,那就再研究一下事件響應鏈&原理

傳統的問法來了:UIView和CALayer的區別? 一般咱們這樣回答:UIView能夠響應用戶事件,而CALayer不能處理事件


回答這個以前, 先回顧一下另一個經典面試題:事件響應鏈和事件傳遞?

基本概念:

  • 響應鏈: 是由連接在一塊兒的響應者(UIResponse子類)組成的,通常爲第一響應着到application對象以及中間全部響應者一塊兒組成的。

  • 事件傳遞: 獲取響應鏈以後, 將事件由第一響應者網application的傳遞過程

  • image

  • image

  • 事件的分發和傳遞

  • 當程序中發生觸摸事件以後,系統會將事件添加到UIApplication管理的一個隊列當中

  • UIApplication將處於任務隊列最前端的事件向下分發 即UIWindow

  • UIWindow將事件向下分發,即UIView或者UIViewController

  • UIView首先看本身可否處理這個事件,觸摸點是否在本身身上,本身的透明度是否大於0,01,userInteractionEnabled 是不是YES, Hidden實際是NO,若是這些都知足,那麼繼續尋找其子視圖

  • 遍歷子控件,重複上面步驟

  • 若是沒有找到,那麼本身就是改事件的處理者

  • 若是本身不能處理,那麼就不作任何處理 即視爲沒有合適的View能接收處理當前事件,則改事件會被廢棄。

  • *** 怎麼尋找當前觸摸的是哪個View?*** 下面中兩個方法

// 此方法返回的View是本次點擊事件須要的最佳View
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

// 判斷一個點是否落在範圍內
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

複製代碼

事件傳遞給控件以後, 就會調用hitTest:withEvent方法去尋找更合適的View,若是當前View存在子控件,則在子控件繼續調用hitTest:withEvent方法判斷是不是合適的View, 若是還不是就一直遍歷尋找, 找不到的話直接廢棄掉。

// 由於全部的視圖類都是繼承BaseView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
   // 1.判斷當前控件可否接收事件
   if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
   // 2\. 判斷點在不在當前控件
   if ([self pointInside:point withEvent:event] == NO) return nil;
   // 3.從後往前遍歷本身的子控件
   NSInteger count = self.subviews.count;
   for (NSInteger i = count - 1; i >= 0; i--) {
      UIView *childView = self.subviews[I];
       // 把當前控件上的座標系轉換成子控件上的座標系
      CGPoint childP = [self convertPoint:point toView:childView];
      UIView *fitView = [childView hitTest:childP withEvent:event];
       if (fitView) { // 尋找到最合適的view
           return fitView;
       }
   }
   // 循環結束,表示沒有比本身更合適的view
   return self;

}

複製代碼
  • 判斷觸摸點是否在視圖內?
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

複製代碼
  • tableView 加一個tap的手勢, 點擊當前cell的位置 哪一個事件被響應 爲何?
  • tap事件被響應, 由於tap事件添加以後,默認是取消當前tap之外的全部事件的, 也就是說, tap事件處於當前響應者鏈的最頂端, 解決的辦法執行tap的delagete, 實現
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch  
{  
    if([touch.view isKindOfClass:[XXXXcell class]])  
    {  
        return NO;  
    }  
    return YES;  
}

複製代碼
結交人脈

最後推薦個個人iOS交流羣:834688868,無論你是大牛仍是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 你們一塊兒交流學習成長!

'有一個共同的圈子很重要,結識人脈!裏面都是iOS開發,全棧發展,歡迎入駐,共同進步!(羣內會免費提供一些羣主收藏的免費學習書籍資料以及整理好的幾百道面試題和答案文檔!)

做者:iOS開發面試總結 連接:www.jianshu.com/p/65dd685e3… 來源:簡書

相關文章
相關標籤/搜索