iOS 面試題彙總

猿來是你_發表於2019-05-14

1. 簡單介紹下NSURLConnection類及+ sendSynchronousRequest:returningResponse:error:– initWithRequest:delegate:兩個方法的區別?

答: NSURLConnection主要用於網路訪問,其中+ sendSynchronousRequest:returningResponse:error:是同步訪問資料,即當前執行緒會阻塞,並等待request的返回的response,而– initWithRequest:delegate:使用的是非同步載入,當其完成網路訪問後,會通過delegate回到主執行緒,並其委託的物件。

2. 在專案什麼時候選擇使用GCD,什麼時候選擇NSOperation

答: 專案中使用NSOperation的優點是NSOperation是對執行緒的高度抽象,在專案中使用它,會使專案的程式結構更好,子類化NSOperation的設計思路,是具有物件導向的優點(複用、封裝),使得實現是多執行緒支援,而介面簡單,建議在複雜專案中使用。 專案中使用GCD的優點是GCD本身非常簡單、易用,對於不復雜的多執行緒操作,會節省程式碼量,而Block引數的使用,會是程式碼更為易讀,建議在簡單專案中使用。

3. ViewController的didReceiveMemoryWarning怎麼被呼叫

答:[supper didReceiveMemoryWarning];

4. 寫一個setter方法用於完成@property(nonatomic, retain) NSString *name,寫一個setter方法用於完成@property(nonatomic, copy) NSString *name

- (void)setName:(NSString *)str{
    [str retain];
    [name release];
    name = str;
}
- (void)setName:(NSString *)str{
    id t = [str copy];
    [name release];
    name = t;
}
複製程式碼

5. 對於語句NSString *obj = [[NSData alloc] init]; obj在編譯時和執行時分別時什麼型別的物件?

答: 編譯時是NSString的型別;執行時是NSData型別的物件

6. Object C中建立執行緒的方法是什麼?如果在主執行緒中執行程式碼,方法是什麼?如果想延時執行程式碼、方法又是什麼?

答:執行緒建立有三種方法:使用NSThread建立、使用GCD的dispatch、使用子類化的NSOperation,然後將其加入NSOperationQueue;在主執行緒執行程式碼,方法是performSelectorOnMainThread,如果想延時執行程式碼可以用performSelector:onThread:withObject:waitUntilDone:

7. 淺複製和深複製的區別?

答:淺層複製:只複製指向物件的指標,而不復制引用物件本身。 深層複製:複製引用物件本身。

8. PerformSelecter

當呼叫 NSObject 的performSelecter:afterDelay:後,實際上其內部會建立一個 Timer 並新增到當前執行緒的 RunLoop 中。所以如果當前執行緒沒有 RunLoop,則這個方法會失效。 當呼叫performSelector:onThread:時,實際上其會建立一個 Timer 加到對應的執行緒去,同樣的,如果對應執行緒沒有 RunLoop 該方法也會失效。

9. 優化你是從哪幾方面著手?

一、首頁啟動速度 啟動過程中做的事情越少越好(儘可能將多個介面合併) 不在UI執行緒上作耗時的操作(資料的處理在子執行緒進行,處理完通知主執行緒重新整理節目) 在合適的時機開始後臺任務(例如在使用者指引節目就可以開始準備載入的資料) 二、頁面瀏覽速度 json的處理(iOS 自帶的NSJSONSerialization,Jsonkit,SBJson) 資料的分頁(後端資料多的話,就要分頁返回,例如網易新聞,或者 微博記錄) 資料壓縮(大資料也可以壓縮返回,減少流量,加快反應速度) 內容快取(例如網易新聞的最新新聞列表都是要快取到本地,從本地載入,可以快取到記憶體,或者資料庫,根據情況而定) 延時載入tab(比如app有5個tab,可以先載入第一個要顯示的tab,其他的在顯示時候載入,按需載入) 演算法的優化(核心演算法的優化,例如有些app 有個 聯絡人姓名用漢語拼音的首字母排序) 三、操作流暢度優化 Tableview 優化(tableview cell的載入優化) ViewController載入優化(不同view之間的跳轉,可以提前準備好資料) 四、資料庫的優化 資料庫設計上面的重構 查詢語句的優化 分庫分表(資料太多的時候,可以分不同的表或者庫) 五、伺服器端和客戶端的互動優化 客戶端儘量減少請求 服務端儘量做多的邏輯處理 伺服器端和客戶端採取推拉結合的方式(可以利用一些同步機制) 通訊協議的優化(減少報文的大小) 電量使用優化(儘量不要使用後臺執行) 六、非技術效能優化 產品設計的邏輯性(產品的設計一定要符合邏輯,或者邏輯儘量簡單,否則會讓程式設計師抓狂,有時候用了好大力氣,才可以完成一個小小的邏輯設計問題) 介面互動的規範(每個模組的介面的互動儘量統一,符合操作習慣) 程式碼規範(這個可以隱形帶來app 效能的提高,比如 用if else 還是switch ,或者是用!還是 ==) code review(堅持code Review 持續重構程式碼。減少程式碼的邏輯複雜度)

