今日頭條的iOS高級開發崗第三面,下面記錄這次面試的回憶以作日後複習。
一、自我介紹
簡單介紹一下你自己吧
- 解析:簡單介紹下自己的名字,教育背景,現在的工作,做過的項目
二、自我介紹衍生的口頭問題
講講下你在你項目中做過的優化或者技術難點
- 解析:介紹了自己封裝的一個集picker,文本域的靈活展開的表視圖。這個視圖的數據源是json,怎麼轉成模型數組的?這個cell有哪些類型?展示的怎麼區分這些cell?這裏面有用過複用機制嗎?這些cell有實現過多重繼承嗎?
- 題外話:這種問題最好各人自己找問題講講,不多,提前準備一個你項目中非常擅長並熟悉的點,即可。
三、編程題:實現以下功能
- 編寫一個自定義類:Person,父類爲NSObject
- 解析:頭文件這樣寫
@interface Person:NSObject
- 該類有兩個屬性,外部只讀的屬性
name
,還有一個屬性age
- 解析:
name
的修飾符nonatomic
,strong
,readonly
。age
的修飾符nonatomic
,copy
。
- 爲該類編寫一個初始化方法
initWithName:(NSString *)nameStr
,並依據該方法參數初始化name
屬性。
- 解析:頭文件聲明該方法,實現文件實現該方法
- 如果兩個Person類的name相等,則認爲兩個Person相等
- 解析:重寫
isEqual
,這裏面涉及到了哈希函數在iOS中的應用。
四、由編程題衍生的口頭題目
4.1
題目: 怎樣實現外部只讀的屬性,讓它不被外部篡改
解析:
頭文件用readonly修飾並聲明該屬性。正常情況下,屬性默認是readwrite,可讀寫,如果我們設置了只讀屬性,就表明不能使用setter方法。在.m文件中不能使用
self.ivar = @"aa";
只能使用實例變量_ivar = @"aa";
,而外界想要修改只讀屬性的值,需要用到kvc賦值[object setValue:@"mm" forKey:@"ivar"];
。實現文件裏面聲明私有屬性,並在頭文件在protocol裏面規定該屬性就可以了,外部通過protocol獲取,這樣還可以達到隱藏成員的效果。
4.2
題目: nonatomic是非原子操作符,爲什麼要這樣,atomic爲什麼不行?有人說能atomic耗內存,你覺得呢?保讀寫安全嗎,能保證線程安全嗎?有的人說atomic並不能保證線程安全,你覺得他們的出發點是什麼,你認同這個說法嗎?
- 關於爲什麼用nonatomic
如果該對象無需考慮多線程的情況,請加入這個屬性修飾,這樣會讓編譯器少生成一些互斥加鎖代碼,可以提高效率。
而atomic這個屬性是爲了保證程序在多線程情況下,編譯器會自動生成一些互斥加鎖代碼,避免該變量的讀寫不同步問題。
atomic 和 nonatomic 的區別在於,系統自動生成的 getter/setter 方法不一樣。如果你自己寫 getter/setter,那 atomic/nonatomic/retain/assign/copy 這些關鍵字只起提示作用,寫不寫都一樣。
- 關於atomic語nonatomic的實現
蘋果的官方文檔 有解釋,下面我們舉例子解釋一下背後的原理。
- 至於 nonatomic 的實現
//@property(nonatomic, retain) UITextField *userName;
//系統生成的代碼如下:
- (UITextField *) userName {
return userName;
}
- (void) setUserName:(UITextField *)userName_ {
[userName_ retain];
[userName release];
userName = userName_;
}
- 而 atomic 版本的要複雜一些:
//@property(retain) UITextField *userName;
//系統生成的代碼如下:
- (UITextField *) userName {
UITextField *retval = nil;
@synchronized(self) {
retval = [[userName retain] autorelease];
}
return retval;
}
- (void) setUserName:(UITextField *)userName_ {
@synchronized(self) {
[userName release];
userName = [userName_ retain];
}
}
簡單來說,就是 atomic 會加一個鎖來保障多線程的讀寫安全,並且引用計數會 +1,來向調用者保證這個對象會一直存在。假如不這樣做,如有另一個線程調 setter,可能會出現線程競態,導致引用計數降到0,原來那個對象就釋放掉了。
- 關於atomic和線程安全
atomic修飾的屬性只能說是讀/寫安全的,但並不是線程安全的,因爲別的線程還能進行讀寫之外的其他操作。線程安全需要開發者自己來保證。
- 關於修飾符失效
因爲atomic修飾的屬性靠編譯器自動生成的get和set方法實現原子操作,如果重寫了任意一個,atomic關鍵字的特性將失效
4.3
題目: 你在初始化的方法中爲什麼將參數賦給_name,爲什麼這樣寫就能訪問到屬性聲明的示例變量?
- xcode4 之後,編輯器添加了自動同步補全功能,只需要在 h 文件中定義 property,在編譯期m文件會自動補全出
@synthesize name = _name
的代碼,不再需要手寫,避免了“體力代碼”的手動編碼
4.4
題目: 初始化方法中的_name是在什麼時候生成的?分配內存的時候嗎?還是初始化的時候?
成員變量存儲在堆中(當前對象對應的堆得存儲空間中) ,不會被系統自動釋放,只能有程序員手動釋放。
編譯的時候自動的爲name屬性生成一個實例變量_name
如果m中什麼都不寫,xcode會默認在編譯期爲 market 屬性,補全成 @synthesize market = _market,實例變量名爲 _market;
如果m中指定了 @synthesize market,xcode會認爲你手動指定了實例變量名爲 market ,編譯期補全成:@synthesize market = market,實例變量名爲 market。
4.5
題目: 作爲return的self是在上面時候生成的?
是在alloc時候分配內存,在init初始化的。
一種典型帶成員變量初始化參數的代碼爲:
- (instancetype)initWithDistance:(float)distance maskAlpha:(float)alpha scaleY:(float)scaleY direction:(CWDrawerTransitionDirection)direction backImage:(UIImage *)backImage {
if (self = [super init]) {
_distance = distance;
_maskAlpha = alpha;
_direction = direction;
_backImage = backImage;
_scaleY = scaleY;
}
return self;
}
4.6
題目: 爲什麼用copy,哪些情況下用copy,爲什麼用copy?
- 可變的類,例如NSArray、NSDictionary、NSString最好用copy來修飾,它們都有對應的Mutable類型。
- copy修飾屬性的本質是爲了專門設置屬性的setter方法,例如,
setName:
傳進一個nameStr參數,那麼有了copy修飾詞後,傳給對應的成員變量_name的其實是[nameStr copy];
。 - 爲什麼要這樣?如果不用copy會有什麼問題?例如,
strong
修飾的NSString類型的name屬性,傳一個NSMutableString:
NSMutableString *mutableString = [NSMutableString stringWithFormat:@"111"];
self.myString = mutableString;
在strong
修飾下,把可變字符串mutableString賦值給myString後,改變mutableString的值導致了myString
值的改變。而copy
修飾下,卻不會有這種變化。
在strong
修飾下,可變字符串賦值給myString後,兩個對象都指向了相同的地址。而copy
修飾下,myString和mutableString指向了不同地址。這也是爲什麼strong修飾下,修改mutableString引起myString變化,而copy修飾下則不會。
- 總之,當修飾可變類型的屬性時,如NSMutableArray、NSMutableDictionary、NSMutableString,用strong。當修飾不可變類型的屬性時,如NSArray、NSDictionary、NSString,用copy。
4.7
題目: 分類中添加實例變量和屬性分別會發生什麼,編譯時就報錯嗎,還是什麼時候會發生問題?爲什麼
編譯的時候,不能添加實例變量,否則報錯。
編譯的時候可以添加屬性,但是一旦在創建對象後爲屬性賦值或者使用這個屬性的時候,程序就崩潰了,奔潰的原因也很簡單,就是找不到屬性的set/get方法。
那我們就按照這個流程來,在類別中爲屬性添加set/get方法,在set方法裏面賦值的時候找不到賦值的對象,也就是說系統沒有爲我們生成帶下劃線的成員變量,沒生成我們就自己加。但是通過傳統實例變量的方式,一加就報錯。看來這纔是類別不能擴展屬性的根本原因。
- 那麼怎麼辦?通過runtime的關聯對象。
五、另外聊到的實際開發問題
- 你平時有做過優化內存的哪些工作?怎樣避免內存消耗的大戶?
- 可以參考這個https://www.2cto.com/kf/201505/401059.html
- 關於TableView的優化可以參考https://www.jianshu.com/p/9cd9382c0a5b
- 你怎樣實現線程安全的?這些線程安全的辦法和atomic有什麼不一樣?atomic的實現機制是怎樣
- 可以參考YYKit的多線程安全機制,它是用MUTEX實現線程鎖的https://github.com/ibireme/YYKit
- 關於鎖的實現原理可參考https://www.jianshu.com/p/a33959324cc7
- 其它辦法,例如隊列
- 關於atomic的實現機制前面有討論,就是加鎖。
- 如果不加atomic會怎麼樣呢?當內存長度大於地址總線的時候,例如在64位系統下內存中讀取無法像bool等純量類型原子性完成,可能會在讀取的時候發生寫入,從造成異常情況。atomic還會使用memory barrier能夠保證內存操作的順序,按照我們代碼的書寫順序來。
資料推薦
如果你正在跳槽或者正準備跳槽不妨動動小手,添加一下咱們的交流羣1012951431來獲取一份詳細的大廠面試資料爲你的跳槽多添一份保障。