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