10. 什麼情況使用 weak 關鍵字,相比 assign 有什麼不同?

1.在ARC中,在有可能出現迴圈引用的時候,往往要通過讓其中一端使用 weak 來解決,比如: delegate 代理屬性。 2.自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak,如自定義 IBOutlet 控制元件屬性一般也使用 weak;當然,也可以使用strong。 IBOutlet連出來的檢視屬性為什麼可以被設定成weak? 答:因為父控制元件的subViews陣列已經對它有一個強引用。 不同點 assign 可以用非 OC 物件,而 weak 必須用於 OC 物件。 weak 表明該屬性定義了一種“非擁有關係”。在屬性所指的物件銷燬時,屬性值會自動清空(nil)。

11. 用@property宣告的 NSString / NSArray / NSDictionary 經常使用 copy 關鍵字,為什麼?如果改用strong關鍵字,可能造成什麼問題?

答:用 @property 宣告 NSString、NSArray、NSDictionary 經常使用 copy 關鍵字,是因為他們有對應的可變型別:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作(就是把可變的賦值給不可變的),為確保物件中的字串值不會無意間變動,應該在設定新屬性值時拷貝一份。 1.因為父類指標可以指向子類物件,使用 copy 的目的是為了讓本物件的屬性不受外界影響,使用 copy 無論給我傳入是一個可變物件還是不可物件,我本身持有的就是一個不可變的副本。 2.如果我們使用是 strong ,那麼這個屬性就有可能指向一個可變物件,如果這個可變物件在外部被修改了,那麼會影響該屬性。 總結:使用copy的目的是,防止把可變型別的物件賦值給不可變型別的物件時,可變型別物件的值傳送變化會無意間篡改不可變型別物件原來的值。

12. runtime如何實現weak變數的自動置nil?

runtime對註冊的類,會進行佈局,會將 weak 物件放入一個 hash 表中。用 weak 指向的物件記憶體地址作為 key,當此物件的引用計數為0的時候會呼叫物件的 dealloc 方法,假設 weak 指向的物件記憶體地址是a,那麼就會以a為key,在這個 weak hash表中搜尋,找到所有以a為key的 weak 物件,從而設定為nil。

13. runloop是什麼/runloop的概念?

runloop是執行緒相關的基礎框架的一部分。一個runloop就是一個事件處理的迴圈,用來不停的排程工作以及處理輸入事件。其實內部就是do-while迴圈,這個迴圈內部不斷地處理各種任務(比如Source,Timer,Observer)。使用runloop的目的是讓你的執行緒在有工作的時候忙於工作,而沒工作的時候處於休眠狀態。

14. UITableViewCell上有個UILabel,顯示NSTimer實現的秒錶時間,手指滾動cell過程中,label是否重新整理,為什麼?

