1. 什么是arc?(arc是為了解決什么問題誕生的?)
- ARC的全稱是Automatic Reference Counting(自動引用計數)
- ARC是自iOS 5之后增加的新特性,不需要程序員管理內容, 編譯器會在適當的地方自動給我們添加release/retain等代碼(主要針對內存泄露)
相關知識:
1.與ARC相對的是MRC, 手動引用計數, 可以簡稱MRC (Manual Reference Counting) 所有對象的內容都需要我們手動管理, 需要程序員自己編寫release/retain等代碼
2.ARC的判斷原則:只要沒有強指針指向對象,對象就會被釋放。
(1)強指針:默認的情況下,所有的指針都是強指針,關鍵字strong
(2)弱指針:_ _weak關鍵字修飾的指針
3.ARC的特點總結:
(1)不允許調用release,retain,retainCount
(2)允許重寫dealloc,但是不允許調用[super dealloc]
(3)@property的參數:
Strong:相當于原來的retain(適用于OC對象類型),成員變量是強指針
Weak:相當于原來的assign,(適用于oc對象類型),成員變量是弱指針
Assign:適用于非OC對象類型(基礎類型)
2. 請解釋以下keywords的區別: assign vs weak, __block
vs__weak
- weak和assign都是引用計算不變,兩個的差別在于,weak用于指針類型,而assign用于簡單的數據類型,如int BOOL 等。
- assign看起來跟weak一樣,其實不能混用的,assign的變量在釋放后并不設置為nil(和weak不同),當你再去引用時候就會發生錯誤,崩潰,EXC_BAD_ACCESS.
-
__block
是通過引用來訪問self的實例變量,block也是一個強引用,self被retain,引起循環引用,用__weak
是弱引用,當self釋放時,weakSelf已經等于nil
相關知識
在垃圾回收機制里面,如果你同時使用__weak
和__block
來標識一個變量,那么該block將不會保證它是一直是有效的。 如果你在實現方法的時候使用了block,對象的內存管理規則更微妙:也是(__weak
與__block
區別:)
(1)如果你通過引用來訪問一個實例變量,self會被retain。
(2)如果你通過值來訪問一個實例變量,那么變量會被retain
擴展:NSTimer注意避免循環引用的地方,需要找個合適的時機和地方來 invalidate timer
在引用計數的環境里面,默認情況下當你在block里面引用一個Objective-C對象的時候,該對象會被retain。當你簡單的引用了一個對象的實例變量時,它同樣被retain。但是被__block存儲類型修飾符標記的對象變量不會被retain
3. __block在arc和非arc下含義一樣嗎?
- 對于非ARC下, 為了防止循環引用, 我們使用
__block
來修飾在Block中使用的對象: - 對于ARC下, 為了防止循環引用, 我們使用
__weak
來修飾在Block中使用的對象。原理就是:ARC中,Block中如果引用了__strong
修飾符的自動變量,則相當于Block對該變量的引用計數+1。
<解決循環引用問題>
//非ARC
__block typeof(self) weakSelf = self;
self.myBlock = ^(int paramInt){
//使用weakSelf訪問self成員
[weakSelf anotherFunc];
};
4. 使用atomic一定是線程安全的嗎?
- 當然不是。
atomic在set方法里加了鎖,防止了多線程一直去寫這個property,造成難以預計的數值, 但這也只是讀寫的鎖定, 線程安全其實還是差一些。
atomic有個很大的問題是很慢,要比nonatomic慢20倍。
當然最后建議這種數值數值變化可以讓服務器來做
5. 描述一個你遇到過的retaincycle例子。
- retain cycle 會造成內存溢出,嚴重情況會引起崩潰。一般注意點也不會發生,但在網絡連接比較多的地方就會不小心出現,vc異步的網絡請求,成功后的block調用vc,如果此時,用戶已經不用此vc了,vc還是沒有釋放。
一個下拉刷新,那個view和vc互相強引用,導致了沒釋放。view回去調用vc的scrollview的contentoffset
6. +(void)load; +(void)initialize;有什么用處?
- 當類對象被引入項目時, runtime 會向每一個類對象發送 load 消息, 并且會在每一個類甚至分類被引入時僅調用一次,(調用的順序是父類優先于子類, 子類優先于分類) 而且每一個類中的 load 方法都不需要像 viewDidLoad 方法一樣調用父類的方法.
舉個例子:
由于 load 方法會在類被 import 時調用一次, 而這時往往是改變類的行為的最佳時機. 我在 DKNightVersion 中使用 method swizlling 來修改原有的方法時, 就是在分類 load 中實現的.
- initialize 方法和 load 方法有一些不同, 它雖然也會在整個 runtime 過程中調用一次, 但是它是在該類的第一個方法執行之前調用, 也就是說 initialize 的調用是 <惰性> 的, 它的實現也與我們在平時使用的惰性初始化屬性時基本相同.
我在實際的項目中并沒有遇到過必須使用這個方法的情況, 在該方法中主要做 靜態變量的設置 并用于 確保在實例初始化前某些條件必須滿足 .
7. 為什么其他語言里叫函數調用,objective-c語言里則是給對象發消息(或者談下對runtime的理解)
- 我們在其他語言中比如說: C, Python, Java, C++, Haskell ... 中提到函數調用或者方法調用(面向對象). 函數調用是在編譯期就已經決定了會調用哪個函數(方法), 編譯器在編譯期就能檢查出函數的執行是否正確.
**然而 Objective-C(ObjC) 是一門動態的語言, 整個 ObjC 語言都是盡可能的將所有的工作推遲到運行時才決定. 它基于 runtime 來工作, runtime 就是 ObjC 的靈魂, 其核心就是消息發送 objc_msgSend **
- 所有的消息都會在運行時才會確定,
[obj message]
在運行時會被轉化為objc_msgSend(id self, SEL cmd, ...)
來執行, 它會在運行時從 選擇子表中尋找對應的選擇子 并將選擇子與實現進行綁定. 而如果沒有找到對應的實現, 就會進入類似黑魔法的消息轉發流程. 調用+ (BOOL)resolveInstanceMethod:(SEL)aSelector
方法, 我們可以在這個方法中 為類動態地生成方法 .
我們幾乎可以使用 runtime 修改 Objective-C 中的一切:
*class property object ivar method protocol *
8. 什么是methodswizzling?
-
method swizzling
實際上就是一種在運行時動態修改原有方法的技術, 它實際上是基于ObjC runtime
的特性, 而method swizzling
的核心方法就是method_exchangeImplementations(SEL origin, SEL swizzle)
使用這個方法就可以在運行時動態地改變原有的方法實現
在DKNigtVersion (為iOS應用添加夜間模式) 中能夠看到大量 method swizzling 的使用, 方法的調用時機就是在上面提到的 load 方法中, 不在 initialize 方法中改變方法實現的原因是 initialize 可能會被子類所繼承并重新執行最終導致錯誤 , 而 load 并不會被繼承并重新執行.
9. UIView和CALayer是啥關系?
- 每一個 UIView 的身后對應一個 Core Animation 框架中的 CALayer.
在 iOS 上 當你處理一個一個有一個的 UIView 時實際上是在操作 CALayer . 盡管有的時候你并不知道 (直接操作 CALayer 并不會在對效率有著顯著的提升).
- UIView 實際上就是對 CALayer 的輕量級的封裝. UIView 繼承自 UIResponder 處理來自用戶的事件; CALayer 繼承自 NSObject 主要用于圖層的渲染和動畫. 設計原因:
1.你可以通過操作 UIView 在一個更高的層級上處理與用戶的交互, 觸摸, 點擊, 拖拽等事件, 這些都是在 UIKit 這個層級上完成的.
2.UIView 和 NSView(AppKit) 的實現極其不同, 而使用 Core Animation 可以實現底層代碼地重用, 因為在 Mac 和 iOS 平臺上都使用著近乎相同的 Core Animation 代碼, 這樣我們可以對這個層級進行抽象在兩種平臺上產生 UIKit 和 AppKit 用于不同平臺的框架.
3.使用 CALayer 的唯一原因大概是便于移植到不同的平臺, 如果僅僅使用 Core Animation 層級, 處理用戶的交互時間需要寫更多的代碼.
10. 如何高性能的給UIImageView加個圓角?
(不準說layer.cornerRadius!)
- 一般情況下給 UIImageView 或者說 UIKit 的控件添加圓角都是改變
clipsToBounds 和 layer.cornerRadius
, 這樣大約兩行代碼就可以解決. 但是, 這樣使用這樣的方法會 強制Core Animation
提前渲染屏幕的離屏繪制 , 而離屏繪制就會為性能帶來負面影響.
<貝塞爾曲線> 可以完美解決
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
11. 使用drawRect有什么影響?(這個可深可淺,你至少得用過。。)
這個方法的主要作用是根據傳入的 rect 來繪制圖像,我們還可以在這個方法中使用 Core Graphics 和 UIKit 來繪制視圖的內容.
這個方法的調用機制也是非常特別. 當你調用 setNeedsDisplay 方法時, UIKit 將會把當前圖層標記為 dirty, 但還是會顯示原來的內容, 直到下一次的視圖渲染周期, 才會重新建立 Core Graphics 上下文, 然后將內存中的數據恢復出來, 使用 CGContextRef 進行繪制.
12. ASIHttpRequest或者SDWebImage里面給
UIImageView加載圖片的邏輯是什么樣的?(把
UIImageView放到UITableViewCell里面問更贊)
SDWebImage 中為 UIView 提供了一個分類叫做 WebCache, 這個分類中有一個最常用的接口,
sd_setImageWithURL:placeholderImage:
, 這個分類同時提供了很多類似的方法, 這些方法最終會調用一個同時具有optionprogressBlockcompletionBlock
的方法, 而在這個類最終被調用的方法首先會檢查是否傳入了placeholderImage
以及對應的參數,并設置
placeholderImage
-
然后獲取SDWebImageManager中的單例調用一個
``downloadImageWithURL:...`` 的方法來獲取圖片, 而這個manager 獲取圖片的過程有大體上分為兩部分:
首先會在 SDWebImageCache 中尋找圖片是否有對應的緩存, 它會以 url 作為數據的索引先在內存中尋找是否有對應的緩存, 如果緩存未命中就會在磁盤中利用 MD5 處理過的 key 來繼續查詢對應的數據, 如果找到了, 就會把磁盤中的緩存備份到內存中.
然而, 假設我們在內存和磁盤緩存中都沒有命中, 那么manager就會調用 SDWebImageDownloader 對象的方法
downloadImageWithURL:...
來下載圖片, 過程中調用另一個方法:
addProgressCallback:andCompletedBlock:fotURL:createCallback:
來存儲下載過程中和下載完成的回調, 當回調塊是第一次添加的時候, 方法會實例化一個 NSMutableURLRequest 和 SDWebImageDownloaderOperation , 并將后者加入 downloader 持有的下載隊列開始圖片的異步下載.
- 而在圖片下載完成之后, 就會在主線程設置 image, 完成整個圖像的異步下載和配置.
13. 麻煩你設計個簡單的圖片內存緩存器(移除策略是一定要說的)
圖片的內存緩存,可以考慮將圖片數據保存到一個數據模型中。所以在程序運行時這個模型都存在內存中。
移除策略:釋放數據模型對象。
14. 講講你用Instrument優化動畫性能的經歷吧(別問我什么是Instrument)
考慮程序的性能,可以借助數據化圖形化的輸出方式。與其花費時間在優化小細節上不如多點時間找到你改優化的地方.時間事件查看器 ——> Time Profiler打開Instrument的步驟:xcode ——> open developer Tool ——> instrument 選取相應的strumnet的工具,選擇相應的strument里面的工具,然后選擇模擬器或者真機上面的應用,點擊右上角的開始按鈕,然后就可以看到相應內容
15. loadView是干嘛用的?
- 當你訪問一個ViewController的view屬性時,如果此時view的值是nil,那么,ViewController就會自動調用loadView這個方法。這個方法就會加載或者創建一個view對象,賦值給view屬性。
loadView默認做的事情是:如果此ViewController存在一個對應的nib文件,那么就加載這個nib。否則,就創建一個UIView對象。
如果你用Interface Builder來創建界面,那么不應該重載這個方法。
如果你想自己創建view對象,那么可以重載這個方法。此時你需要自己給view屬性賦值。你自定義的方法不應該調用super。如果你需要對view做一些其他的定制操作,在viewDidLoad里面去做。
- 根據上面可以知道,有兩種情況:
1、如果你用了nib文件,重載這個方法就沒有太大意義。因為loadView的作用就是加載nib。如果你重載了這個方法不調用super,那么nib文件就不會被加載。如果調用了super,那么view已經加載完了,你需要做的其他事情在viewDidLoad里面做更合適。
2、如果你沒有用nib,這個方法默認就是創建一個空的view對象。如果你想自己控制view對象的創建,例如創建一個特殊尺寸的view,那么可以重載這個方法,自己創建一個UIView對象,然后指定 self.view = myView; 但這種情況也沒有必要調用super,因為反正你也不需要在super方法里面創建的view對象。如果調用了super,那么就是浪費了一些資源而已
16. viewWillLayoutSubView你總是知道的。
橫豎屏切換的時候,系統會響應一些函數,其中
viewWillLayoutSubviews 和 viewDidLayoutSubviews。
- (void)viewWillLayoutSubviews {
[self _shouldRotateToOrientation:(UIDeviceOrientation)[UIApplication sharedApplication].statusBarOrientation];
}
-(void)_shouldRotateToOrientation:(UIDeviceOrientation)orientation {
if (orientation == UIDeviceOrientationPortrait ||orientation ==UIDeviceOrientationPortraitUpsideDown) {
// 豎屏 }
else {
// 橫屏 }
}
- (NSUInteger)supportedInterfaceOrientations{
// 切換橫屏
return UIInterfaceOrientationMaskLandscape;
}
通過上述一個函數就知道橫豎屏切換的接口了。
注意:viewWillLayoutSubviews只能用在ViewController里面,在view里面沒有響應。
17. GCD里面有哪幾種Queue?你自己建立過串行queue嗎?背后的線程模型是什么樣的?
- 1.主隊列 dispatch_main_queue(); 串行 ,更新UI
2.全局隊列 dispatch_global_queue(); 并行,四個優先級:background,low,default,high
3.自定義隊列 dispatch_queue_t queue ; 可以自定義是并行:DISPATCH_QUEUE_CONCURRENT
或者串行DISPATCH_QUEUE_SERIAL
18. 用過coredata或者sqlite嗎?讀寫是分線程的嗎?遇到過死鎖沒?咋解決的?
- 數據庫讀取操作一般都是多線程訪問的。在對數據進行讀取時,我們要保證其當前狀態不能被修改,即讀取時加鎖,否則就會出現數據錯誤混亂。
IOS中常用的兩種數據持久化存儲方式:CoreData和SQLite,兩者都需要設置線程安全
19. http的post和get啥區別?(區別挺多的,麻煩多說點)
GET和POST與數據如何傳遞沒有關系, GET和POST是由HTTP協議定義的。在HTTP協議中,Method和Data(URL, Body, Header)是正交的兩個概念,也就是說,使用哪個Method與應用層的數據如何傳輸是沒有相互關系的。HTTP沒有要求,如果Method是POST數據就要放在BODY中。也沒有要求,如果Method是GET,數據(參數)就一定要放在URL中而不能放在BODY中.
- 1.(1) GET請求的數據會附在URL之后(就是把數據放置在HTTP協議頭中),以?分割URL和傳輸數據,參數之間以&相連,如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0%E5%A5%BD。如果數據是英文字母/數字,原樣發送,如果是空格,轉換為+,如果是中文/其他字符,則直接把字符串用BASE64加密,得出如:%E4%BD%A0%E5%A5%BD,其中%XX中的XX為該符號以16進制表示的ASCII。
(2) POST把提交的數據則放置在是HTTP包的包體中。 - 2 . HTTP協議對GET和POST都沒有對長度的限制
(1).首先是”GET方式提交的數據是有字節限制的”,因為GET是通過URL提交數據,那么GET可提交的數據量就跟URL的長度有直接關系了。其限制取決于操作系統的支持。
注意這是限制是整個URL長度,而不僅僅是你的參數值數據長度。
(2).理論上講,POST是沒有大小限制的,HTTP協議規范也沒有進行大小限制,POST數據是沒有限制的,起限制作用的是服務器的處理程序的處理能力。
- 3.POST的安全性要比GET的安全性高,其實沒有絲毫關系。對于GET和POST的理解,是純粹地來源于HTTP協議。他們只有一點根本區別,簡單點兒說,一個用于獲取數據,一個用于修改數據
20. 我知道你大學畢業過后就沒接觸過算法數據結構了,但是請你一定告訴我什么是Binary search tree? search的時間復雜度是多少?我很想知道!
- Binary search tree:二叉搜索樹。
主要由四個方法:(用C語言實現或者Python)
1.search:時間復雜度為O(h),h為樹的高度
2.traversal:時間復雜度為O(n),n為樹的總結點數。
3.insert:時間復雜度為O(h),h為樹的高度。
4.delete:最壞情況下,時間復雜度為O(h)+指針的移動開銷。
可以看到,二叉搜索樹的dictionary operation的時間復雜度與樹的高度h相關。所以需要盡可能的降低樹的高度,由此引出平衡二叉樹Balanced binary tree。它要求左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹。這樣就可以將搜索樹的高度盡量減小。常用算法有紅黑樹、AVL、Treap、伸展樹等。