iOS 最全的面試題~[有答案]
//聯絡人:石虎 QQ:1224614774 暱稱:嗡嘛呢叭咪哄
QQ群:807236138 群稱:iOS 技術交流學習群
/*
----
多執行緒、特別是NSOperation和 GCD的內部原理。
執行時機制的原理和運用場景。
SDWebImage的原理。實現機制。如何解決TableView卡的問題。
block和代理的,通知的區別。block的用法需要注意些什麼。
strong,weak,retain,assign,copy nomatic 等的區別。
設計模式,mvc,單利,工廠,代理等的應用場景。
單利的寫法。在單利中建立陣列應該注意些什麼。
NSString 的時候用copy和strong的區別。
響應值鏈。
NSTimer 在子執行緒中應該手動建立NSRunLoop,否則不能迴圈執行。
UIScrollView和NSTimer組合做迴圈廣告圖輪播的時候有一個屬性可以控制當上下滾動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伺服器,包括iPlanet、IIS和Apache,都支援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-Alive和POST
在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是引用計數的物件,但是以上兩個都是全域性的佇列,不用retain或release。
2、dispatch_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被列印。
3、dispatch_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
請注意執行的時間,可以看到執行的順序如上所述。
4、dispatch_apply
執行某個程式碼片段N次。
dispatch_apply(5, globalQ, ^(size_t index) {
// 執行5次
});
copy與retain:
1、copy其實是建立了一個相同的物件,而retain不是;
2、copy是內容拷貝,retain是指標拷貝;
3、copy是內容的拷貝 ,對於像NSString,的確是這樣,但是如果copy的是一個NSArray呢?這時只是copy了指向array中相對應元素的指標.這便是所謂的"淺複製".
4、copy的情況:NSString *newPt = [pt copy];
此時會在堆上重新開闢一段記憶體存放@"abc"比如0X1122內容為@"abc同時會在棧上為newPt分配空間比如地址:0Xaacc內容為0X1122因此retainCount增加1供newPt來管理0X1122這段記憶體;
assign與retain:
1、assign:簡單賦值,不更改索引計數;
2、assign的情況:NSString *newPt = [pt assing];
此時newPt和pt完全相同地址都是0Xaaaa內容為0X1111即newPt只是pt的別名,對任何一個操作就等於對另一個操作,因此retainCount不需要增加;
3、assign就是直接賦值;
4、retain使用了引用計數,retain引起引用計數加1, release引起引用計數減1,當引用計數為0時,dealloc函式被呼叫,記憶體被回收;
5、retain的情況:NSString *newPt = [pt retain];
此時newPt的地址不再為0Xaaaa,可能為0Xaabb但是內容依然為0X1111。因此newPt和 pt都可以管理"abc"所在的記憶體,因此 retainCount需要增加1;
readonly:
1、屬性是隻讀的,預設的標記是讀寫,如果你指定了只讀,在@implementation中只需要一個讀取器。或者如果你使用@synthesize關鍵字,也是有讀取器方法被解析
readwrite:
1、說明屬性會被當成讀寫的,這也是預設屬性。設定器和讀取器都需要在@implementation中實現。如果使用@synthesize關鍵字,讀取器和設定器都會被解析;
nonatomic:
1、非原子性訪問,對屬性賦值的時候不加鎖,多執行緒併發訪問會提高效能。如果不加此屬性,則預設是兩個訪問方法都為原子型事務