這是否重新整理取決於timer加入到Run Loop中的Mode是什麼。Mode主要是用來指定事件在執行迴圈中的優先順序的,分為

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode):預設,空閒狀態
  • UITrackingRunLoopMode:ScrollView滑動時會切換到該Mode
  • UIInitializationRunLoopMode:run loop啟動時,會切換到該mode
  • NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合 蘋果公開提供的Mode有兩個
  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
  • NSRunLoopCommonModes(kCFRunLoopCommonModes) 在程式設計中:如果我們把一個NSTimer物件以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)新增到主執行迴圈中的時候, ScrollView滾動過程中會因為mode的切換,而導致NSTimer將不再被排程。當我們滾動的時候,也希望不排程,那就應該使用預設模式。但是,如果希望在滾動時,定時器也要回撥,那就應該使用common mode。

15. NStimer準嗎?談談你的看法?如果不準該怎樣實現一個精確的NSTimer?

不準;不準的原因如下 1、NSTimer加在main runloop中,模式是NSDefaultRunLoopMode,main負責所有主執行緒事件,例如UI介面的操作,複雜的運算,這樣在同一個runloop中timer就會產生阻塞。 2、模式的改變。主執行緒的 RunLoop 裡有兩個預置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。 當你建立一個 Timer 並加到 DefaultMode 時,Timer 會得到重複回撥,但此時滑動一個ScrollView時,RunLoop 會將 mode 切換為 TrackingRunLoopMode,這時 Timer 就不會被回撥,並且也不會影響到滑動操作。所以就會影響到NSTimer不準的情況。 PS:DefaultMode 是 App 平時所處的狀態,rackingRunLoopMode 是追蹤 ScrollView 滑動時的狀態。 方法: 1、在主執行緒中進行NSTimer操作,但是將NSTimer例項加到main runloop的特定mode(模式)中。避免被複雜運算操作或者UI介面重新整理所干擾self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(showTime) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; 2、在子執行緒中進行NSTimer的操作,再在主執行緒中修改UI介面顯示操作結果 -(void)timerMethod2 { NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil]; [thread start]; } -(void)newThread{ @autoreleasepool{ [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(showTime) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] run]; } }

16. NSOperation 相比於 GCD 有哪些優勢?

GCD是基於c的底層api,NSOperation屬於object-c類。ios 首先引入的是NSOperation,IOS4之後引入了GCD和NSOperationQueue並且其內部是用gcd實現的。 相對於GCD: 1、NSOperation擁有更多的函式可用,具體檢視api。 2、在NSOperationQueue中,可以建立各個NSOperation之間的依賴關係。 3、有kvo可以監測operation是否正在執行(isExecuted)、是否結束(isFinished),是否取消(isCanceld)。 4、NSOperationQueue可以方便的管理併發、NSOperation之間的優先順序。 GCD主要與block結合使用。程式碼簡潔高效。 GCD也可以實現複雜的多執行緒應用,主要是建立個個執行緒時間的依賴關係這類的情況,但是需要自己實現相比NSOperation要複雜。 具體使用哪個,依需求而定。 從個人使用的感覺來看,比較合適的用法是:除了依賴關係儘量使用GCD,因為蘋果專門為GCD做了效能上面的優化。

17. 如何訪問並修改一個類的私有屬性?

有兩種方法可以訪問私有屬性,一種是通過KVC獲取,一種是通過runtime訪問並修改私有屬性。

18. 如何捕獲異常?

1. 在app啟動時(didFinishLaunchingWithOptions),新增一個異常捕獲的監聽
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
2. 實現捕獲異常日誌並儲存到本地的方法
void UncaughtExceptionHandler(NSException *exception){
    //異常日誌獲取
    NSArray  *excpArr = [exception callStackSymbols];
    NSString *reason = [exception reason];
    NSString *name = [exception name];
    NSString *excpCnt = [NSString stringWithFormat:@"exceptionType: %@ \n reason: %@ \n stackSymbols: %@",name,reason,excpArr];
    //日常日誌儲存(可以將此功能單獨提煉到一個方法中)
    NSArray  *dirArr  = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *dirPath = dirArr[0];
    NSString *logDir = [dirPath stringByAppendingString:@"/CrashLog"];

    BOOL isExistLogDir = YES;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:logDir]) {
        isExistLogDir = [fileManager createDirectoryAtPath:logDir withIntermediateDirectories:YES attributes:nil error:nil];
    }
    if (isExistLogDir) {
        //此處可擴充套件
        NSString *logPath = [logDir stringByAppendingString:@"/crashLog.txt"];
        [excpCnt writeToFile:logPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    }
}
複製程式碼

