《iOS面試題 - 老生常談》之提示答案

數據結構、算法和網絡結構

這兩個類別的面試題請參考算法網絡程序員

面向對象的基礎題

  • 面向對象的幾個設計原則?面試

    • SOLID原則(更新:據朋友提醒應爲七個)
      • Single Responsibility Principle(單一原則)
      • Open Close Principle(開閉原則)
      • Liskov Substitution Principle(里氏替換原則)
      • Interface Segregation Principle(接口分離原則)
      • Dependency Inversion Principle(依賴倒置原則)
      • Law of Demeter(Leaset Knowledge Principle)迪米特法則(最少知道原則)
      • Composite Reuse Principle(合成複用原則)
  • Hash表的實現?算法

    • 經過把關鍵碼值(key)映射到表中的一個位置來訪問記錄,Hash實現的關鍵是散列函數和衝突解決(鏈地址法和開放定址法)。
  • 什麼是進程和線程?有什麼區別?數據庫

    • 進程:是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位。
    • 線程:操做系統可以進行運算調度的最小單位
    • 區別:線程被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務。
  • 內存的幾大區域?各自的職能?編程

    • 棧區:由編譯器自動分配和釋放,通常存放函數的參數值,局部變量等。
    • 堆區:由程序員分配和釋放,若不釋放,則程序結束由操做系統回收。
    • 全局區(static):由編譯器管理(分配和釋放),程序結束由系統釋放。全局變量和靜態變量(相鄰兩塊區,初始化和未初始化)。
    • 文字常量區:由編譯器管理(分配釋放),程序結束由系統釋放;存放常量字符。
    • 程序代碼區:存放函數的二進制代碼。
  • 架構、框架和設計模式的區別?swift

    • 架構:一種頂層歸納性的設計概念,像是藍圖,將不一樣的需求抽象爲具體組件並使組件互相通訊。
    • 框架:軟件框架是提取特定領域軟件的共性部分造成的體系結構,不一樣領域的軟件項目有不一樣的框架類型。
    • 設計模式:一套被反覆使用、多數人知曉、通過分類編目的總結,代碼設計的一個成果,能夠用在不一樣軟件框架中一種實際問題的解決方案。(在軟件開發中總結出的一種適合解決某種問題的方法)
  • MVC、MVVM和MVP架構的不一樣?設計模式

    • MVC
      • 【優勢】簡單易上手、沒有多層調用
      • 【缺點】耦合性強;不利於單元測試,Controller擔負了太多的事件處理,處理用戶操做,管理View的聲明週期,API接口調用,處理錯誤,處理回調,監聽通知,處理視圖方向等,容易形成臃腫,維護不方便
    • MVP
      • 【優勢】利於單元測試、明確責任分配
      • 【缺點】要寫一些額外的代碼(如綁定),若業務邏輯複雜,也會形成Presenter的臃腫
    • MVVM
      • 【優勢】責任劃分明確;邏輯清晰;可測性高;雙向綁定,配合第三方開源庫使用方便
      • 【缺點】(1)數據綁定使得Bug很難被定位;(2)對於過大的項目,數據綁定須要花費更多的內存;依賴於開源庫實現雙向綁定,學習起來比較複雜;

iOS基礎面試題

UI

  • UIView和CALayer的區別?
    • UIView 和 CALayer 都是 UI 操做的對象。二者都是 NSObject 的子類,發生在 UIView 上的操做本質上也發生在對應的 CALayer 上。
    • UIView 是 CALayer 用於交互的抽象。UIView 是 UIResponder 的子類( UIResponder 是 NSObject 的子類),提供了不少 CALayer 所沒有的交互上的接口,主要負責處理用戶觸發的種種操做。
    • CALayer 在圖像和動畫渲染上性能更好。這是由於 UIView 有冗餘的交互接口,並且相比 CALayer 還有層級之分。CALayer 在無需處理交互時進行渲染能夠節省大量時間。
    • CALayer的動畫要經過邏輯樹、動畫樹和顯示樹來實現
  • loadView是幹嗎用的?
    • loadView用來自定義view,只要實現了這個方法,其餘經過xib或storyboard建立的view都不會被加載 。
  • layoutIfNeeded、layoutSubviews和setNeedsLayout的區別?
    • layoutIfNeeded:方法調用後,在主線程對當前視圖及其全部子視圖當即強制更新佈局。
    • layoutSubviews:方法只能重寫,咱們不能主動調用,在屏幕旋轉、滑動或觸摸界面、子視圖修改時被系統自動調用,用來調整自定義視圖的佈局。
    • setNeedsLayout:方法與layoutIfNeeded類似,不一樣的是方法被調用後不會當即強制更新佈局,而是在下一個佈局週期進行更新。
  • iOS的響應鏈?什麼狀況會影響響應鏈?
    • 事件UIResponder:繼承該類的對象才能響應
    • 事件處理:touchesBegain、touchesMoved、touchesEnded、touchesCancelled;
    • 事件響應過程:
      1. UIApplication(及delegate)接收事件傳遞給 keyWindow
      2. keyWindow遍歷subViews的hitTest:withEvent:方法和pointInside方法找到點擊區域內的視圖並處理事件
      3. UIView的子視圖也會遍歷其hitTest:withEvent:方法,以此類推,直到找到點擊區域內最上層的視圖,將視圖逐步返回給UIApplication
      4. 最後找到的視圖成爲第一響應者,若沒法響應,調用其nextResponder方法,一直找到響應鏈中能處理該事件的對象(據朋友提醒:UIControl子類不適用)。
      5. 最後到Application依然沒有能處理該事件的對象的話,就廢棄該事件;
      • 關鍵是hitTest:withEvent:方法和pointInside方法
      • ⚠️ 如下幾種狀況會忽略,hidden爲YES,alpha小於等於0.01,userInteractionEnabled爲NO,
    • UIControl的子類和UIGestureRecognizer優先級較高,會打斷響應鏈;
  • 說幾種給UIImageView添加圓角的方式?
    • cornerRadius(iOS9以後不會致使離屏渲染)
    • CoreGraphic繪製
    • UIBezierPath(本質同上)
  • iOS有哪些實現動畫的方式?
    • UIView Animation:系統提供的基於CALayer Animation的封裝,能夠實現平移、縮放、旋轉等功能。
    • CALayer Animation:底層CALayer配合CAAnimation的子類,能夠實現更復雜的動畫
    • UIViewPropertyAnimator:iOS10後引入的用於處理交互的動畫,能夠實現UIView Animation的全部效果。
  • 使用drawRect有什麼影響?
    • 處理touch事件時會調用setNeedsDisplay進行強制重繪,帶來額外的CPU和內存開銷。

