iOS 面試題(1)

整理一下面試題:

阿里p5

1.MVC具有什麼樣的優勢,各個模塊之間怎麼通信,比如點擊 Button 後 怎麼通知 Model?

參考答案:

MVC 是一種設計思想,一種框架模式,是一種把應用中的所有類組織起來的策略,它把你的程序分爲三塊,分別是:

M : 實際上考慮的是“什麼”問題,你的程序本質上是什麼,獨立於UI工作,是程序重處理應用程序邏輯的部分,通常負責存取數據。

C:  控制你的Model 如何呈現在屏幕上,當它需要數據的時候就告訴Model,你幫我獲取某某某數據,當它需要UI展示和更新的時候就告訴View,你幫我生成一個UI顯示某某數據,是Model 和View 的溝通橋樑。

MVC 通信規則

Controller to Model

可以直接單向通訊。Controller 需要將Model 呈現給用戶,因此需要知道模型的一切,還需要有通Model 完全通信的能力,並且能任意使用Model 的公共API.

Controller to View

可以直接通信,Controller 通過View 來佈局用戶界面。

Model to View

永遠不要直接通信,Model 是獨立於UI的,並不需要和View 直接通信,View 通過Controller 獲取數據。

View to Controller

View 不能對Controller 知道的太多,因此要通過間接的方式通信。

Target action.首先Contrller 會留給自己一個Target ,再把配套的action 交給view 作爲聯繫方式。那麼view 接收到某些變化時,View 就會發送action 給target 從而達到通知的目的。這裏View 只需要發送action ,並不需要知道Controller 如何取執行方法。

代理。有時候View 沒有足夠的邏輯去判斷用戶操作是否符合規範,他會把判斷這些問題的權利委託給其他對象。他只需要獲得答案就行了,並不管是誰給的答案。

DataSource  View 沒有擁有他們所顯示數據的權利,View 只能向controller 請求數據進行顯示,controller 則獲取Model 的數據整理排版後提供給View.

Model 訪問Controller

同樣的Model 是獨立於UI存在的。因此無法直接與Controller 通信,但是當Moel 本身信息發生改變的時候,會通過下面的方式進行間接通信。

Notification & KVO 一種類似電臺的方法,Model 信息的改變時會廣播消息給感興趣的人,只要Controller 收到了這個廣播的時候就會主動聯繫Model,獲取新的數據並提供給View.

MVC的優點: 低耦合性。有利於開發分工。有利於組件重用。可維護性。

2.兩個無限長度鏈表(也就是可能有環) 判斷有沒有交點

(單鏈表是否存在環?環的入口是什麼?)

是否存在環

1) 判斷是否存在環: 設置快慢指針fast 和slow,fast 步速爲2,slow 爲1,若最終fast==slow,那麼就證明單鏈表中一定有環,如果沒有環的話,fast 一定先達到尾節點。

2)簡單證明: 利用相對運動的概念,以slow 爲參考點(靜止不動),那麼fast的步速實際爲1,當fast 超過slow之後,fast 以每步一個節點的速度追趕slow,如果鏈表有環的話,fast 一定會追趕slow,即fast==slow.

如何找到環的入口

第一次相遇

字母代表的量:

a.鏈表頭節點到環入口的距離

r:環長

藍色線:fast 指針所走的距離爲2s

黑色線:slow指針所走的距離1s

假設鏈表的總長度爲L,且fast 與slow 相遇時fast 已經繞環走了n圈,則有如下關係:

2s = s + nr

將s 移到左邊得:

s = nr

轉換:

s = (n-1)r + r = (n-1)r + L -a

a + x = (n-1)r + L -a

得:

a = (n-1)r+L-a-x

由圖可知,(L-a-x)爲相遇點到環入口的距離。由上式可知:

從鏈表頭到環入口的距離 = (n-1)圈內環循環 + 相遇點到環入口的距離,將r視爲週期的話,a 與L-a-x 在某種意義上是相等的(實際並不相等)。那麼由此我們便找到了突破點,爲了找到環的入口點,在fast 與slow 相遇時,將slow 指針重新指向單鏈表的頭節點,fast 仍留在相遇點,只不過步速降爲與slow 相同的1,每次循環只經過一個節點,如此,當fast 與slow 再次相遇,那個新的相遇點便是我們苦苦尋找的入口點了。

如何知道環的長度

記錄下相遇點,讓slow 與fast 從該點開始,再次碰撞所走過的操作數就是環的長度r.

帶環的鏈表長度是多少

鏈表的長度=環入口+環的長度 L = a+ r

分析問題之前我們先搞清楚鏈表相交的一些基本概念

明確概念: 兩個單向鏈表相交,只能是y型相交,不可能是x型相交

