1. 程式人生 > 實用技巧 >iOS今日頭條第3輪面試回憶

iOS今日頭條第3輪面試回憶

今日頭條的iOS高階開發崗第三面,下面記錄這次面試的回憶以作日後複習。

一、自我介紹

簡單介紹一下你自己吧
  • 解析:簡單介紹下自己的名字,教育背景,現在的工作,做過的專案

二、自我介紹衍生的口頭問題

講講下你在你專案中做過的優化或者技術難點
  • 解析:介紹了自己封裝的一個集picker,文字域的靈活展開的表檢視。這個檢視的資料來源是json,怎麼轉成模型陣列的?這個cell有哪些型別?展示的怎麼區分這些cell?這裡面有用過複用機制嗎?這些cell有實現過多重繼承嗎?
  • 題外話:這種問題最好各人自己找問題講講,不多,提前準備一個你專案中非常擅長並熟悉的點,即可。

三、程式設計題:實現以下功能

  1. 編寫一個自定義類:Person,父類為NSObject

  • 解析:標頭檔案這樣寫@interface Person:NSObject
  1. 該類有兩個屬性,外部只讀的屬性name,還有一個屬性age

  • 解析:name的修飾符nonatomicstrongreadonlyage的修飾符nonatomiccopy
  1. 為該類編寫一個初始化方法initWithName:(NSString *)nameStr,並依據該方法引數初始化name屬性。

  • 解析:標頭檔案宣告該方法,實現檔案實現該方法
  1. 如果兩個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 屬性,補全成@synthesizemarket = _market,例項變數名為 _market;
  • 如果m中指定了@synthesizemarket,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方法裡面賦值的時候找不到賦值的物件,也就是說系統沒有為我們生成帶下劃線的成員變數,沒生成我們就自己加。但是通過傳統例項變數的方式,一加就報錯。看來這才是類別不能擴充套件屬性的根本原因。


image

  • 那麼怎麼辦?通過runtime的關聯物件。

五、另外聊到的實際開發問題

  1. 你平時有做過優化記憶體的哪些工作?怎樣避免記憶體消耗的大戶?
  1. 你怎樣實現執行緒安全的?這些執行緒安全的辦法和atomic有什麼不一樣?atomic的實現機制是怎樣
  • 可以參考YYKit的多執行緒安全機制,它是用MUTEX實現執行緒鎖的https://github.com/ibireme/YYKit
  • 關於鎖的實現原理可參考
  • 其它辦法,例如佇列
  • 關於atomic的實現機制前面有討論,就是加鎖。
  • 如果不加atomic會怎麼樣呢?當記憶體長度大於地址匯流排的時候,例如在64位系統下記憶體中讀取無法像bool等純量型別原子性完成,可能會在讀取的時候發生寫入,從造成異常情況。atomic還會使用memory barrier能夠保證記憶體操作的順序,按照我們程式碼的書寫順序來。

資料推薦

如果你正在跳槽或者正準備跳槽不妨動動小手,新增一下咱們的交流群1012951431來獲取一份詳細的大廠面試資料為你的跳槽多添一份保障。