OC基礎

  • iOS的內存管理機制?
    • 引用計數,從MRC(Manuel Reference Count)到ARC(Automatic Reference Count)。(最好能說明對象的生命週期)
  • @property後的相關修飾詞有哪些?
    • 原子性:
      1. nonatomic:非原子性,編譯器不會對其進行加鎖(同步鎖的開銷較大)
      2. atomic:原子性(默認),編譯器合成的方法會經過鎖定機制確保原子性(非線程安全)
    • 讀寫權限:
      1. readwrite:可讀寫(默認),若不用@dynamic修飾編譯器會自動爲其生成setter和getter方法,不然編譯器不生成由用戶本身實現
      2. readonly:只讀,若不用dynamic修飾僅生成getter方法,不然編譯器不生成getter方法 有用戶來實現
    • 內存管理語義:
      1. strong:修飾引用類型(表示持有當前實例),保留新值釋放舊值,若修飾IMMutable不可變類型建議用copy
      2. copy:指修飾不可變集合類型、AttributeString、Block(ARC下strong和copy同樣,把棧中的Block拷貝到堆中,copy表示不須要copy操做了)(設置方法不保留新值而是將其copy,不可變類型的可變類型子類)
      3. weak:修飾引用類型(非持有關係),不保留新值,不釋放舊值(屬性所指對象被摧毀時,屬性值也會清空置nil)
      4. assign:修飾基本數據類型(也能夠修飾引用類型,但可能會產生野指針),執行簡單賦值操做
      5. unsafe_unretained:修飾引用類型(也能夠修飾基本類型),所指對象被摧毀時,屬性值不會被清空(unsafe)
    • 方法名:
      1. getter=指定方法名
      2. setter(不經常使用)
  • dynamic和synthesis的區別?
    • dynamic:告訴編譯器不要幫我自動合成setter和getter方法,本身來實現,若沒有實現,當方法被調用時會致使消息轉發。
    • synthesis:指定實例變量的名字,子類重載父類屬性也須要synthesis(從新set和get、使用dynamic,在Protocol定義屬性、在category定義屬性,默認不會自動合成)。
  • array爲什麼用copy修飾?mutableArray爲什麼用strong修飾?
    • array:若用strong修飾,在其被賦值可變的子類後,內容可能會在不知不覺中修改,用copy防止被修改。
    • mutableArray若用copy修飾會返回一個NSArray類型,若調用可變類型的添加、刪除、修改方法時會由於找不到對應的方法而crash。
  • 深拷貝和淺拷貝(注意NSString類型)?
    • NSString:strong和copy修飾的屬性是同等的,指向同一個內存地址,mutableCopy纔是內存拷貝;
    • NSMutableString:strong和copy不一樣,strong指向同一個內存地址,copy則會進行內存拷貝,mutableCopy也會進行內存拷貝;
    • NSArray:對於字符類型和自定義對象結果是不一樣的,strong和copy都指向相同內存地址,mutableCopy也僅僅是指針拷貝;可是mutableCopy能夠把Array變成MutableArray
    • PS:copy產生一個不可變類型,mutableCopy產生一個可變類型;對於字符類型mutableCopy會拷貝字符,copy對於NSArray是不拷貝的,對於NSMutableArray 是拷貝的;對於對象類型,僅僅是指針拷貝;(建議手動試試)
  • Block的幾種類型?
    • _NSConcreteGlobalBlock:全局Block也是默認的block類型,一種優化操做,私有和公開,保持私有可防止外部循環引用,存儲在數據區,當捕獲外部變量時會被copy到堆上
    • _NSConcreteStackBlock:棧Block,存儲在棧區,待其做用域結束,由系統自動回收
    • _NSConcreteMallocBlock:堆Block,計數器爲0 的時候銷燬
  • isEqual和「==」的區別?
    • ==:比較兩個指針自己,而不是其所指向的對象。
    • isEqual:當且僅當指針值也就是內存地址相等;若重寫該方法則要保證isEqual相等則hash相等,hash相等isEqual不必定相等;若指針值相等則相等,值不相等就判斷對象所屬的類,不屬於同一個類則不相等,同屬一個類時再判斷每一個屬性是否相等。
  • id和NSObject的區別?
    • id:指向對象的指針,編譯時不作類型檢查,運行時向其發送消息纔會對其檢查。
    • NSObject:NSObject類及其子類,編譯時作類型檢查,向其發送的消息沒法處理時就會執行消息轉發。
    • id<NSObject>:編譯器不對其作類型檢查,對象所屬的類默認實現名爲NSObject的Protocol,既能響應方法又不對其作類型檢查.
  • 通知、代理、KVO和Block的不一樣(結合應用場景回答)?
    • 通知: 適用於毫無關聯的頁面之間或者系統消息的傳遞,屬於一對多的信息傳遞關係。例如接收系統音量、系統狀態、鍵盤等,應用模式的設置和改變,都比較適合用通知去傳遞信息。
    • 代理: 一對一的信息傳遞方式,適用於相互關聯的頁面之間的信息傳遞,例如push和present出來的頁面和原頁面之間的信息傳遞。
    • block: 一對一的信息傳遞方式,效率會比代理要高(畢竟是直接取IMP指針的操做方式)。適用的場景和代理差很少,都是相互關聯頁面之間的頁面傳值。
    • KVO:屬性監聽,監聽對象的某一屬性值的變化情況,當須要監聽對象屬性改變的時候使用。例如在UIScrollView中,監聽contentOffset,既能夠用KVO,也能夠用代理。可是其餘一些狀況,好比說UIWebView的加載進度,AVPlayer的播放進度,就只能用KVO來監聽了,不然獲取不到對應的屬性值。
  • 什麼是循環引用?__weak、__strong和__block的區別?
    • 循環引用:兩個對象互相持有對方,一個釋放須要另一個先釋放,delegate的weak聲明就是爲了防止循環引用。(通常指雙向強引用,單向強引用不須要考慮,如:UIView動畫Block)
    • __weak:Block會捕獲在Block中訪問Block做用域外的實例,這樣會有內存泄漏的風險,用__weak修飾表示在Block不會致使該實例引用計數器加1,也能夠在Block執行結束後強制將Block置nil,這樣Block捕獲的實例也會跟着釋放,若是捕獲的僅是基本數據類型,Block只會對其值進行拷貝一份,此時值再怎麼變化也不會影響Block內部的操做。
    • __strong:在Block中使用__weak修飾的實例很容易被釋放,因此須要加鎖判斷是否釋放,未釋放則對其進行強引用持有,保證向該實例發送消息的時候不會致使崩潰
    • __block:Block默認不容許修改外部變量的值,以int類型爲例,若在Block中訪問變量就把該變量進行Copy一份保存到Block函數內,而後變量在Block外部不管怎麼改變都不會影響Block中使用的變量的值,若在Block改變外部變量的值,變量必需要用__block修飾,Block是把該變量的棧內存地址拷貝到堆中,因此能夠直接把改變的新值寫入內存。(把棧中變量的指針地址拷貝到堆中)
  • 內存泄漏、野指針和殭屍對象的區別?
    • 內存泄露:在堆中申請的再也不使用的內存沒有釋放,程序結束釋放(Analyzer,Leaks)
    • 野指針:指向的內存已經被釋放,或被系統標記爲可回收(用Malloc Scribble調試)。
    • 殭屍對象:已經被釋放的對象,指針指向的內存塊認爲你無權訪問或它沒法執行該消息。(EXC_Bad_Access,開啓NSZombieEnabled檢測)
  • nil、Nil、NULL、NSNull的區別?
    • nil:空實例對象(給對象賦空值)
    • Nil:空類對象(Class class = Nil)
    • NULL:指向C類型的空指針
    • NSNull:類,用於空對象的佔位符(用於替代集合中的空對象,還有判斷對象是否爲空對象)
  • static和const的區別?
    • const:聲明全局只讀變量,(若前面沒有static修飾,在另一個文件中聲明同名的常量會報錯)
    • static:修飾變量的做用域(本文件內),被修飾的變量只會分配一分內存,在上一次修改的基礎上進行修改。
    • 通常二者配合使用,如:static const NSTimeInterval kAnimationDuration = 1.0;不會建立外部符合,編譯時預處理指令會把變量替換成常值。
  • iOS中有哪些設計模式?
    • 【單例】保證應用程序的生命週期內僅有一個該類的實力對象,易於外界訪問。如:UIApplication、NSBundle、NSNotificationCenter、NSFileManager、NSUserDefault、NSURLCache等;
    • 【觀察者】定義了一種一對多的依賴關係,可讓多個觀察者同時監聽一個主題對象,當主題對象狀態或值發生改變,會通知全部的觀察者;KVO當對象屬性變化時,通知觀察此屬性的對象。案例表明:通知和KVO
    • 【類簇】(隱藏抽象基類背後的實現細節)如:UIButton、NSNumber、NSData、NSArray、NSDictionary、NSSting。用isMemberOfClass和isKindOfClass來判斷。
    • 【命令模式】(運行時能夠調用任意類的方法),表明:NSInvocation,封裝一個請求或行爲做爲對象,包含選擇器、方法名等。
    • 【委託模式】「我想知道列表中被選中的內容在第幾行」?能夠,接受個人委託就能夠知道;只是接受個人委託就要幫我完成這幾件事情,有必需要完成的,有沒必要要完成的,至於你怎麼完成我就不關心了。
    • 【裝飾器模式】:裝飾器模式在不修改原來代碼的狀況下動態的給對象增長新的行爲和職責,它經過一個對象包裝被裝飾對象的方法來修改類的行爲,這種方法能夠作爲子類化的一種替代方法。 案例表明:Category和Delegation
  • 靜態庫和動態庫的區別?
    • 靜態庫:.a(.h配合)和.framework(資源文件.bundle)文件,編譯好的二進制代碼,使用時link,編譯時會拷貝一份到target程序中,容易增長target體積。
    • 動態庫:.tbd和.dylib,程序運行時動態加載到內存,可共享使用,動態載入有性能損失且有依賴性(系統直接提供給的framework都是動態庫!)
  • iOS中內省的幾個方法?
    • 內省是指對象在運行時將其自身細節泄露爲對象的能力。 這些細節包括對象在繼承樹中的位置,它是否符合特定的協議,以及它是否響應某個特定的消息。以及它是否響應某一消息。
    1. class和superclass方法
    2. isKindOfClass:和isMemberOfClass:
    3. respondsToSelector:
    4. conformsToProtocol:
    5. isEqual:

OC進階

  • Foundation和CoreFoundation的轉換?數組

    • __bridge:負責傳遞指針,在OC和CF之間轉換,不轉移管理權,若把OC橋接給CF,則OC釋放後CF也沒法使用。
    • __bridge_retained:將OC換成CF對象,並轉移對象全部權,同時剝奪ARC的管理權,以後須要使用CFRelease釋放。
    • __bridge_transfer:將CF轉換成OC,並轉移對象全部權,由ARC接管,因此不須要使用CFRelease釋放。
  • array和set的區別?查找速度和遍歷速度誰更快?緩存

    • array:分配的是一片連續的存儲單元,是有序的,查找時須要遍歷整個數組查找,查找速度不如Hash。
    • set:不是連續的存儲單元,且數據是無序的,經過Hash映射存儲的位置,直接對數據hash便可判斷對應的位置是否存在,查找速度較快。
    • 遍歷速度:array的數據結構是一片連續的內存單元,讀取速度較快,set是不連續的非線性的,讀取速度較慢;
  • 什麼是內聯函數?爲何須要它?安全

    • 用inline修飾的函數,不必定是內聯函數,而內聯函數必定是inline修飾的。
    • 普通函數:編譯會生成call指令(入棧、出棧),調用時將call指令地址入棧,並將子程序的起始地址送入,執行完畢後再返回原來函數執行的地址,因此會有必定的時間開銷。
    • #define宏定義和inline修飾的函數代碼被放入符號表中,使用時直接替換,沒有調用的開銷;#define宏定義的函數:使用時從符號表替換,有格式要求,不會對參數做有效性檢查且返回值不能強轉;inline修飾的內聯函數:不須要預編譯,使用時直接替換且做類型檢查,是一段直接在函數中展開的代碼(宏是直接文本替換)
    • PS:inline定義的內聯函數代碼被放入符號表中,在使用時直接替換(把代碼嵌入調用代碼),不像普通函數須要在函數表中查找,省去壓棧和出棧,沒有了調用的開銷,提升效率;
    • PS:inline修飾的內聯函數只是向編譯器申請,最後不必定按照內聯函數的方式執行,可能申請會失敗,由於函數內不容許使用循環或開關語句,太大會被編譯器還原成普通函數;inline關鍵詞必定要與函數定義一塊兒使用,聲明的話不算內聯函數;
  • 圖片顯示的過程?

    1. 假設咱們使用 +imageWithContentsOfFile: 方法從磁盤中加載一張圖片,這個時候的圖片並無解壓縮;
    2. 而後將生成的 UIImage 賦值給 UIImageView ;
    3. 接着一個隱式的 CATransaction 捕獲到了 UIImageView 圖層樹的變化;
    4. 在主線程的下一個 run loop 到來時,Core Animation 提交了這個隱式的 transaction ,這個過程可能會對圖片進行 copy 操做,而受圖片是否字節對齊等因素的影響,這個 copy 操做可能會涉及如下部分或所有步驟:
      • 分配內存緩衝區用於管理文件 IO 和解壓縮操做;
      • 將文件數據從磁盤讀到內存中;
      • 將壓縮的圖片數據解碼成未壓縮的位圖形式,這是一個很是耗時的 CPU 操做;
      • 最後 Core Animation 使用未壓縮的位圖數據渲染 UIImageView 的圖層。(必需要了解位圖相關的知識點)
  • dispatch_once如何只保證只執行一次?

    • 多線程中,如有一個線程在訪問其初始化操做,另一個線程進來後會延遲空待,有內存屏障來保證程序指令的順序執行
    void dispatch_once_f(dispatch_once_t *val, void *ctxt, void (*func)(void *)){
        volatile long *vval = val;
        if (dispatch_atomic_cmpxchg(val, 0l, 1l)) {
            func(ctxt); // block真正執行
            dispatch_atomic_barrier();
            *val = ~0l;
        } 
        else 
        {
            do
            {
                 _dispatch_hardware_pause();
            } while (*vval != ~0l);
            dispatch_atomic_barrier();
        }
    }
    複製代碼
  • NSThread、NSRunLoop和NSAutoreleasePool三者之間的關係?

    • 根據官方文檔中對 NSRunLoop 的描述,咱們能夠知道每個線程,包括主線程,都會擁有一個專屬的 NSRunLoop 對象,而且會在有須要的時候自動建立。
    • 根據官方文檔中對NSAutoreleasePool的描述,咱們可知,在主線程的 NSRunLoop 對象(在系統級別的其餘線程中應該也是如此,好比經過 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 獲取到的線程)的每一個 event loop 開始前,系統會自動建立一個 autoreleasepool ,並在 event loop 結束時 drain 。
    • 須要手動添加autoreleasepool的狀況:
      1. 若是你編寫的循環中建立了大量的臨時對象;
      2. 若是你建立了一個輔助線程
  • 分類可擴展的區別?(可從內存佈局、加載順序、分類方法和原類方法的執行順序來回答)

    • extension:必須在.m中添加,編譯時做爲類的一部分參與內存佈局,生命週期與類同樣,添加私有實現,隱藏類的私有信息。
    • category:獨立的.h和.m文件,獨立編譯動態加載dyld,爲已有類擴展功能。
    • 分類侷限:沒法添加實例變量,能夠添加屬性但不會爲其生成實例變量(在運行時,對象的內存佈局已肯定,若添加實例變量就會破壞類的內存佈局,這對編譯型語言來講是災難性的)
    • 分類的加載:把實例方法、屬性和協議添加到類上,如有協議和類方法則添加到元類, 運行時分類的方法列表被動態添加到類的方法列表中,且在類的原有方法前面;
    • 方法順序:【load方法】先調用父類的load方法再調用分類的,【重寫的方法】因爲分類方法在原類方法的前面,因此優先調用分類中的方法。
  • OC對象釋放的流程?runloop的循環週期會檢查引用計數,釋放流程:release->dealloc->dispose

    • release:引用計數器減一,直到爲0時開始釋放
    • dealloc:對象銷燬的入口
    • dispose:銷燬對象和釋放內存
      • objc_destructInstance:調用C++的清理方法和移除關聯引用
        • clearDeallocating:把weak置nil,銷燬當前對象的表結構,經過如下兩個方法執行(二選一)
          • sidetable_clearDeallocating:清理有指針isa的對象
          • clearDeallocating_slow:清理非指針isa的對象
      • free:釋放內存
  • CDDisplayLink和NSTimer的區別?

    • CADisplayLink:以和屏幕刷新率相同的頻率將內容畫到屏幕上的定時器,需手動添加runloop
    • NSTimer:可自動添加到當前線程的runloop,默認defualt模式,也能夠選擇添加的loopMode;
  • 用runtime實現方法交換有什麼風險?

    • 風險1:若不在load中交換是非原子性的,在initial方法中不安全
    • 風險2:重寫父類方法時,大部分都是須要調用super方法的,swizzling交換方法,若不調用,可能會出現一些問題,如:命名衝突、改變參數、調用順序、難以預測、難以調試。