分析: 有兩個鏈表,La,Lb,設他們的交點爲p,假設La 中,p的前驅爲pre_a,,後繼爲next_a,在Lb 中,前驅爲pre_b,後繼爲next_b,則pre_a->next=p,pre_b->next=p,接下來看後繼,p->next = next_a,p->next=next_b,那麼問題來了,一個單鏈表的next指針只有一個,怎麼跑兩個呢,所以必有next_a==next_b,於是我們能得出兩個鏈表相交只能是y型相交。

情況一: 兩個鏈表都是無環

1)問題簡化。將鏈表B接到鏈表A的後面,如果A,B有交點,則構成一有環的單鏈表,而我們上面討論了一個如何判斷一個單鏈表有環。

2) 若兩個鏈表相交則必爲Y型,由此可知兩個鏈表從相交點到尾節點是相同的,我們並不知道他們的相交點位置。但是我們可以遍歷的出AB鏈表的尾節點,如此,比較他們的尾節點是否相等便可以求證A。B是否相交了。

情況二:鏈表有環

1)其中一個鏈表有環,另一個鏈表無環。則兩個鏈表不可能相交。

2)那麼有環相交的情況只有當兩個鏈表都有環時纔會出現,如果兩個有環鏈表相交,則他們擁有共通的換,即環上任意一個節點都存在於兩個鏈表上,因此,通過判斷A鏈表上的快慢指針相遇點是否也在B鏈表上遍可以得出兩個鏈表是否相交了。

如果兩個無環相交的單向鏈表,怎麼求出他們的第一個節點呢? 分析:採用對齊的思想,計算兩個鏈表的長度L1,L2,分別用兩個指針P1,P2指向兩個鏈表的頭,然後將較長的表的p1向後移動L2-L1個節點,然後再同時向後移動P1,P2,知道P1=p2,相遇的點就是相交的第一個節點。

3.UITableView的相關優化

1)正確的使用UITableViewCell的重用機制

2)提前計算好cell的高度和佈局

3)避免阻塞主線程

很多時候我們需要從網絡加載圖片,把這些操作放在後臺執行,並且緩存起來,現在大部分使用sdwebImage進行網絡圖片處理,正常的使用是沒有問題的,但是如果對性能要求較高,或者要處理gif圖片,推薦YYWebImage

4)按需加載

5)減少SubViews的數量

6)儘可能重用開銷比較大的對象。

如NSDateFormatter 和NSCalendar等對象初始化比較慢,我們可以把它加入類的屬性中,或創建單例來使用。

7)儘量減少計算的複雜度。

在高分屏儘量用ceil 或floor 或round 取整,不要出現1.007這樣的小數。

8)不要動態add 或者remove 子控件

最好在初始化時就添加完,然後通過hidden來控制是否顯示。

9)cell 上的顏色儘量不要用透明顏色

因爲在渲染這些view 時,如果是透明的顏色,需要將該View 和下層view 混合後才計算出該像素點的實際顏色

10)使用異步繪製

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       CGRect rect = CGRectMake(0, 0, 100, 100);
       UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);
       CGContextRef context = UIGraphicsGetCurrentContext();
       [[UIColor lightGrayColor] set];
       CGContextFillRect(context, rect);
       
       //將繪製的內容以圖片的形式返回,並調用主線程顯示
       UIImage * tempImg = UIGraphicsGetImageFromCurrentImageContext();
       UIGraphicsEndImageContext();
       //回到主線程
       dispatch_async(dispatch_get_main_queue(), ^{
           //code
       });

   });

另外繪製cell 不建議使用UIView,建議使用CALayer

從形式來說;UIView 的繪製是建立在CoreGraphic 上的,使用的是CPU,CALayer 使用的是CoreAnimation,CPU,GPU通吃,由系統決定使用哪個,View 的繪製使用的是自下而上的一層一層的繪製,然後渲染,Layer處理的是Texture,利用GPU的Texture Chache 和獨立的浮點數計算單元加速紋理的處理。

從事件響應上來說,UIView 是CALayer 的代理,layer 本身並不能響應事件,因爲layer 是直接繼承NSObject,不具備處理事件的能力。而UIView 繼承了UIResponder 的,這也是事件轉發的事件角度說明,view 要比單純的layer 複雜的多。在滑動的列表上,多層次的view 再加上各種手勢的處理勢必會導致幀數的下降。

在這一塊還有個問題就是當tableView 快速滑動的時候,會有大量的異步繪製任務提交到後臺線程去執行,線程並不是越多越好,太多了只會增加cpu的負擔,所以我們需要在適當的時候取消不重要的線程。

目前兩種做法:

YY的做法是:

儘量快速、提前判斷當前繪製的任務是否取消,在繪製每一行文本前,都不會調用isCancelled()來進行判斷,保證被取消的任務能及時退出,不至於影響後續操作。

另一種做法是:

當滑動時,鬆開手指後,立刻計算出滑動停止時cell 的位置,並預先繪製那個位置附近的幾個cell,而忽略當前滑動中的cell,忽略的代價就是快速滑動中會出現大量空白的內容。

11)儘量避免使用帶來離屏渲染的效果

