1. 程式人生 > >iOS 最全的面試題~[有答案]

iOS 最全的面試題~[有答案]

//聯絡人:石虎 QQ:1224614774 暱稱:嗡嘛呢叭咪哄

 QQ群:807236138  群稱:iOS 技術交流學習群

/*

 ----

多執行緒、特別是NSOperation GCD的內部原理。

執行時機制的原理和運用場景。

 SDWebImage的原理。實現機制。如何解決TableView卡的問題。

 block和代理的,通知的區別。block的用法需要注意些什麼。

 strongweakretainassigncopy nomatic 等的區別。

設計模式,mvc,單利,工廠,代理等的應用場景。

單利的寫法。在單利中建立陣列應該注意些什麼。

 NSString 的時候用copystrong的區別。

響應值鏈。

 NSTimer 在子執行緒中應該手動建立NSRunLoop,否則不能迴圈執行。

 UIScrollViewNSTimer組合做迴圈廣告圖輪播的時候有一個屬性可以控制當上下滾動tableview的時候廣告輪播圖依然正常滾動。

 Xcode最新的自動佈局。。。這個很多公司都用。儘量自學下。

 git ,和svn的用法。。。git的幾個命令簡單的記下。。。

友盟報錯可以查到具體某一行的錯誤,原理是什麼。

 Instrument  可以檢測電池的耗電量、和記憶體的消耗。的用法。

動畫CABaseAnimation CAKeyAni….  CATrans…..  CAGoup….   等熟悉。。

 ARC

的原理。

自己寫過什麼自定義控制元件就最好了。。

 ———————————————回答好上面的足夠了-------------------------------------

 __block__weak修飾符的區別其實是挺明顯的:

 1.__block不管是ARC還是MRC模式下都可以使用,可以修飾物件,還可以修飾基本資料型別。

 2.__weak只能在ARC模式下使用,也只能修飾物件(NSString),不能修飾基本資料型別(int)。

 3.__block物件可以在block中被重新賦值,__weak不可以。

 tableView 滑動卡的問題主要是因為:從快取中或者是從本地讀取圖片給UIImage

的時候耗費的時間。需要把下面的兩句話放到子執行緒裡面:

 NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]]; //得到影象資料

 UIImage *image = [UIImage imageWithData:imgData];

UIImage賦值給圖片的時候在主執行緒。

子執行緒不能更新UI所有的UI跟新都是主執行緒執行了。手指滑動螢幕了。或者螢幕的某個方法執行了。

子執行緒裡面加入NSTimer的時候需要手動新增NSRunloop  否則不能迴圈。

單利裡面新增 NSMutableArray的時候,防止多個地方對它同時便利和修改的話,需要加原子屬性。並且用strong,,,並且寫一個遍歷和修改的方法。加上鎖。   Lock   UnLock

 __weak ViewController*  weakSelf = self;

 GCD裡面用 __weak防止記憶體釋放不了,迴圈引用。

二、SDWebImage內部實現過程

入口 setImageWithURL:placeholderImage:options:會先把 placeholderImage顯示,然後 SDWebImageManager根據 URL開始處理圖片。

進入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交給 SDImageCache 從快取查詢圖片是否已經下載 queryDiskCacheForKey:delegate:userInfo:.

先從記憶體圖片快取查詢是否有圖片,如果記憶體中已經有圖片快取,SDImageCacheDelegate回撥 imageCache:didFindImage:forKey:userInfo: SDWebImageManager

 SDWebImageManagerDelegate 回撥 webImageManager:didFinishWithImage: UIImageView+WebCache等前端展示圖片。

如果記憶體快取中沒有,生成 NSInvocationOperation新增到佇列開始從硬碟查詢圖片是否已經快取。

根據 URLKey在硬碟快取目錄下嘗試讀取圖片檔案。這一步是在 NSOperation進行的操作,所以回主執行緒進行結果回撥 notifyDelegate:

如果上一操作從硬碟讀取到了圖片,將圖片新增到記憶體快取中(如果空閒記憶體過小,會先清空記憶體快取)。SDImageCacheDelegate回撥 imageCache:didFindImage:forKey:userInfo:。進而回調展示圖片。

如果從硬碟快取目錄讀取不到圖片,說明所有快取都不存在該圖片,需要下載圖片,回撥 imageCache:didNotFindImageForKey:userInfo:

共享或重新生成一個下載器 SDWebImageDownloader開始下載圖片。

圖片下載由 NSURLConnection來做,實現相關 delegate來判斷圖片下載中、下載完成和下載失敗。

 connection:didReceiveData: 中利用 ImageIO做了按圖片下載進度載入效果。

 connectionDidFinishLoading: 資料下載完成後交給 SDWebImageDecoder做圖片解碼處理。

圖片解碼處理在一個 NSOperationQueue完成,不會拖慢主執行緒 UI。如果有需要對下載的圖片進行二次處理,最好也在這裡完成,效率會好很多。

在主執行緒 notifyDelegateOnMainThreadWithInfo:宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo:回撥給 SDWebImageDownloader

 imageDownloader:didFinishWithImage: 回撥給 SDWebImageManager告知圖片下載完成。

通知所有的 downloadDelegates下載完成,回撥給需要的地方展示圖片。

將圖片儲存到 SDImageCache中,記憶體快取和硬碟快取同時儲存。寫檔案到硬碟也在以單獨 NSInvocationOperation完成,避免拖慢主執行緒。

 SDImageCache 在初始化的時候會註冊一些訊息通知,在記憶體警告或退到後臺的時候清理記憶體圖片快取,應用結束的時候清理過期圖片。

 SDWI 也提供了 UIButton+WebCache MKAnnotationView+WebCache,方便使用。

 SDWebImagePrefetcher 可以預先下載圖片,方便後續使用。

從上面流程可以看出,當你呼叫setImageWithURL:方法的時候,他會自動去給你幹這麼多事,當你需要在某一具體時刻做事情的時候,你可以覆蓋這些方法。比如在下載某個圖片的過程中要響應一個事件,就覆蓋這個方法:

 1

 2

 3

 4

 5

 6

 7

 8

 9

 10

 11

 //覆蓋方法,指哪打哪,這個方法是下載imagePath2的時候響應

 SDWebImageManager *manager = [SDWebImageManager sharedManager];

 [manager downloadImageWithURL:imagePath2 options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {

 NSLog(@"顯示當前進度");

 } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {

 NSLog(@"下載完成");

 }];

對於初級來說,用sd_setImageWithURL:的若干個方法就可以實現很好的圖片快取。

 UIButton 的父類是UIControl  UIControl的父類是UIView UIView的父類是 UIResponder

 http狀態嗎302是請求重定向。500以上是伺服器錯誤。400以上是請求連結錯誤或者找不到伺服器。200以上是正確。100以上是請求接受成功。

 HTTP Keep-Alive詳解[]

 HTTP是一個請求<->響應模式的典型範例,即客戶端向伺服器傳送一個請求資訊,伺服器來響應這個資訊。在老的HTTP版本中,每個請求都將被建立一個新的客戶端->伺服器的連線,在這個連線上傳送請求,然後接收請求。這樣的模式有一個很大的優點就是,它很簡單,很容易理解和程式設計實現;它也有一個很大的缺點就是,它效率很低,因此Keep-Alive被提出用來解決效率低的問題。

 Keep-Alive功能使客戶端到伺服器端的連線持續有效,當出現對伺服器的後繼請求時,Keep-Alive功能避免了建立或者重新建立連線。市場上的大部分Web伺服器,包括iPlanetIISApache,都支援HTTP Keep-Alive。對於提供靜態內容的網站來說,這個功能通常很有用。但是,對於負擔較重的網站來說,這裡存在另外一個問題:雖然為客戶保留開啟的連接有一定的好處,但它同樣影響了效能,因為在處理暫停期間,本來可以釋放的資源仍舊被佔用。當Web伺服器和應用伺服器在同一臺機器上執行時,Keep- Alive功能對資源利用的影響尤其突出。此功能為HTTP 1.1預設的功能,HTTP 1.0加上Keep-Aliveheader也可以提供HTTP的持續作用功能。

 Keep-Alive: timeout=5, max=100

 timeout:過期時間5秒(對應httpd.conf裡的引數是:KeepAliveTimeout),max是最多一百次請求,強制斷掉連線

就是在timeout時間內又有新的連線過來,同時max會自動減1,直到為0,強制斷掉。見下面的四個圖,注意看Date的值(前後時間差都是在5秒之內)!

 HTTP/1.0

HTTP/1.0版本中,並沒有官方的標準來規定Keep-Alive如何工作,因此實際上它是被附加到HTTP/1.0協議上,如果客戶端瀏覽器支援Keep-Alive,那麼就在HTTP請求頭中新增一個欄位 Connection: Keep-Alive,當伺服器收到附帶有Connection: Keep-Alive的請求時,它也會在響應頭中新增一個同樣的欄位來使用Keep-Alive。這樣一來,客戶端和伺服器之間的HTTP連線就會被保持,不會斷開(超過Keep-Alive規定的時間,意外斷電等情況除外),當客戶端傳送另外一個請求時,就使用這條已經建立的連線

 HTTP/1.1

HTTP/1.1版本中,官方規定的Keep-Alive使用標準和在HTTP/1.0版本中有些不同,預設情況下所在HTTP1.1中所有連線都被保持,除非在請求頭或響應頭中指明要關閉:Connection: Close  ,這也就是為什麼Connection: Keep-Alive欄位再沒有意義的原因。另外,還添加了一個新的欄位Keep-Alive:,因為這個欄位並沒有詳細描述用來做什麼,可忽略它

 Not reliable(不可靠)

 HTTP是一個無狀態協議,這意味著每個請求都是獨立的,Keep-Alive沒能改變這個結果。另外,Keep-Alive也不能保證客戶端和伺服器之間的連線一定是活躍的,在HTTP1.1版本中也如此。唯一能保證的就是當連線被關閉時你能得到一個通知,所以不應該讓程式依賴於Keep-Alive的保持連線特性,否則會有意想不到的後果

 Keep-AlivePOST

HTTP1.1細則中規定了在一個POST訊息體後面不能有任何字元,還指出了對於某一個特定的瀏覽器可能並不遵循這個標準(比如在POST訊息體的後面放置一個CRLF符)。而據我所知,大部分瀏覽器在POST訊息體後都會自動跟一個CRLF符再發送,如何解決這個問題呢?根據上面的說明在POST請求頭中禁止使用Keep-Alive,或者由伺服器自動忽略這個CRLF,大部分伺服器都會自動忽略,但是在未經測試之前是不可能知道一個伺服器是否會這樣做。

 1、常用的方法dispatch_async

為了避免介面在處理耗時的操作時卡死,比如讀取網路資料,IO,資料庫讀寫等,我們會在另外一個執行緒中處理這些操作,然後通知主執行緒更新介面。

GCD實現這個流程的操作比前面介紹的NSThread  NSOperation的方法都要簡單。程式碼框架結構如下:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

 // 耗時的操作

 dispatch_async(dispatch_get_main_queue(), ^{

 // 更新介面

 });

 });

如果這樣還不清晰的話,那我們還是用上兩篇部落格中的下載圖片為例子,程式碼如下:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

 NSData * data = [[NSData alloc]initWithContentsOfURL:url];

 UIImage *image = [[UIImage alloc]initWithData:data];

 if (data != nil) {

 dispatch_async(dispatch_get_main_queue(), ^{

 self.imageView.image = image;

 });

 }

 });

執行顯示:

是不是程式碼比NSThread  NSOperation簡潔很多,而且GCD會自動根據任務在多核處理器上分配資源,優化程式。

系統給每一個應用程式提供了三個concurrent dispatch queues。這三個併發排程佇列是全域性的,它們只有優先順序的不同。因為是全域性的,我們不需要去建立。我們只需要通過使用函式dispath_get_global_queue去得到佇列,如下:

 dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

這裡也用到了系統預設就有一個序列佇列main_queue

 dispatch_queue_t mainQ = dispatch_get_main_queue();

雖然dispatch queue是引用計數的物件,但是以上兩個都是全域性的佇列,不用retainrelease

 2dispatch_group_async的使用

 dispatch_group_async可以實現監聽一組任務是否完成,完成後得到通知執行其他的操作。這個方法很有用,比如你執行三個下載任務,當三個任務都下載完成後你才通知介面說完成的了。下面是一段例子程式碼:

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

 dispatch_group_t group = dispatch_group_create();

 dispatch_group_async(group, queue, ^{

 [NSThread sleepForTimeInterval:1];

 NSLog(@"group1");

 });

 dispatch_group_async(group, queue, ^{

 [NSThread sleepForTimeInterval:2];

 NSLog(@"group2");

 });

 dispatch_group_async(group, queue, ^{

 [NSThread sleepForTimeInterval:3];

 NSLog(@"group3");

 });

 dispatch_group_notify(group, dispatch_get_main_queue(), ^{

 NSLog(@"updateUi");

 });

 dispatch_release(group);

 dispatch_group_async是非同步的方法,執行後可以看到列印結果:

 2012-09-25 16:04:16.737 gcdTest[43328:11303] group1

 2012-09-25 16:04:17.738 gcdTest[43328:12a1b] group2

 2012-09-25 16:04:18.738 gcdTest[43328:13003] group3

 2012-09-25 16:04:18.739 gcdTest[43328:f803] updateUi

每個一秒列印一個,當第三個任務執行後,upadteUi被列印。

 3dispatch_barrier_async的使用

 dispatch_barrier_async是在前面的任務執行結束後它才執行,而且它後面的任務等它執行完成之後才會執行

例子程式碼如下:

 dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);

 dispatch_async(queue, ^{

 [NSThread sleepForTimeInterval:2];

 NSLog(@"dispatch_async1");

 });

 dispatch_async(queue, ^{

 [NSThread sleepForTimeInterval:4];

 NSLog(@"dispatch_async2");

 });

 dispatch_barrier_async(queue, ^{

 NSLog(@"dispatch_barrier_async");

 [NSThread sleepForTimeInterval:4];

 });

 dispatch_async(queue, ^{

 [NSThread sleepForTimeInterval:1];

 NSLog(@"dispatch_async3");

 });

列印結果:

 2012-09-25 16:20:33.967 gcdTest[45547:11203] dispatch_async1

 2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_async2

 2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_barrier_async

 2012-09-25 16:20:40.970 gcdTest[45547:11303] dispatch_async3

請注意執行的時間,可以看到執行的順序如上所述。

 4dispatch_apply

執行某個程式碼片段N次。

 dispatch_apply(5, globalQ, ^(size_t index) {

 // 執行5

 });

 copyretain

 1copy其實是建立了一個相同的物件,而retain不是;

 2copy是內容拷貝,retain是指標拷貝;

 3copy是內容的拷貝 ,對於像NSString,的確是這樣,但是如果copy的是一個NSArray?這時只是copy了指向array中相對應元素的指標.這便是所謂的"淺複製".

 4copy的情況:NSString *newPt = [pt copy];

此時會在堆上重新開闢一段記憶體存放@"abc"比如0X1122內容為@"abc同時會在棧上為newPt分配空間比如地址:0Xaacc內容為0X1122因此retainCount增加1newPt來管理0X1122這段記憶體;

 assignretain

 1assign:簡單賦值,不更改索引計數;

 2assign的情況:NSString *newPt = [pt assing];

此時newPtpt完全相同地址都是0Xaaaa內容為0X1111newPt只是pt的別名,對任何一個操作就等於對另一個操作,因此retainCount不需要增加;

 3assign就是直接賦值;

 4retain使用了引用計數,retain引起引用計數加1, release引起引用計數減1,當引用計數為0時,dealloc函式被呼叫,記憶體被回收;

 5retain的情況:NSString *newPt = [pt retain];

此時newPt的地址不再為0Xaaaa,可能為0Xaabb但是內容依然為0X1111因此newPt pt都可以管理"abc"所在的記憶體,因此 retainCount需要增加1

 readonly

 1、屬性是隻讀的,預設的標記是讀寫,如果你指定了只讀,在@implementation中只需要一個讀取器。或者如果你使用@synthesize關鍵字,也是有讀取器方法被解析

 readwrite

 1、說明屬性會被當成讀寫的,這也是預設屬性。設定器和讀取器都需要在@implementation中實現。如果使用@synthesize關鍵字,讀取器和設定器都會被解析;

 nonatomic

 1、非原子性訪問,對屬性賦值的時候不加鎖,多執行緒併發訪問會提高效能。如果不加此屬性,則預設是兩個訪問方法都為原子型事務