19. Object-c的類可以多重繼承麼?可以實現多個介面麼?Category是什麼?重寫一個類的方式用繼承好還是分類好?為什麼?

答:Object-c的類不可以多重繼承;可以實現多個介面,通過實現多個介面可以完成C++的多重繼承;Category是類別,一般情況用分類好,用Category去重寫類的方法,僅對本Category有效,不會影響到其他類與原有類的關係。

20. 類別和類擴充套件的區別

答:category和extensions的不同在於,後者可以新增屬性。另外後者新增的方法是必須要實現的。 extensions可以認為是一個私有的Category。

21. 簡述記憶體分割槽情況

1).程式碼區:存放函式二進位制程式碼 2).資料區:系統執行時申請記憶體並初始化,系統退出時由系統釋放。存放全域性變數、靜態變數、常量 3).堆區:通過malloc等函式或new等操作符動態申請得到,需程式設計師手動申請和釋放 4).棧區:函式模組內申請,函式結束時由系統自動釋放。存放區域性變數、函式引數

22. 直接呼叫_objc_msgForward函式將會發生什麼?

_objc_msgForward是 IMP 型別,用於訊息轉發的:當向一個物件傳送一條訊息,但它並沒有實現的時候,_objc_msgForward會嘗試做訊息轉發。 直接呼叫_objc_msgForward是非常危險的事,如果用不好會直接導致程式Crash,但是如果用得好,能做很多非常酷的事。 一旦呼叫_objc_msgForward,將跳過查詢 IMP 的過程,直接觸發“訊息轉發”,如果呼叫了_objc_msgForward,即使這個物件確實已經實現了這個方法,你也會告訴objc_msgSend:“我沒有在這個物件裡找到這個方法的實現”

23. 對於Run Loop的理解

  • RunLoop,是多執行緒的法寶,即一個執行緒一次只能執行一個任務,執行完任務後就會退出執行緒。主執行緒執行完即時任務時會繼續等待接收事件而不退出。非主執行緒通常來說就是為了執行某一任務的,執行完畢就需要歸還資源,因此預設是不執行RunLoop的;
  • 每一個執行緒都有其對應的RunLoop,只是預設只有主執行緒的RunLoop是啟動的,其它子執行緒的RunLoop預設是不啟動的,若要啟動則需要手動啟動;
  • 在一個單獨的執行緒中,如果需要在處理完某個任務後不退出,繼續等待接收事件,則需要啟用RunLoop;
  • NSRunLoop提供了一個新增NSTimer的方法,可以指定Mode,如果要讓任何情況下都回撥,則需要設定Mode為Common模式;
  • 實質上,對於子執行緒的runloop預設是不存在的,因為蘋果採用了懶載入的方式。如果我們沒有手動呼叫[NSRunLoop currentRunLoop]的話,就不會去查詢是否存在當前執行緒的RunLoop,也就不會去載入,更不會建立。

24. runtime如何通過selector找到對應的IMP地址?(分別考慮類方法和例項方法)

1.每一個類物件中都一個物件方法列表(物件方法快取) 2.類方法列表是存放在類物件中isa指標指向的元類物件中(類方法快取) 3.方法列表中每個方法結構體中記錄著方法的名稱,方法實現,以及引數型別,其實selector本質就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應的方法實現. 4.當我們傳送一個訊息給一個NSObject物件時,這條訊息會在物件的類物件方法列表裡查詢 5.當我們傳送一個訊息給一個類時,這條訊息會在類的Meta Class物件的方法列表裡查詢

25. runtime 中,SEL 和 IMP 的區別

方法名 SEL – 表示該方法的名稱; IMP – 指向該方法的具體實現的函式指標,說白了IMP就是實現方法。

26.block底層實現

block本質是指向一個結構體的一個指標 執行時機制 比較高階的特性 純C語言 平時寫的OC程式碼 轉換成C語言執行時的程式碼 指令:clang -rewrite-objc main.m(可以列印驗證) 預設情況下,任何block都是在棧裡面的,隨時可能被回收 只要對其做一次copy操作 block的記憶體就會放在堆裡面 不會釋放 只有copy才能產生一個新的記憶體地址 所有地址會發生改變

