iOS面試·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 屬性,補全成 @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的關聯對象。

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

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

資料推薦

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章