招聘高峰期來了,大家都非常積極地準備著跳槽,那么去一家公司面試就會有一堆新鮮的問題,可能不會,也可能會,但是了解不夠深。本篇文章為群里的小伙伴們?nèi)ツ彻镜墓P試題,由筆者整理并提供筆者個人參考答案。注意,僅供參考,不代表絕對正確。
iOS中高級筆試題
題照(前五題)
1、鏈表不具備的特點是()
A. 可隨機訪問任何一個元素
B. 插入,刪除操作不需要移動元素
C. 無需事先估計存儲空間大小
D. 所欲存儲空間可以是不連續(xù)的
這道題是考大學(xué)時所學(xué)的鏈表知識,其實筆者也忘了很多了。因為在大學(xué)時,曾經(jīng)自己寫過很多鏈表相關(guān)的代碼,再加上經(jīng)常幫同學(xué)調(diào)試,以及幫助同學(xué)講解鏈表相關(guān)知識點,因此記憶較深。以下答案純屬個人認知,非百度而來,若有不對之處,請指出。
參考答案: (A)
鏈表不同于數(shù)組。鏈表之所有叫鏈表,就是像一條鏈一樣,要過到某個節(jié)點處,就得遍歷著找;而數(shù)組才具備隨機訪問任何一個元素的能力,數(shù)組可以通過索引直接訪問元素,時間復(fù)雜度為常量,效率非常高,因此在某些場合上,我們需要數(shù)組這樣的數(shù)據(jù)結(jié)構(gòu)。
B. 鏈表的插入、刪除都不需要移動元素,只需要修改指針的指向就可以了,因為鏈表上的每個節(jié)點都是動態(tài)分配的,分配在堆上,通過指針來指向每個節(jié)點的內(nèi)存區(qū),要獲取某個節(jié)點的值,是需要遍歷一遍才能找到對應(yīng)的節(jié)點的。
C. 因為鏈表上的每個節(jié)點是分配在堆上,需要開發(fā)人員手動申請內(nèi)存空間的,因此不像數(shù)組在定義時就要指定存儲空間大小。對于鏈表,需要增加一個節(jié)點時,直接在堆上申請。當需要刪除某個節(jié)點時,可以直接將該節(jié)點的內(nèi)存給釋放掉。
D. 因為鏈接中的節(jié)點都是存儲在堆上的,而每個節(jié)點之間都有一個指向前一個節(jié)點和后一個節(jié)點的指針,只要知道鏈表頭指針,就可以通過遍歷查找到任何一個節(jié)點。因此,鏈表不同于數(shù)組,數(shù)組是要連續(xù)的內(nèi)存存儲空間,才能保證以常量時間復(fù)雜度快速訪問任意元素;而鏈表不要求每個節(jié)點是連接,在堆上申請的內(nèi)存空間很難得到連續(xù)的,而且空間產(chǎn)生內(nèi)存碎片。
2、關(guān)于多線程和多進程編程,下面描述正確的是()
A. 多進程里,子進程可獲取父進程的所有堆和棧的數(shù)據(jù);而線程會與同進程的其他線程共享數(shù)據(jù),擁有自己的?臻g。
B. 線程因為有自己的獨立?臻g且共享數(shù)據(jù),所有執(zhí)行的開銷相對較大,同時不利于資源管理和保護。
C. 線程的通信速度更快,切換更快,因為他們在同一地址空間內(nèi)。
D. 線程使用公共變量/內(nèi)存時需要使用同步機制,因為他們在同一地址空間內(nèi)。
3、設(shè)兩個變量a=19;b=29;在不創(chuàng)建新實例的情況下使a、b的值互換
參考答案:
這道題要求不創(chuàng)建新的實例,只有a、b兩個變量,要交換這兩個變量的值,通常的做法是使用臨時變量來臨時存儲,但是現(xiàn)在要求不使用新的實例,那么有什么辦法呢?
方法就是通過位運算來操作:
a = a ^ b;
b = a ^ b;
a = a ^ b;
對于題目中的a = 19,也就是對應(yīng)二進制 00010011 ;而b=29,也就是對應(yīng)二進制00011101
第一步:a = 00010011 ^ 00011101 => 00001110,將a、b的值都記錄下來了
第二步:b = 00001110 ^ 00011101 => 00010011(值為19,也就是b得到了原來的a的值)
第三步:a = 00001110 ^ 00010011 => 00011101 (值為29,也就是a得到了原來的b的值)
注意,
符號表示按位異或。所謂按位異或是指對應(yīng)位置上的二進制數(shù)值相同為0,不同為1。
4、使用block時什么情況會發(fā)生引用循環(huán),如何解決?
參考答案:
筆者之前寫過這篇文章講了講開發(fā)中常見的內(nèi)存循環(huán)引用的案例:
iOS Block循環(huán)引用精講
5、為什么要序列化,對象序列化方式
參考答案:
筆者也不是很確定,iOS里的序列化是指歸檔、JSON序列化嗎?實際上歸檔也就是將對象轉(zhuǎn)換成XML、JSON序列化也就是將對象轉(zhuǎn)換data。
將對象JSON序列化:
NSLog(@"%s", __FUNCTION__);
NSDictionary *dict = @{@"key" : @"value",
@"key1" : @"value1",
@"key2" : @"value2"};
NSData *data = [NSJSONSerializationdataWithJSONObject:dictoptions:NSJSONWritingPrettyPrintederror:nil];
NSLog(@"%@", [[NSString alloc]initWithData:dataencoding:NSUTF8StringEncoding]);
將對象歸檔:需要遵守NSCoding協(xié)議,實現(xiàn)如下方法:
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoderencodeObject:self.titleforKey:@"title"];
}
如果所謂的序列化不是指這兩種,還請高人指點。
題照(后三題)
7、簡述如何處理UI與耗時操作的通信,有哪些方式及各自的優(yōu)缺點
參考答案:
將耗時的計算和IO操作放在子線程去處理,然后到主線程更新UI。優(yōu)點是
采用預(yù)加載方式,將耗時操作提前處理。優(yōu)點是可讓UI更流暢;缺點是內(nèi)存會增多,控制加載邏輯比較復(fù)雜。
采用延遲加載方式,將耗時操作而不立刻使用時,采用延遲加載。優(yōu)點是界面可提高流暢度;缺點是在需要顯示時還需要加載才能顯示,需要稍稍等待。
不知道說得合不合適,還請高人提出還有哪些方式?
8、如何優(yōu)化一個TableView
參考答案:
若高度一定,直接使用rowHeight屬性而不是使用heightForRowAtIndexPath方法,以減少調(diào)用的消耗。若高度是不固定的,heightForRowAtIndexPath所計算的高度應(yīng)該緩存起來,每次數(shù)據(jù)源發(fā)生變化時,比如刪除、插入、更新行都會重新請求所有的高度。若有100個行,就會有調(diào)用100次,因為將高度緩存起來是應(yīng)該的。同理,heightForHeaderInSection、heightForFooterInSection也應(yīng)該緩存起來。
不要在tableView:cellForRowAtIndexPath:中做太多的計算和IO操作,比如可以將需要的計算提前計算好、IO操作也提前計算好。它應(yīng)該直接調(diào)用來顯示就可以。
將計算行高的時間提前到從服務(wù)器獲取數(shù)據(jù)的時候,計算完了高度一并寫回數(shù)據(jù)庫或者通過轉(zhuǎn)型為model,將高度放到模型中。但是,最好將高度緩存起來。若一個model的數(shù)據(jù)有不同的狀態(tài),比如展開與收起狀態(tài),應(yīng)該也將高度都緩存起來。注意使用異步去計算,計算完成后再回到主線程顯示。
在設(shè)置顯示圖片時,不要直接設(shè)置UIImageView的contentMode屬性自動適應(yīng),圖片變形會計算transform,壓縮時會乘以一個矩陣,消耗性能。對于要求性能較高的app,應(yīng)該將得到的圖片經(jīng)過處理成UIImageView大小后再呈現(xiàn)。
不要將視圖的opaque屬性設(shè)置為NO,默認為YES,它表示不透明度。當opque為NO的時候,圖層的半透明取決于圖片和其本身合成的圖層為結(jié)果。
layer添加圓角是比較耗時的,這樣會離屏渲染,需要犧牲更多的性能。比如,圖片顯示有圓角時,可以通過core graphics來生成帶圓角的圖片等。
手動繪制cell。繪制cell不建議使用UIView,建議使用CALayer。 UIView的繪制是建立在CoreGraphic上的,其使用的是CPU。CALayer使用的是Core Animation,CPU、GPU都可以使用且由系統(tǒng)自動決定使用哪一個。UIView的繪制,使用的是自下向上的一層一層的繪制,而后渲染。Layer處理的是紋理,利用GPU的 Texture Cache和獨立的浮點數(shù)計算單元可以加速紋理的處理。
重用cell。防止重復(fù)的繪制,減少渲染次數(shù),可提高性能。
減少subviews的數(shù)量。盡量放在同一層view上顯示。
盡量少動態(tài)給cell添加子view。用addView給Cell動態(tài)添加View,可以初始化時就添加,然后通過hide來控制是否顯示。
想要更深入,不防看看大牛的文章吧: iOS 保持界面流暢的技巧
9、假設(shè)讓你設(shè)計一關(guān)于指示器的開源庫,請設(shè)想和設(shè)計框架的public API,并指出大概需要如何做、需要注意一些什么方面來使別人容易使用這個框架
參考答案:
設(shè)計API的基本準則是:
簡單易用
易擴展
單一功能
注意事項:
盡可能不要依賴第三方庫(除非不使用第三庫需要非常大的工作量)。
每個API的功能應(yīng)該是單一的,只做一件事。
注意性能、內(nèi)存問題
注意多個HUD顯示、關(guān)閉的切換問題
樣式問題
分析:
要想讓全工程使用起來非常方便,那最好的方式就是使用單例,比如SVProgressHUD就是通過單例的方式來操作的。將單例封閉在內(nèi)部,外部并不知道是單例,而外部的調(diào)用全是通過類方法的形式來調(diào)用,代碼調(diào)用是非常簡化的。使用單例的優(yōu)點是方便管理和調(diào)用。缺點就是一直占用內(nèi)存而不釋放。
當然,我們也可以通過正常的對象創(chuàng)建,在哪里使用就在哪里創(chuàng)建一個對象,自己來管理。這樣的方式在使用的地方不是那么方便,還需要再單獨進行一層封裝,以方便直接調(diào)用。但這種方式的好處就是在不需要使用的時候可以釋放掉;缺點就是如果同時創(chuàng)建了多個HUD來顯示時,需要調(diào)用者使用代碼邏輯來控制之前的顯示與隱藏或者切換文本等。
筆者覺得,使用單例方式更方便調(diào)用一些,外部也不用通過邏輯來管理多個HUD的顯示與隱藏問題,都封裝到內(nèi)部,由封裝庫的人來維護。