27. TCP協議三次握手

TCP協議採用了三次握手策略。用TCP協議把資料包送出去後,TCP不會對傳送後的情況置之不理,它一定會向對方確認是否成功送達。握手過程中使用了TCP的標誌——SYN(synchronize)和ACK(acknowledgement)。傳送端首先傳送一個帶SYN標誌的資料包給對方。接收端收到後,回傳一個帶有SYN/ACK標誌的資料包以示傳達確認資訊。最後,傳送端再回傳一個帶ACK標誌的資料包,代表“握手”結束。

TCP協議三次握手示意圖

28. @property 的本質是什麼?

@property = ivar + getter + setter; “屬性” (property)有兩大概念:ivar(例項變數)、getter+setter(存取方法)

29. KVC的底層實現?

當一個物件呼叫setValue方法時,方法內部會做以下操作: 1). 檢查是否存在相應的key的set方法,如果存在,就呼叫set方法。 2). 如果set方法不存在,就會查詢與key相同名稱並且帶下劃線的成員變數,如果有,則直接給成員變數屬性賦值。 3). 如果沒有找到_key,就會查詢相同名稱的屬性key,如果有就直接賦值。 4). 如果還沒有找到,則呼叫valueForUndefinedKey:和setValue:forUndefinedKey:方法。 這些方法的預設實現都是丟擲異常,我們可以根據需要重寫它們。

30. ViewController生命週期

按照執行順序排列: 1). initWithCoder:通過nib檔案初始化時觸發。 2). awakeFromNib:nib檔案被載入的時候,會發生一個awakeFromNib的訊息到nib檔案中的每個物件。
3). loadView:開始載入檢視控制器自帶的view。 4). viewDidLoad:檢視控制器的view被載入完成。
5). viewWillAppear:檢視控制器的view將要顯示在window上。 6). updateViewConstraints:檢視控制器的view開始更新AutoLayout約束。 7). viewWillLayoutSubviews:檢視控制器的view將要更新內容檢視的位置。 8). viewDidLayoutSubviews:檢視控制器的view已經更新檢視的位置。 9). viewDidAppear:檢視控制器的view已經展示到window上。 10). viewWillDisappear:檢視控制器的view將要從window上消失。 11). viewDidDisappear:檢視控制器的view已經從window上消失。

31. 如何用GCD同步若干個非同步呼叫?(如根據若干個url非同步載入多張圖片,然後在都下載完成後合成一張整圖)

// 使用Dispatch Group追加block到Global Group Queue,這些block如果全部執行完畢,就會執行Main Dispatch Queue中的結束處理的block。
// 建立佇列組
dispatch_group_t group = dispatch_group_create();
// 獲取全域性併發佇列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{ /*載入圖片1 */ });
dispatch_group_async(group, queue, ^{ /*載入圖片2 */ });
dispatch_group_async(group, queue, ^{ /*載入圖片3 */ }); 
// 當併發佇列組中的任務執行完畢後才會執行這裡的程式碼
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 合併圖片
});
複製程式碼

32. dispatch_barrier_async(柵欄函式)的作用是什麼?

函式定義:dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
作用:
	1.在它前面的任務執行結束後它才執行,它後面的任務要等它執行完成後才會開始執行。
	2.避免資料競爭

// 1.建立併發佇列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
// 2.向佇列中新增任務
dispatch_async(queue, ^{  // 1.2是並行的
    NSLog(@"任務1, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"任務2, %@",[NSThread currentThread]);
});

dispatch_barrier_async(queue, ^{
    NSLog(@"任務 barrier, %@", [NSThread currentThread]);
});

dispatch_async(queue, ^{   // 這兩個是同時執行的
    NSLog(@"任務3, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"任務4, %@",[NSThread currentThread]);
});

// 輸出結果: 任務1 任務2 ——》 任務 barrier ——》任務3 任務4 
// 其中的任務1與任務2,任務3與任務4 由於是並行處理先後順序不定。
複製程式碼

附:我的部落格地址

相關文章