runtime源碼相關

  • 知道AutoreleasePoolPage嗎?它是怎麼工做的?

    • AutoreleasePool由AuthReleasePoolPage實現,對應AutoreleasePoolPage 的具體實現就是往AutoreleasePoolPage中的next位置插入一個POOL_SENTINEL,而且返回插入的POOL_SENTINEL的內存地址。這個地址也就是咱們前面提到的 pool token,在執行pop操做的時候做爲函數的入參。
    • 經過調用 autoreleaseFast函數來執行具體的插入操做;autoreleaseFast函數在執行一個具體的插入操做時,分別對三種狀況進行了不一樣的處理:
      1. 當前 page存在且沒有滿時,直接將對象添加到當前page中,即next指向的位置;
      2. 當前page存在且已滿時,建立一個新的page,並將對象添加到新建立的 page中,而後關聯child page。
      3. 當前page不存在時,即尚未page時,建立第一個page,並將對象添加到新建立的page中。
  • KVO的底層實現?(看過RAC源碼的應該知道,RAC監聽方法也是基於此原理,只是稍微有些不一樣)

    1. 當一個object有觀察者時,動態建立這個object的類的子類
    2. 對每一個被觀察的property,重寫其setter方法
    3. 在重寫的setter方法中調用willChangeValueForKey:和didChangeValueForKey通知觀察者
    4. 當一個property沒有觀察者時,重寫方法不會被刪除,直到移除全部的觀察者纔會刪除且刪除動態建立的子類。
    // 1)監聽前的準備
    [human addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { /* 監聽後的處理 */ }
    // 2)關閉系統觀察者的自動通知
    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
        if ([key isEqualToString:@"name"]) {
            return NO;
        }
        return [super automaticallyNotifiesObserversForKey:key];
    }
    // 3)調用這兩個方法
    [self willChangeValueForKey:@"name"];
    /* 在此對值進行修改 */
    [self didChangeValueForKey:@"name」]; 複製代碼
  • 被weak修飾的對象是如何被置nil的?知道SideTable嗎?

    • Runtime對註冊的類會進行內存佈局,有個SideTable結構體是負責管理類的引用計數表和weak表,weak修飾的對象地址做爲key存放到全局的weak引用表中,value是全部指向這個weak指針的地址集合,調用release會致使引用計數器減一,當引用計數器爲0時調用dealloc,在執行dealloc時將全部指向該對象的weak指針的值設爲nil,避免懸空指針。
  • 什麼是關聯對象?能夠用來幹嗎?系統如何管理管理對象?支持KVO嗎?

    • 關聯對象:在運行時動態爲指定對象關聯一個有生命週期的變量(經過objc_setAssociatedObject和objc_getAssociatedObject),用於實現category中屬性保存數據的能力和傳值。(支持KVO,讓關聯的對象做爲觀察者)
    • 管理關聯對象:系統經過管理一個全局哈希表,經過對象指針地址和傳遞的固定參數地址來獲取關聯對象。根據setter傳入的參數策略,來管理對象的生命週期。經過一個全局的hash表管理對象的關聯,經過對象指針地址獲取對象關聯表,再根據自定義key查找對應的值(外表:key(對象指針)-value(hash表),內表:key(自定義name)-value(管理的值))
  • isa、對象、類對象、元類和父類之間的關係?

    • 類:對象是類的一個實例,類也是另外一個類的實例,這個類就是元類 (metaclass)。元類保存了類的類方法。當一個類方法被調用時,元類會首先查找它自己是否有該類方法的實現,若是沒有,則該元類會向它的父類查找該方法,一直找到繼承鏈的根部,找不到就轉發。
    • 元類:元類 (metaclass) 也是一個實例,那麼元類的 isa 指針又指向哪裏呢?爲了設計上的完整,全部的元類的 isa 指針都會指向一個根元類 (root metaclass)。根元類 (root metaclass) 自己的 isa 指針指向本身,這樣就行成了一個閉環。上面提到,一個對象可以接收的消息列表是保存在它所對應的類中的。在實際編程中,咱們幾乎不會遇到向元類發消息的狀況,那它的 isa 指針在實際上不多用到。不過這麼設計保證了面向對象的乾淨,即全部事物都是對象,都有 isa 指針。
    • 繼承:咱們再來看看繼承關係,因爲類方法的定義是保存在元類 (metaclass) 中,而方法調用的規則是,若是該類沒有一個方法的實現,則向它的父類繼續查找。因此,爲了保證父類的類方法能夠在子類中能夠被調用,因此子類的元類會繼承父類的元類,換而言之,類對象和元類對象有着一樣的繼承關係。

  • 知道建立類的方法objc_allocateClassPair?方法裏面具體作了什麼事情?

// objc-class-old.mm
    Class objc_allocateClassPair(Class supercls, const char *name, 
                             size_t extraBytes)
    {
        Class cls, meta;
        if (objc_getClass(name)) return nil;
        // fixme reserve class name against simultaneous allocation
        if (supercls  &&  (supercls->info & CLS_CONSTRUCTING)) {
            // Can't make subclass of an in-construction class return nil; } // Allocate new classes. if (supercls) { // 若父類存在,父類的內存空間+額外的空間 = 新類的內存大小 cls = _calloc_class(supercls->ISA()->alignedInstanceSize() + extraBytes); meta = _calloc_class(supercls->ISA()->ISA()->alignedInstanceSize() + extraBytes); } else { // 若父類不存在,基類的內存空間+額外的空間 = 新類的內存大小(objc_class是objc_object的子類) cls = _calloc_class(sizeof(objc_class) + extraBytes); meta = _calloc_class(sizeof(objc_class) + extraBytes); } // 初始化 objc_initializeClassPair(supercls, name, cls, meta); return cls; } // objc-runtime-new.mm Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) { Class cls, meta; rwlock_writer_t lock(runtimeLock); // Fail if the class name is in use. // Fail if the superclass isn't kosher.
        if (getClass(name)  ||  !verifySuperclass(superclass, true/*rootOK*/)) {
            return nil;
        }
        // Allocate new classes.
        cls  = alloc_class_for_subclass(superclass, extraBytes);
        meta = alloc_class_for_subclass(superclass, extraBytes);
        // fixme mangle the name if it looks swift-y?
        objc_initializeClassPair_internal(superclass, name, cls, meta);
        return cls;
    }
複製代碼
  • class_ro_t 和 class_rw_t 的區別?
    • class_ro_t是readonly,class_rw_t是readwrite;
    • 在編譯以後,class_ro_t的baseMethodList就已經肯定。當鏡像加載的時候,methodizeClass方法會將baseMethodList添加到class_rw_t的methods列表中,以後會遍歷category_list,並將category的方法也添加到methods列表中。這裏的category指的是分類,基於此,category能擴充一個類的方法。這是開發時常常須要使用到。
    • class_ro_t在內存中是不可變的。在運行期間,動態給類添加方法,實質上是更新class_rw_t的methods列表。
  • 除了objc_msgSend,還知不知作別的消息發送函數?
    • objc_msgSend_stret:當CPU的寄存器可以容納下消息返回類型時,該函數才能處理此消息,若超過CPU寄存器則由另外一個函數執行派發,原函數會經過分配在棧上的變量來處理消息所返回的結構體。
    • objc_msgSendSuper_stret:向父類實例發送一個返回結構體的消息
    • objc_msgSendSuper:向超類發送消息([super msg])
    • objc_msgSend_fpret:消息返回浮點數時,在某些結構的CPU中調用函數時須要對「浮點數寄存器」作特殊處理,不一樣的架構下浮點數表示範圍不同(寄存器是CPU的一部分,用於存儲指令、數據和地址)
  • 什麼是方法交換?怎麼用的?
    • SEL方法地址和IMP函數指針是經過DispatchTable表來映射,能夠經過runtime動態修改SEL,因此能夠實現方法的交換(使用經驗根據項目來談便可)。

數據持久化

  • plist:XML文件,讀寫都是整個覆蓋,需讀取整個文件,適用於較少的數據存儲,通常用於存儲App設置相關信息。
  • NSUserDefault:經過UserDefault對plist文件進行讀寫操做,做用和應用場景同上。
  • NSKeyedArchiver:被序列化的對象必須支持NSCoding協議,能夠指定任意數據存儲位置和文件名。整個文件複寫,讀寫大數據性能低。
  • CoreData:是官方推出的大規模數據持久化的方案,它的基本邏輯相似於 SQL 數據庫,每一個表爲 Entity,而後咱們能夠添加、讀取、修改、刪除對象實例。它能夠像 SQL 同樣提供模糊搜索、過濾搜索、表關聯等各類複雜操做。儘管功能強大,它的缺點是學習曲線高,操做複雜。
  • SQLite(FMDB、Realm)

多線程

  • 串行隊列和併發隊列的區別?同步和異步的區別?

    • 串行隊列(Serial Queue):指隊列中同一時間只能執行一個任務,當前任務執行完後才能執行下一個任務,在串行隊列中只有一個線程。
    • 併發隊列(Concurrent Queue):容許多個任務在同一個時間同時進行,在併發隊列中有多個線程。串行隊列的任務必定是按開始的順序結束,而併發隊列的任務並不必定會按照開始的順序而結束。
    • 同步(Sync):會把當前的任務加入到隊列中,除非等到任務執行完成,線程纔會返回繼續運行,也就是說同步會阻塞線程。
    • 異步(Async):也會把當前的任務加入到隊列中,但它會馬上返回,無需等任務執行完成,也就是說異步不會阻塞線程。
    • PS:不管是串行仍是併發隊列均可以執行執行同步或異步操做。注意在串行隊列上執行同步操做容易形成死鎖,在併發隊列上則不用擔憂。異步操做不管是在串行隊列仍是併發隊列上均可能出現線程安全的問題。
  • GCD和NSOperation的區別?

    • NSOperation VS GCD
    1. 語法:面向對象(重量級) - 面向C(輕量級)
    2. 相比GCD的優勢:
      1. 取消某個操做(未啓動的任務)、
      2. 指定操做間的依賴關係(根據需求指定依賴關係)
      3. 經過鍵值觀察機制監控NSOperation對象的屬性(KVO)
      4. 指定操做的優先級
      5. 重用NSOperation對象
      6. 提供可選的完成block

線程安全

  • 如何保證線程安全?

    • 原子操做(Atomic Operation):是指不會被線程調度機制打斷的操做;這種操做一旦開始,就一直運行到結束,中間不會有任何的上下文切換(context switch)。
    • 內存屏障(Memory Barrier):確保內存操做以正確的順序發生,不阻塞線程(鎖的底層都會使用內存屏障,會減小編譯器執行優化的次數,謹慎使用)。
      • 互斥鎖(pthread_mutex):原理與信號量相似,但其並不是使用忙等,而是阻塞線程和休眠,需切換上下文。
      • 遞歸鎖(NSRecursiveLock):本質是封裝了互斥鎖的PTHREAD_MUTEX_RECURSIVE類型的鎖,容許同一個線程在未釋放其擁有的鎖時反覆對該鎖進行加鎖操做。
      • 自旋鎖(OSSpinLock):經過全局變量,來判斷當前鎖是否可用,不可用就忙等。
      • @synchronized(self):系統提供的面向OC的API,經過把對象hash當作鎖來用。
      • NSLock:本質是封裝了互斥鎖的PTHREAD_MUTEX_ERRORCHECK類型的鎖,它會損失必定性能換來錯誤提示,由於面向對象的設計,其性能稍慢。
      • 條件變量(NSConditionLock):底層經過(condition variable)pthread_cond_t來實現的,相似信號量具備阻塞線程與信號機制,當某個等待的數據就緒後喚醒線程,好比常見的生產者-消費者模式。
      • 信號量(dispatch_semaphore)
        // 傳入值需>=0,若傳0則阻塞線程並等待timeout
        dispatch_semaphore_create(1):
        // lock 資源已被鎖,會使得signal值-1
        dispatch_semaphore_wait(signal, overTime):
        // unlock,會使得signal值+1
        dispatch_semaphore_signal(signal):
        複製代碼
  • 什麼是死鎖?如何避免死鎖?

    • 死鎖:兩個或兩個以上的線程,互相等待彼此中止以得到某種資源,可是沒有一方會提早退出的狀況。
    • 避免在串行隊列中執行同步任務;避免Operation相互依賴;
  • 什麼是優先倒置?

    • 低優先級任務會先於高優先級任務執行,假設:任務優先級A>B>C,A等待訪問被C正在使用的臨界資源,同時B也要訪問該資源,B的優先級高於C,同時,C優先級任務被B次高優先級的任務所搶先,從而沒法及時地釋放該臨界資源。這種狀況下,B次高優先級任務得到執行權,而高優先級任務A只能被阻塞。 能夠設置相同的任務優先級避免優先倒置。

項目經驗相關題

  • 何時重構?怎麼重構的?(包括但不限於如下幾點,僅供參考)
    • 當前架構支撐不了業務、當前設計模式弊端太多、項目急着上線沒有來得及優化等等。
    1. 優化業務邏輯;刪除沒用的代碼(包括第三方庫、變量、方法等,註釋除外);
    2. 保持類和方法的單一性原則,把不屬於本類功能的部門移植到一個新類中
    3. 根據實際使用的需求、優化基類中的屬性(像BaseObject等)
    4. 修改一點就測試一點,保證每一步的重構不影響原有功能
    5. 規範(命名、語法等)
    6. 編譯優化
    • PS: 重構是一個長期的過程,每一次緊迫迭代的新功能均可能須要優化,特別是在多人開發的時候,若是來不及在項目發佈前Code Review,在重構時Code Review就顯得頗有必要。
  • AppDelegate如何瘦身?
    • 主要是在保證功能和效率以及性能的前提下,把第三方的初始化代碼拆解到別的文件中處理
    1. category
      • 優勢:簡單、直接,不須要任何第三方來協助;
      • 缺點:添加屬性不太方便,須要藉助關聯對象來實現;
    2. FRDModuleManager
      • 優勢:簡單、易維護,耦合性低;
      • 缺點:模塊增多需分配很多內存,對象都是長期持有的(如有依賴關係,優先級不明確)
  • 如何解決卡頓?
    • 卡頓主要是由於主線程執行了一些耗時操做致使
    • 耗時計算:儘量放到子線程中執行
    • 圖片解壓縮:在子線程強制解壓縮
    • 離屏渲染:避免致使離屏渲染(注意繪製方法)
    • 優化業務流程:減小中間層,業務邏輯
    • 合理分配線程:UI操做和數據源放到主線程,保證主線程儘量處理少的非UI操做,同時控制App子線程數量。
    • 預加載和延時加載:平衡CPU和GPU使用率;優先加載可視內容,提高界面繪製速度。
  • 如何排查Crash?
    • 斷點:通常的Crash
    • zombie object:殭屍對象
    • dSYM:iOS編譯後保存16進制函數地址映射信息的文件 (根據Crash的內存地址,定位對應的Crash代碼的範圍)
  • 如何檢測內存泄漏?有沒有遇到內存警告?怎麼解決的?
    • dealloc方法:手動檢測的笨方法
    • 第三方庫PLeakSniffer和MLeaksFinder:自動檢測
  • 有何優化App啓動速度?(main前和main後)
    • 設置環境變量:DYLD_PRINT_STATISTICS或DYLD_PRINT_STATISTICS_DETAILS爲1(更詳細),Xcode-> Edit Scheme-> Run-> Arguments,添加以上二選一 環境變量
    main以前:
            Total pre-main time:  94.33 milliseconds (100.0%) // main函數前總共須要94.33ms
            dylib loading time:  61.87 milliseconds (65.5%)    // 動態庫加載
            rebase/binding time:   3.09 milliseconds (3.2%)    // 指針重定位
            ObjC setup time:  10.78 milliseconds (11.4%)    // Objc類初始化
            initializer time:  18.50 milliseconds (19.6%)    // 各類初始化
            slowest intializers :    // 在各類初始化中,最耗時的以下:
                libSystem.B.dylib :   3.59 milliseconds (3.8%)
                libBacktraceRecording.dylib :   3.65 milliseconds (3.8%)
                GTFreeWifi :   7.09 milliseconds (7.5%)
    複製代碼
    • main()函數以前耗時的影響因素

      • 動態庫加載越多,啓動越慢。
      • ObjC類越多,啓動越慢
      • C的constructor函數越多,啓動越慢
      • C++靜態對象越多,啓動越慢
      • ObjC的+load越多,啓動越慢
    • main()函數以後耗時的影響因素

      • 執行applicationWillFinishLaunching的耗時
      • rootViewController及其childViewController的加載、view及其subviews的加載
    • 具體優化內容:

      1. 移除不須要用到的動態庫
      2. 移除不須要用到的類
      3. 合併功能相似的類和擴展(Category)
      4. 壓縮資源圖片
      5. 優化applicationWillFinishLaunching
      6. 優化rootViewController加載
      7. 挖掘最後一點性能優化
    • PS:應該在400ms內完成main()函數以前的加載,總體過程耗時不能超過20秒,不然系統會kill掉進程,App啓動失敗

開源庫

這部分主要跟簡歷中提到的相關庫有關,建議對簡歷中提到的開源庫,必定要有所準備。

SDWebImage

SDWebImage幾乎是每一個iOS開發者都用過的開源庫,也是在簡歷中曝光度比較高的開源庫之一,同時也幾乎是面試都會問到的,因此要準備充分再去。

  • 從調用到顯示的過程?
    1. 根據請求的文件URL路徑判斷當前是否存在還沒結束的操做,如有的話就取消並移除與URL映射的操做。
    2. 設置placeholder佔位圖,無論URL是否爲空都會設置,且在當前隊列同步執行。
    3. 判斷URL是否爲空,爲空則執行completedBlock回調,並結束。
    4. URL非空,如果首次則開始相關類的初始化,如:加載進度及其相關的Block、SDWebImageManager(SDWebImageCache管理緩存、SDWebImageDownloader管理),若非首次仍是使用最初初始化的實例,由於SDWebImageManager是以單例的形式存在。
    5. 開始進入SDWebImageManager的範圍,URL容錯處理、建立負責加載圖片和取消加載圖片的類,判斷當前的URL是否在黑名單裏,若存在則執行回調,返回當前的操做類,再也不處理下載失敗過的URL,並結束。
    6. 若URL不在黑名單裏,則開始在內存緩存中查找,找到後執行doneBlock回調,在該回調方法中執行判斷當前操做是否已取消,未取消則回調並異步設置圖片,在回調的Block中,每次都會檢查當前操做是否已取消,若取消則不處理,並結束。
    7. 內存緩存沒找到,則在磁盤緩存中查找,通常是在串行隊列異步執行,根據URL路徑找到存儲的數據並取出,而後縮放解壓縮返回,執行回調設置圖片,並結束。
    8. 磁盤沒找到則下載圖片,下載完成後緩存,設置圖片;SDWebImage對圖片做了哪些優化:子線程強制解壓縮,從硬盤獲取的圖片和下載的圖片都進行解壓縮操做,提升渲染效率,節省主線程的工做量。

ReactiveCocoa

該庫比較複雜,可問的問題也很是多,如下僅供參考,建議本身找答案(本身理解後才能從容面對)

  • 冷熱信號的區別?
  • RAC如何監聽方法?
  • bind方法作了什麼?
  • RAC中的RACObserver和KVO有什麼區別?
  • RAC的map和flattenMap的區別?

工具

不免會遇到一些有關經常使用工具的問題,提供幾個僅供參考

  • Git、SVN?
    • 問題可深可淺,淺:基本用法或相關命令,深:Git的工做原理
  • CocoaPods
    • pod update和pod install的區別
  • CI(持續集成、持續部署)
    • 後期更新
相關文章
相關標籤/搜索