4.KVO、Notification、delegate各自的優缺點,效率還有使用場景

delegate 的優勢:

1)很嚴格的語法,所有能響應的事件必須在協議中有清晰的定義

2)因爲有很嚴格的語法,所以編譯器能幫你檢查是否實現了所有應該實現的方法,不容易遺忘和出錯

3)使用delegate的時候,邏輯很清楚,控制流程可跟蹤和識別

4)在一個controller 中可以定義多個協議,每個協議有不同的delegate

5) 沒有第三方的要求保持/監視通訊過程,假如出現問題我們可以很容易比較方便的定位錯誤代碼。

6)能夠接受調用的協議方法的返回值,意味着delegate 能夠提供反饋信息給controller

delegate的缺點:

 需要寫的代碼很多

NotificationCenter : 單例,允許當前事件發生的時候通知一些對象,滿足控制器與一個任意的對象進行通信的目的,這種模式的基本特徵就是接收到在該controller中發生的某種事件而產生的消息,controller 用一個key 通知的名稱,這樣對於controller 是匿名的,其他的使用同樣的key 來註冊了該通知的對象能對通知的事件作出反應。

notification的優勢:

1、不需要寫多少代碼,實現比較簡單

2、一個對象發出的通知,多個對象能進行反應,一對多的方式實現很簡單

缺點:

1、編譯期不會知道通知是否能被正確處理

2、釋放註冊的對象時候,需要在通知中心取消註冊。

3、調試的時候程序的工作以及控制流程難跟蹤

4、需要第三方來管理controller 和觀察者的聯繫

5、controller 和觀察者需要提前知道通知的名稱、UseInfo dictionary keys,如果沒有這些在工作區間會出現不同步的現象

6、通知發出後,發出通知的對象不能從觀察者獲得任何反饋

KVO

KVO 是一個對象能觀察另一個對象屬性的值,前兩種模式更適合一個controller 和其它對象通信,而kvo 適合任何對象監聽另一個對象的改變,這是一個對象與另一個對象保持同步的方法,kvo 只能對屬性做出反應,不會用來對方法或者事件作出反應。

優點:

1、提供一個簡單的方法來實現兩個對象同步

2、能夠對非我們創建的對象作出反應

3、能夠提供觀察的屬性的最新值和先前值

4、用keypaths 來觀察屬性,因此也可以觀察嵌套對象

缺點:

1、觀察的屬性值必須用string 定義,因此編譯器不會出現警告和檢查

2、對屬性重構將導致觀察不可用

3、複雜的if 語句要求對象正在觀察多個值,這是因爲所有的觀察都通過一個方法來指向kvo 有顯著的使用場景,當你希望監視一個屬性的時候,我們選用KVO

5.如何手動通知KVO

想要知道如何手動觸發,必須知道自動觸發KVO的原理:

willChangeValueForKey: 和didChangeValueForKey

一個是在被觀察屬性發生改變之前,willChangeValueForKey 一定會被調用,這就會記錄舊的值

當改變發生之後,didChangeValueForKey:會被調用

繼而objectServerValueForKey: OfObject:change:content: 也會被調用

如果可以手動實現這些調用就可以實現手動觸發了

6.Objective-C 中的copy方法

對象的複製就是複製一個對象作爲副本,它會開闢一塊新的內存來存儲副本對象。必須實現NSCopying 或NSMutableCopying 協議

copy:產生的副本是不可變的

mutableCopy:產生的副本是可變的

自定義copy 還必須實現copywithzone 或mutableCopywithzone

7.runtime 中,SEL和IMP的區別

IMP 指向方法實現的指針

SEL 類成員方法的指針

8.autoreleasepool的使用場景和原理

當某一塊代碼中產生大量變量,容易造成殭屍對象的地方。autoreleasepool 可以自動管理對象,當對象引用計數器爲0時會在autoreleasepool pop時自動銷燬,原理是延遲調用release

9.RunLoop的實現原理和數據結構,什麼時候會用到

 此處省略一千字

10.block爲什麼會有循環引用

 block 在oc 中是一個獨立的代碼塊,當block 中持有block 所在控制器時,而控制器又持有block時,兩者的引用計數器都是1,只有一方的引用計數器爲0,纔可以釋放,否則,相互引用就不會釋放,可以使用__weak 來修飾變量來解除循環引用。

12.NSOperation和GCD的區別

NSOperation 是對GCD的封裝的一個OC的api.GCD是基於c的api,是蘋果對C中Pthread 的一個封裝,其內部做了很多性能方面的優化。GCD使用block,代碼高效簡潔,可以實現比較複雜的多線程應用。

NSOperation 很顯著的特點是可以設置最大併發數和依賴關係,可以使用KVO監聽不同時段的狀態。

13.CoreData的使用,如何處理多線程問題


14.如何設計圖片緩存?

1、提高相應速度

2、減少網絡流量

3、提高用戶體驗

15.有沒有自己設計過網絡控件?




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章