招一個靠譜的iOS實習生(附參考答案)
以下是我列出來的能夠幫助大家招到一個 靠譜的iOS實習生 需要掌握的點,再次說明下情況:
- 此份題適用於電面和face to face,更加偏向於電面;
- 能夠較為流暢的說到每道題的點上,基本上可以認為是掌握了;
- 考慮到電面過程中,對被電面者心理素質考驗非常大,所以,我本人抵制電面過程中考演算法(這是一個流氓行為)此套題不涉及任何關於演算法方面知識。若有此需求,推薦找專門的在線OJ進行測評。
概念部分
struct和class的區別?
- struct中不能定義函數(針對面向過程語言,例如C)// 可不用
- 使用大括弧進行初始化 class和struct如果定義了構造函數,就不能用大括弧初始化,若沒有,則struct可以,class只有在所有成員變數均為public時才行。 // 可不用
- 默認訪問許可權 class默認成員訪問許可權為private;struct默認訪問你許可權為public。 // 重點
- 繼承方式 class默認private;struct默認public。 // 重點
說出以下指針的含義:
int **a : 指向一個指針的指針,該指針指向一個整數。int *a[10] : 指向一個有10個指針的數組,每個指針指向一個整數。int (*a)[10] : 指向一個有10個整數數組的指針。int (*a)(int) : 指向一個函數的指針,該函數有一個整數參數,並返回一個整數。
int 和 NSInterger 的區別:
- 以C語言舉例,int和long的位元組數和當前操作系統中的指針所佔位數是相等的,也就是說,long的長度永遠 ≥ int,並且我們需要去考慮此時是使用int還是long比較合適,會不會因為一時疏忽選擇了int而導致位數不夠造成溢出。
- 而在OC中使用 NSInterger ,蘋果對其進行了一個宏定義的判斷(cmd+滑鼠左鍵進去看吧),這個宏定義會自動判斷當前App運行的硬體環境,到底是32位機還是64位機等等,從而自動返回最大的類型,而不用我們去思考此時到底應該是用int還是long。
深拷貝和淺拷貝的區別:
- 淺拷貝:又稱「指針拷貝」。不增加新內存,只增加一個指針指向原來的內存區域。
- 深拷貝:又稱「內容拷貝」。同時拷貝指正和指針所指向的內存,新增指針指向新增內存。
- // 可對OC中的可變對象和不可變對象做拓展,此問題只是單純的概念。
內存中的區域是怎麼劃分的?
- 用之前做的一張圖進行描述:
語言部分
nil、Nil、NULL和NSNULL的區別:
- nil: 把對象置空,置空後是一個空對象且完全從內存中釋放;
- Nil: 用nil的地方均可用Nil替換,Nil表示置空一個類;
- NULL: 表示把一個指針置空。(空指針)
- NSNULL: 把一個OC對象置空,但想保留其容器(大小)。
category和extension的區別:
- category:為已知類增加新方法。
- 新增方法被子類集成;
- 新增的方法比原有類具備更高的優先順序,且不可重名,防止被覆蓋;
- 不能增加成員變數。
- extension: 為當前類增加私有變數和私有方法,添加的方法是必須實現的。
@Property關鍵詞及其相關關鍵字的理解:
- 根據被修改的可能性,、@Property中關鍵字的排列推薦為:原子性、讀寫性、內存管理特性;
- 原子性: automatic和nonautomatic。決定了該屬性是否為原子性的,即在多線程的操作中,不能被其它線程打斷的特性,一旦使用了該變數的操作不能被完整執行時,將會回到該變數操作之前的狀態,但原子性即automatic因為是原語操作(保證setter/getter的原語執行),會損耗性能,在iOS開發中一般不用,而在macOS開發中隨意。
- 讀寫性: readOnly和readWrite。默認為readWrite,編譯器會幫助生成serter/getter方法,而readOnly只會幫助生成getter方法。 // 此處可拓展,非要修改readOnly修飾的變數怎麼辦,可用KVC,又可繼續拓展KVC相關知識。
- 內存管理特性: assign、weak、strong、unsafe_unretained。
- assign:一般用於值類型,比如int、BOOL等(還可用於修飾OC對象);
- weak:用於修飾引用類型(弱引用,只能修飾OC對象);
- strong:用於修飾引用類型(強引用);
- unsafeunretained:只用於修飾引用類型(弱引用),與weak的區別在於,被unsafeunretained修飾的對象被銷毀後,其指針並不會被自動置空,此時指向了一個野地址。
OC中如何定義一個枚舉?
- 在OC中定義一個枚舉有三種做法:
- 因為OC是兼容C的,所以可以使用C語言風格的enum進行定義。
- 使用
NS_ENUM
宏進行定義; - 使用
NS_OPTIONS
宏進行定義;
NS_ENUM
為定義通用性枚舉,只能單選,NS_OPTIONS
為定義位移枚舉,可多選。 // 枚舉為啥要這麼分?因為涉及到是否使用C++模式進行編譯有關。
Block和函數的關係(對Block的理解)?
- Block與函數指針非常類似,但Block能夠訪問函數以外、詞法作用域以外的外部變數的值;
- Block不僅實現了函數的功能,還攜帶了函數的執行環境;
- Block實際上是指向結構體的指針;(可參考這篇文章)
- Block會把進入其內部的基本數據類型變數當做常量處理。】
- Block執行的是一個回調,並不知道其中的對象合適被釋放,所以為了防止在使用對象之前就被釋放掉了,會自動給其內部所使用的對象進行retain一次。
- Block使用
copy
修飾符進行修飾,且不能使用retain
進行修飾,因為retain
只是進行了一次回調,但block的內存還是放在了棧空間中,在棧上的變數隨時會被系統回收,且Block在創建的時候內存默認就已經分配在棧空間中,其本身的作用域限於其創建時,一旦在超出其創建時的作用域之外使用,則會導致程序的崩潰,故使用copy
修飾,使其拷貝到堆空間中,block有時還會用到一些本地變數,只有將其copy到堆空間中,才能使用這些變數。
deletegate需要weak修飾的原因?
- 以圖說明,圖中所表示的是VC對tableView的持有,如果此時的tableView.deletegate對VC也是強引用,會導致循環引用,同時也給了我們敲了警鐘,當出現兩個對象都是強引用時,萬分小心!
解釋一下這段代碼的輸出:
- 簡寫:
Computer : NSObjectMac : Computer@implementation MacNSLog(@"%@", [self class]);NSLog(@"%@", [super class])@end
- 二者都會輸出Mac。
- [self class]:當使用self調用方法時,從當前類方法列表中找,若沒有則再去父類中找。調用[self class]時,會轉化成
objc_msgSend
函數,其定義為id objc_msgSend(id self, SEL op, ...)
,第一個參數是Mac實例,但其並無-(Class)class
方法,此時去父類Computer中尋找,發現也沒有,再去其父類NSObject
中找,找到了!返回的就是self
其本身,可猜測其方法實現如下:
- (Class)class {
return object_getClass(self);}- [super class]:從父類方法列表中開始找,調用父類方法。當調用[super class]時,轉換成
objc_msgSendSuper
函數,其定義為id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
,而struct objc_super
結構體的定義為:
struct objc_super {
__unsafe_unretained id receiver;__unsafe_unretained Class super_class;
}所以轉換成objc_msgSendSuper
函數後,第一步要先去構造objc_super
結構體,結構的第一個成員receiver
就是self
,第二個成員是(id)class_getSuperclass(object_getClass("Mac"))
,該函數輸出的結果為super_class值,即Computer
,第二步,則去Computer
類中去找- (Class)class
,發現並未找到,接著去NSObject中找,找到了!最後是使用了objc_msgSend(objc_super->receiver, @selector(class))
去調用了,這個時候已經跟之前的[self class]調用輸出結果重複了,返回結果還是Mac
。
iOS部分
UITableView性能調優的方法:
- Cell重用:
- 數據源方法優化:創建一個靜態變數重用ID,例如:
static NSString *cellID = @"cellID";
防止因為調用次數過多,static保證只創建一次,提高性能(感覺性能的提升可以忽略不記emmm) - 緩存池獲取可重用Cell的兩個方法:
dequeueReusableCellWithIdentifier:(NSString *)ID
會查詢可重用Cell,若註冊了原型Cell則能夠查詢到,否則為nil,故需要先判斷if(cell == nil)
dequeueReusableCellWithIdentifier:(NSString *)ID indexPath:(NSIndexPath *)indexPath
,使用之前必須通過SB/class進行可重用Cell的註冊(registerNib/registerClass),不需要判斷nil,一定會返回cell,若緩衝區Cell不存在,會使用原型Cell重新實例化一個新Cell。
- 盡量使用一種類型的Cell:能夠減少代碼量,減小Nib文件的數量;保證只有一種類型的Cell,實際上App運後只有N個Cell,但若有M種Cell,則實際上運行最多卻可能會是MxN 個。
- 善用hidden隱藏subview:把所有不同類型的view都定義好,通過cell的枚舉類型變數及hidden顯示/隱藏不同類型的內容,因為在實際快速滑動中,單純的顯示/隱藏subview比實時創建快得多。
- 提前計算並緩存Cell的高度。如果我們不預估行高,則優先調用
heightForRowAtIndexPath
獲取每個Cell即將顯示的高度,實際上就是要確定總的tableView.contenSize,最後才又接著調用cellForRowAtIndexPath
,可以建一個frame模型,保存下提前計算好的cell高度。 - 非同步繪製:這是目前最火的tableView性能調優方法,新浪微博是這麼做的,可以使用
ASDK
這個庫進行。 - tableView滑動時,按需載入:識別tableView靜止或減速滑動結束後,非同步載入,在快速滑動過程中,只按需載入目標方位內的Cell。
- 避免大量使用圖片縮放、顏色漸變、透明圖層、CALayer特效(陰影)等操作,盡量顯示大小剛好合適的圖片資源。
內存優化方案:
- 首先ARC。但要注意防止循環引用,避免內存泄露;
- 懶載入。延遲創建對象,用時再創建;
- 復用。比如tableView、collectionView單元格的復用;
- 巧妙使用單例,而不是全都使用單例!
單例的寫法?
static User *user;+ (User *)shareInstance { if (user == nil) { @synchronized(self) { // 加鎖 user = [User alloc] init]; } } return user;}+ (User *)shareInstance { static dispatch_onec_t onecToken; dispatch_onece(&onceToken, ^{ user = [User alloc] init; }) return user;}
iOS的遠程推送過程?
- 以圖講解:
iOS中多線程的概念:(單問概念)
- 多線程優點:提高程序執行效率。缺點:開啟線程需要一定的內存控制項。
- 同步和非同步:決定了要不要開啟新的線程。同步:在當前線程中執行任務,不具備開啟新線程能力;非同步:在新線程中執行任務,具備開啟新線程的能力。
- 並行和串列:決定了任務的執行方式。並行:多個任務並發(同時)執行,類似迅雷多任務同時下載;串列:一個任務執行完畢後,再執行下一個任務,類似一個一個下載。
- 重點: 必須要明確iOS中只有一個主線程——UI線程,且不可將耗時任務放在主線程執行,否則會造成卡頓。
總結:再次說明,實際電面過程中,本套題只可作為參考,而且在電面過程中會有追問和等待的過程,所以最佳電面時間為三十分鐘內最佳,嫌短?記好了!這招的是實習生,而且這還是電面!!!別學啥大公司的戾氣,一個實習生的電面你要搞一個小時甚至快兩個小時,沒有必要。如果是face to face隨你問一兩個小時,但重點是這是電面!不要把這種「隔空喊話」的面試方式作為考核一個人是否具備對應的工作能力的最終標準,除非能夠確定以後的工作方式就是「隔空喊話」和「隔空擼碼」。
也不推薦把本套題中的內容作為一面,一面應該是作為了解應聘者的職業狀況,基礎水平(也是就是邏輯能力),推薦本套題作為二面,涵蓋基本的iOS開發基礎知識,而且時間能夠較好的把握在三十分鐘內,三面應該側重項目實際情況,而且最好face to face。四面就HR面了吧,盡量不要有五面了,太難受了。其實三面是最佳的。
記住!不要套題,而應該是觸類旁通,引出其它問題。
推薦閱讀: