1. 什么是 ARC? (ARC 是為了解決什么問題而誕生的?)
ARC 是 Automatic Reference Counting 的縮寫, 即自動引用計數. 這是蘋果在 iOS5 中引入的內存管理機制. Objective-C 和 Swift 使用 ARC 追蹤和管理應用的內存使用. 這一機制使得開發者無需鍵入 retain 和 release , 這不僅能夠降低程序崩潰和內存泄露的風險, 而且可以減少開發者的工作量, 能夠大幅度提升程序的 流暢性 和 可預測性 . 但是 ARC 不適用于 Core Foundation 框架中, 仍然需要手動管理內存.
2. 以下 keywords 有什么區別: assign vs weak , __block vs __weak
assign 和 weak 是用于在聲明屬性時, 為屬性指定內存管理的語義.
assign 用于簡單的賦值, 不改變屬性的引用計數, 用于 Objective-C 中的 NSInteger , CGFloat 以及 C 語言中 int , float , double 等數據類型.
weak 用于對象類型, 由于 weak 同樣不改變對象的引用計數且不持有對象實例, 當該對象廢棄時, 該弱引用自動失效并且被賦值為 nil , 所以它可以用于避免兩個強引用產生的 循環引用 導致內存無法釋放的問題.
__block 和 __weak 之間的卻是確實極大的, 不過它們都用于修飾變量.
前者用于指明當前聲明的變量在被 block 捕獲之后, 可以在 block 中改變變量的值. 因為在 block 聲明的同時會截獲該 block 所使用的全部自動變量的值, 而這些值只在 block 中 只具有"使用權"而不具有"修改權" . 而 __block 說明符就為 block 提供了變量的修改權.
后者是 所有權修飾符 , 什么是所有權修飾符? 這里涉及到另一個問題, 因為在 ARC 有效時, id 類型和對象類型同 C 語言中的其他類型不同, 必須附加所有權修飾符. 所有權修飾符一種有 4 種:
__strong
__weak
__unsafe_unretained
__autorelease
__weak 與 weak 的區別只在于, 前者用于變量的聲明, 而后者用于屬性的聲明.
3. __block 在 ARC 和非 ARC 下含義一樣嗎?
__block 在 ARC 下捕獲的變量會被 block retain , 這樣可能導致循環引用, 所以必須要使用弱引用才能解決該問題. 而在非 ARC 下, 可以直接使用 __block 說明符修飾變量, 因為在非 ARC 下, block 不會 retain 捕獲的變量.
4. 使用 nonatomic 一定是線程安全的嗎?
nonatomic 的內存管理語義是 非原子 的, 非原子的操作本來就是線程不安全的, 而 atomic 的操作是原子的, 但是 并不意味著它是線程安全的 , 它會增加正確的幾率, 能夠更好的避免線程的錯誤, 但是它仍然是線程不安全的.
當使用 nonatomic 的時候, 屬性的 setter 和 getter 操作是非原子的, 所以當多個線程同時對某一屬性進行讀和寫的操作, 屬性的最終結果是不能預測的.
當使用 atomic 時, 雖然對屬性的讀和寫是原子的, 但是仍然可能出現線程錯誤: 當線程 A 進行寫操作, 這時其他線程的讀或寫操作會因為該操作的進行而等待. 當 A 線程的寫操作結束后, B 線程進行寫操作, 然后當 A 線程進行讀操作時, 卻獲得了在 B 線程中的值, 這就破壞了線程安全, 如果有線程 C 在 A 線程讀操作前 release 了該屬性, 那么還會導致程序崩潰. 所以僅僅使用 atomic 并不會使得線程安全, 我們還需要為線程添加 lock 來確保線程的安全.
atomic 都不是一定線程安全的, nonatomic 就更不必多說了.
5. 描述一個你遇到過的 retain cycle 例子.
6. + (void)load; 和 + (void)initialize; 有什么用處?
當類對象被引入項目時, runtime 會向每一個類對象發送 load 消息. load 方法還是非常的神奇的, 因為它會在 每一個類甚至分類 被引入時僅調用一次, 調用的順序是父類優先于子類, 子類優先于分類. 而且 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 .
What makes Objective-C truly powerful is its runtime.
所有的消息都會在運行時才會確定, [obj message] 在運行時會被轉化為 objc_msgSend(id self, SEL cmd, ...) 來執行, 它會在運行時從 選擇子表中尋找對應的選擇子 并將選擇子與實現進行綁定. 而如果沒有找到對應的實現, 就會進入類似黑魔法的消息轉發流程. 調用 + (BOOL)resolveInstanceMethod:(SEL)aSelector 方法, 我們可以在這個方法中 為類動態地生成方法 .
我們幾乎可以使用 runtime 魔改 Objective-C 中的一切: class property object ivar method protocol , 而下面就是它的主要應用:
內省
為分類動態的添加屬性
使用方法調劑修改原有的方法實現
...
8. 什么是 Method Swizzling?
method swizzling 實際上就是一種在運行時動態修改原有方法的技術, 它實際上是基于 ObjC runtime 的特性, 而 method swizzling 的核心方法就是 method_exchangeImplementations(SEL origin, SEL swizzle) . 使用這個方法就可以在運行時動態地改變原有的方法實現, 在 DKNigtVersion (為 iOS 應用添加夜間模式) 中能夠看到大量 method swizzling 的使用, 方法的調用時機就是在上面提到的 load 方法中, 不在 initialize 方法中改變方法實現的原因是 initialize 可能會被子類所繼承并重新執行最終導致錯誤 , 而 load 并不會被繼承并重新執行.
9. UIView 和 CALayer 有什么關系?
看到這個問題不禁想到大一在網易面試時的經歷, 當時的兩位面試官就問了我這么一個問題, UIView 和 CALayer 是什么關系, 為什么要這么設計? 我已經忘記了當時是怎么回答的. 隱約記得當時說每一個 UIView 都會對應一個 CALayer 至于為什么這樣, 當時的我實在是太弱無法回答出來了.
每一個 UIView 的身后對應一個 Core Animation 框架中的 CALayer .
Many of the methods you call on UIView simply delegate to the layer
在 iOS 上 當你處理一個一個有一個的 UIView 時實際上是在操作 CALayer . 盡管有的時候你并不知道 (直接操作 CALayer 并不會在對效率有著顯著的提升).
UIView 實際上就是對 CALayer 的輕量級的封裝. UIView 繼承自 UIResponder 處理來自用戶的事件; CALayer 繼承自 NSObject 主要用于圖層的渲染和動畫. 這么設計有以下幾個原因:
你可以通過操作 UIView 在一個更高的層級上處理與用戶的交互, 觸摸, 點擊, 拖拽等事件, 這些都是在 UIKit 這個層級上完成的.
UIView 和 NSView(AppKit) 的實現極其不同, 而使用 Core Animation 可以實現底層代碼地重用, 因為在 Mac 和 iOS 平臺上都使用著近乎相同的 Core Animation 代碼, 這樣我們可以對這個層級進行抽象在兩種平臺上產生 UIKit 和 AppKit 用于不同平臺的框架.
使用 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];
在這里使用了貝塞爾曲線"切割"個這個圖片, 給 UIImageView 添加了的圓角.
11. 使用 drawRect: 有什么影響?
這個問題對于我來說確實有些難以回答, 我記得我在我人生的第一個 iOS 項目 SportJoin 中曾經使用這個方法來繪制圖形, 但是具體怎么做的, 我已經忘記了.
這個方法的主要作用是根據傳入的 rect 來繪制圖像 參見文檔 . 這個方法的默認實現沒有做任何事情, 我們 可以 在這個方法中使用 Core Graphics 和 UIKit 來繪制視圖的內容.
這個方法的調用機制也是非常特別. 當你調用 setNeedsDisplay 方法時, UIKit 將會把當前圖層標記為 dirty, 但還是會顯示原來的內容, 直到下一次的視圖渲染周期, 才會重新建立 Core Graphics 上下文, 然后將內存中的數據恢復出來, 使用 CGContextRef 進行繪制.
12. ASIHttpRequest 或者 SDWebImage 里面給 UIImageView 加載圖片的邏輯是什么樣的?
我曾經閱讀過 SDWebImage 的源代碼, 就在這里對如何給 UIImageView 加載圖片做一個總結吧, SDWebImage 中為 UIView 提供了一個分類叫做 WebCache, 這個分類中有一個最常用的接口, sd_setImageWithURL:placeholderImage: , 這個分類同時提供了很多類似的方法, 這些方法最終會調用一個同時具有 option progressBlock completionBlock 的方法, 而在這個類最終被調用的方法首先會檢查是否傳入了 placeholderImage 以及對應的參數, 并設置 placeholderImage .
然后會獲取 SDWebImageManager 中的單例調用一個 downloadImageWithURL:... 的方法來獲取圖片, 而這個 manager 獲取圖片的過程有大體上分為兩部分, 它首先會在 SDWebImageCache 中尋找圖片是否有對應的緩存, 它會以 url 作為數據的索引先在內存中尋找是否有對應的緩存, 如果緩存未命中就會在磁盤中利用 MD5 處理過的 key 來繼續查詢對應的數據, 如果找到了, 就會把磁盤中的緩存備份到內存中.
然而, 假設我們在內存和磁盤緩存中都沒有命中, 那么 manager 就會調用它持有的一個 SDWebImageDownloader 對象的方法 downloadImageWithURL:... 來下載圖片, 這個方法會在執行的過程中調用另一個方法 addProgressCallback:andCompletedBlock:fotURL:createCallback: 來存儲下載過程中和下載完成的回調, 當回調塊是第一次添加的時候, 方法會實例化一個 NSMutableURLRequest 和 SDWebImageDownloaderOperation , 并將后者加入 downloader 持有的下載隊列開始圖片的異步下載.
而在圖片下載完成之后, 就會在主線程設置 image, 完成整個圖像的異步下載和配置.
13. 設計一個簡單的圖片內存緩存器 (包含移除策略)
待我閱讀完 path 開源的 FastImageCache 的源代碼就來回答.
14. 講講你用Instrument優化動畫性能的經歷
15. loadView 的作用是什么?
This method loads or creates a view and assigns it to the view property.
loadView 是 UIViewController 的實例方法, 我們永遠不要直接調用這個方法 [self loadView] . 這在蘋果的 官方文檔 中已經明確的寫出了. loadView 會在獲取視圖控制器的 view 但是卻得到 nil 時被調用.
loadView 的具體實現會做下面兩件事情中的一件:
如果你的視圖控制器關聯了一個 storyboard, 那么它就會加載 storyboard 中的視圖.
如果試圖控制器沒有關聯的 storyboard, 那么就會創建一個空的視圖, 并分配給 view 屬性
如果你需要覆寫 loadView 方法:
你需要創建一個根視圖.
創建并初始化 view 的子視圖, 調用 addSubview: 方法將它們添加到父視圖上.
如果你使用了自動布局, 提供足夠的約束來保證視圖的位置.
將根視圖分配給 view 屬性.
永遠不要在這個方法中調用 [super loadView] .
16. viewWillLayoutSubviews 的作用是什么?
viewWillLayoutSubviews 方法會在視圖的 bounds 改變時, 視圖會調整子視圖的位置, 我們可以在視圖控制器中覆寫這個方法在視圖放置子視圖前做出改變, 當屏幕的方向改變時, 這個方法會被調用.
17. GCD 里面有哪幾種 Queue? 背后的線程模型是什么樣的?
GCD 中 Queue 的種類還要看我們怎么進行分類, 如果根據同一時間內處理的操作數分類的話, GCD 中的 Queue 分為兩類
Serial Dispatch Queue
Concurrent Dispatch Queue
一類是串行派發隊列, 它只使用一個線程, 會等待當前執行的操作結束后才會執行下一個操作, 它按照追加的順序進行處理. 另一類是并行派發隊列, 它同時使用多個線程, 如果當前的線程數足夠, 那么就不會等待正在執行的操作, 使用多個線程同時執行多個處理.
另外的一種分類方式如下:
Main Dispatch Queue
Global Dispatch Queue
Custom Dispatch Queue
主線程只有一個, 它是一個串行的進程. 所有追加到 Main Dispatch Queue 中的處理都會在 RunLoop 在執行. Global Dispatch Queue 是所有應用程序都能使用的并行派發隊列, 它有 4 個執行優先級 High, Default, Low, Background. 當然我們也可以使用 dispatch_queue_create 創建派發隊列.
18. Core Data 或者 sqlite 的讀寫是分線程的嗎? 死鎖如何解決?
Core Data 和 sqlite 這兩個我還真沒深入用過, 我只在小的玩具應用上使用過 Core Data, 但是發現這貨實在是太難用了, 我就果斷放棄了, sqlite 我也用過, 每次輸入 SQL 語句的時候我多想吐槽, 寫一些簡單的還好, 復雜的就直接 Orz 了. 所以我一般會使用 levelDB 對進行數據的持久存儲.
數據庫讀取操作一般都是多線程的, 在對數據進行讀取的時候, 我們要確保當前的狀態不會被修改, 所以加鎖, 防止由于線程競爭而出現的錯誤. 在 Core Data 中使用并行的最重要的規則是: 每一個 NSManagedObjectContext 必須只從創建它的進程中訪問 .
19. http 的 POST 和 GET 有什么區別?
根據 HTTP 協議的定義 GET 類型的請求是冪等的, 而 POST 請求是有副作用的, 也就是說 GET 用于獲取一些資源, 而 POST 用于改變一些資源, 這可能會創建新的資源或更新已有的資源.
POST 請求比 GET 請求更加的安全, 因為你不會把信息添加到 URL 上的查詢字符串上. 所以使用 GET 來收集密碼或者一些敏感信息并不是什么好主意.
最后, POST 請求比 GET 請求也可以傳輸更多的信息.
20. 什么是 Binary search tree, 它的時間復雜度是多少?
二叉搜索樹是一棵以二叉樹來組織的, 它搜索的時間復雜度 $O(h)$ 與樹的高度成正比, 最壞的運行時間是 $\\Theta(\\lg n)$.
1.什么是arc?(arc是為了解決什么問題誕生的?)
首先解釋ARC: automatic reference counting自動引用計數。
ARC幾個要點:
在對象被創建時 retain count +1,在對象被release時 retain count -1.當retain count 為0 時,銷毀對象。
程序中加入autoreleasepool的對象會由系統自動加上autorelease方法,如果該對象引用計數為0,則銷毀。
那么ARC是為了解決什么問題誕生的呢?這個得追溯到MRC手動內存管理時代說起。
MRC下內存管理的缺點:
1.當我們要釋放一個堆內存時,首先要確定指向這個堆空間的指針都被release了。(避免提前釋放)
2.釋放指針指向的堆空間,首先要確定哪些指針指向同一個堆,這些指針只能釋放一次。(MRC下即誰創建,誰釋放,避免重復釋放)
3.模塊化操作時,對象可能被多個模塊創建和使用,不能確定最后由誰去釋放。
4.多線程操作時,不確定哪個線程最后使用完畢
2.請解釋以下keywords的區別: assign vs weak, __block vs __weak
assign適用于基本數據類型,weak是適用于NSObject對象,并且是一個弱引用。
assign其實也可以用來修飾對象,那么我們為什么不用它呢?因為被assign修飾的對象在釋放之后,指針的地址還是存在的,也就是說指針并沒有被置為nil。如果在后續的內存分配中,剛好分到了這塊地址,程序就會崩潰掉。
而weak修飾的對象在釋放之后,指針地址會被置為nil。所以現在一般弱引用就是用weak。
首先__block是用來修飾一個變量,這個變量就可以在block中被修改(參考block實現原理)
__block:使用__block修飾的變量在block代碼快中會被retain(ARC下,MRC下不會retain)
__weak:使用__weak修飾的變量不會在block代碼塊中被retain
同時,在ARC下,要避免block出現循環引用 __weak typedof(self)weakSelf = self;
3.__block在arc和非arc下含義一樣嗎?
是不一樣的。
在MRC中__block variable在block中使用是不會retain的
但是ARC中__block則是會Retain的。
取而代之的是用__weak或是__unsafe_unretained來更精確的描述weak reference的目的
其中前者只能在iOS5之後可以使用,但是比較好 (該物件release之後,此pointer會自動設成nil)
而後者是ARC的環境下為了相容4.x的解決方案。
所以上面的範例中
__block MyClass* temp = …;? ? // MRC環境下使用
__weak MyClass* temp = …;? ? // ARC但只支援iOS5.0以上的版本
__unsafe_retained MyClass* temp = …;? //ARC且可以相容4.x以後的版本
4.使用nonatomic一定是線程安全的嗎?()
不是的。
atomic原子操作,系統會為setter方法加鎖。 具體使用 @synchronized(self){//code }
nonatomic不會為setter方法加鎖。
atomic:線程安全,需要消耗大量系統資源來為屬性加鎖
nonatomic:非線程安全,適合內存較小的移動設備
5.描述一個你遇到過的retain cycle例子。
block中的循環引用:一個viewController
@property (nonatomic,strong)HttpRequestHandler * handler;
@property (nonatomic,strong)NSData? ? ? ? ? *data;
_handler = [httpRequestHandler sharedManager];
[ downloadData:^(id responseData){
_data = responseData;
}];
self 擁有_handler, _handler 擁有block, block擁有self(因為使用了self的_data屬性,block會copy 一份self)
解決方法:
__weak typedof(self)weakSelf = self
[ downloadData:^(id responseData){
weakSelf.data = responseData;
}];
6.+(void)load; +(void)initialize;有什么用處?
在Objective-C中,runtime會自動調用每個類的兩個方法。+load會在類初始加載時調用,+initialize會在第一次調用類的類方法或實例方法之前被調用。這兩個方法是可選的,且只有在實現了它們時才會被調用。
共同點:兩個方法都只會被調用一次。
7.為什么其他語言里叫函數調用, objective c里則是給對象發消息(或者談下對runtime的理解)
先來看看怎么理解發送消息的含義:
曾經覺得Objc特別方便上手,面對著 Cocoa 中大量 API,只知道簡單的查文檔和調用。還記得初學 Objective-C 時把[receiver message]當成簡單的方法調用,而無視了“發送消息”這句話的深刻含義。于是[receiver message]會被編譯器轉化為:
objc_msgSend(receiver, selector)
如果消息含有參數,則為:
objc_msgSend(receiver, selector, arg1, arg2, ...)
如果消息的接收者能夠找到對應的selector,那么就相當于直接執行了接收者這個對象的特定方法;否則,消息要么被轉發,或是臨時向接收者動態添加這個selector對應的實現內容,要么就干脆玩完崩潰掉。
現在可以看出[receiver message]真的不是一個簡簡單單的方法調用。因為這只是在編譯階段確定了要向接收者發送message這條消息,而receive將要如何響應這條消息,那就要看運行時發生的情況來決定了。
Objective-C 的 Runtime 鑄就了它動態語言的特性,這些深層次的知識雖然平時寫代碼用的少一些,但是卻是每個 Objc 程序員需要了解的。
Objc Runtime使得C具有了面向對象能力,在程序運行時創建,檢查,修改類、對象和它們的方法。可以使用runtime的一系列方法實現。
順便附上OC中一個類的數據結構 /usr/include/objc/runtime.h
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指針指向Meta Class,因為Objc的類的本身也是一個Object,為了處理這個關系,r? ? ? untime就創造了Meta Class,當給類發送[NSObject alloc]這樣消息時,實際上是把這個消息發給了Class Object
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認為0
long info OBJC2_UNAVAILABLE; // 類信息,供運行期使用的一些位標識
long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存,對象接到一個消息會根據isa指針查找消息對象,這時會在method? ? ? Lists中遍歷,如果cache了,常用的方法調用時就能夠提高調用的效率。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協議鏈表
#endif
} OBJC2_UNAVAILABLE;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
OC中一個類的對象實例的數據結構(/usr/include/objc/objc.h):
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa? OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
1
2
3
4
5
6
7
8
9
10
11
12
13
向object發送消息時,Runtime庫會根據object的isa指針找到這個實例object所屬于的類,然后在類的方法列表以及父類方法列表尋找對應的方法運行。id是一個objc_object結構類型的指針,這個類型的對象能夠轉換成任何一種對象。
然后再來看看消息發送的函數:objc_msgSend函數
在引言中已經對objc_msgSend進行了一點介紹,看起來像是objc_msgSend返回了數據,其實objc_msgSend從不返回數據而是你的方法被調用后返回了數據。下面詳細敘述下消息發送步驟:
檢測這個 selector 是不是要忽略的。比如 Mac OS X 開發,有了垃圾回收就不理會 retain,release 這些函數了。
檢測這個 target 是不是 nil 對象。ObjC 的特性是允許對一個 nil 對象執行任何一個方法不會 Crash,因為會被忽略掉。
如果上面兩個都過了,那就開始查找這個類的 IMP,先從 cache 里面找,完了找得到就跳到對應的函數去執行。
如果 cache 找不到就找一下方法分發表。
如果分發表找不到就到超類的分發表去找,一直找,直到找到NSObject類為止。
如果還找不到就要開始進入動態方法解析了,后面會提到。
后面還有:
動態方法解析resolveThisMethodDynamically
消息轉發forwardingTargetForSelector
詳情可參考 http://www.lxweimin.com/p/620022378e97
8.什么是method swizzling?
Method Swizzling 原理(方法攪拌?)
在Objective-C中調用一個方法,其實是向一個對象發送消息,查找消息的唯一依據是selector的名字。利用Objective-C的動態特性,可以實現在運行時偷換selector對應的方法實現,達到給方法掛鉤的目的。
每個類都有一個方法列表,存放著selector的名字和方法實現的映射關系。IMP有點類似函數指針,指向具體的Method實現。
方法指向
我們可以利用 method_exchangeImplementations 來交換2個方法中的IMP,
我們可以利用 class_replaceMethod 來修改類,
我們可以利用 method_setImplementation 來直接設置某個方法的IMP,
……
歸根結底,都是偷換了selector的IMP,如下圖所示:
方法交換
詳情:http://blog.csdn.net/yiyaaixuexi/article/details/9374411
9.UIView和CALayer是啥關系?
1.UIView是iOS系統中界面元素的基礎,所有的界面元素都繼承自它。它本身完全是由CoreAnimation來實現的 (Mac下似乎不是這樣)。它真正的繪圖部分,是由一個叫CALayer(Core Animation Layer)的類來管理。 UIView本身,更像是一個CALayer的管理器,訪問它的跟繪圖和跟坐標有關的屬性,例如frame,bounds等 等,實際上內部都是在訪問它所包含的CALayer的相關屬性。
2.UIView有個layer屬性,可以返回它的主CALayer實例,UIView有一個layerClass方法,返回主layer所使用的 類,UIView的子類,可以通過重載這個方法,來讓UIView使用不同的CALayer來顯示,例如通過
- (class) layerClass {
return ([CAEAGLLayer class]);
}
1
2
3
4
=使某個UIView的子類使用GL來進行繪制。
3.UIView的CALayer類似UIView的子View樹形結構,也可以向它的layer上添加子layer,來完成某些特殊的表 示。例如下面的代碼
grayCover = [[CALayer alloc] init];
grayCover.backgroundColor = [[[UIColor blackColor] colorWithAlphaComponent:0.2] CGColor];
[self.layer addSubLayer: grayCover];
1
2
3
4
5
會在目標View上敷上一層黑色的透明薄膜。
4.UIView的layer樹形在系統內部,被系統維護著三份copy(這段理解有點吃不準)。
邏輯樹,就是代碼里可以操縱的,例如更改layer的屬性等等就在這一份。
動畫樹,這是一個中間層,系統正在這一層上更改屬性,進行各種渲染操作。
顯示樹,這棵樹的內容是當前正被顯示在屏幕上的內容。
這三棵樹的邏輯結構都是一樣的,區別只有各自的屬性。
10. 如何高性能的給UIImageView加個圓角?(不準說layer.cornerRadius!)
我覺得應該是使用Quartz2D直接繪制圖片,得把這個看看。
步驟:
a、創建目標大小(cropWidth,cropHeight)的畫布。
b、使用UIImage的drawInRect方法進行繪制的時候,指定rect為(-x,-y,width,height)。
c、從畫布中得到裁剪后的圖像。
- (UIImage*)cropImageWithRect:(CGRect)cropRect
{
CGRect drawRect = CGRectMake(-cropRect.origin.x , -cropRect.origin.y, self.size.width * self.scale, self.size.height * self.scale);
UIGraphicsBeginImageContext(cropRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, CGRectMake(0, 0, cropRect.size.width, cropRect.size.height));
[self drawInRect:drawRect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
11. 使用drawRect有什么影響?(這個可深可淺,你至少得用過。。)
drawRect方法依賴Core Graphics框架來進行自定義的繪制,但這種方法主要的缺點就是它處理touch事件的方式:每次按鈕被點擊后,都會用setNeddsDisplay進行強制重繪;而且不止一次,每次單點事件觸發兩次執行。這樣的話從性能的角度來說,對CPU和內存來說都是欠佳的。特別是如果在我們的界面上有多個這樣的UIButton實例。
12. ASIHttpRequest或者SDWebImage里面給UIImageView加載圖片的邏輯是什么樣的?
詳見SDWebImage的實現流程 http://www.cnblogs.com/6duxz/p/4159572.html
13. 麻煩你設計個簡單的圖片內存緩存器(移除策略是一定要說的)
圖片的內存緩存,可以考慮將圖片數據保存到一個數據模型中。所以在程序運行時這個模型都存在內存中。
移除策略:釋放數據模型對象。
14. 講講你用Instrument優化動畫性能的經歷吧(別問我什么是Instrument)
可以參考iOS App性能優化
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,那么就是浪費了一些資源而已
參考:http://www.cnblogs.com/dyllove98/archive/2013/06/06/3123005.html
16. viewWillLayoutSubView你總是知道的。
橫豎屏切換的時候,系統會響應一些函數,其中 viewWillLayoutSubviews 和 viewDidLayoutSubviews。
//
- (void)viewWillLayoutSubviews
{
[self _shouldRotateToOrientation:(UIDeviceOrientation)[UIApplication sharedApplication].statusBarOrientation];
}
-(void)_shouldRotateToOrientation:(UIDeviceOrientation)orientation {
if (orientation == UIDeviceOrientationPortrait ||orientation ==
UIDeviceOrientationPortraitUpsideDown) {
// 豎屏
}
else {
// 橫屏
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
通過上述一個函數就知道橫豎屏切換的接口了。
注意: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嗎?讀寫是分線程的嗎?遇到過死鎖沒?咋解決的?
參考:CoreData與SQLite的線程安全
19. http的post和get啥區別?(區別挺多的,麻煩多說點)
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。
POST把提交的數據則放置在是HTTP包的包體中。
2.”GET方式提交的數據最多只能是1024字節,理論上POST沒有限制,可傳較大量的數據,IIS4中最大為80KB,IIS5中為100KB”??!
以上這句是我從其他文章轉過來的,其實這樣說是錯誤的,不準確的:
(1).首先是”GET方式提交的數據最多只能是1024字節”,因為GET是通過URL提交數據,那么GET可提交的數據量就跟URL的長度有直接關系了。而實際上,URL不存在參數上限的問題,HTTP協議規范沒有對URL長度進行限制。這個限制是特定的瀏覽器及服務器對它的限制。IE對URL長度的限制是2083字節(2K+35)。對于其他瀏覽器,如Netscape、FireFox等,理論上沒有長度限制,其限制取決于操作系統的支持。
注意這是限制是整個URL長度,而不僅僅是你的參數值數據長度。[見參考資料5]
(2).理論上講,POST是沒有大小限制的,HTTP協議規范也沒有進行大小限制,說“POST數據量存在80K/100K的大小限制”是不準確的,POST數據是沒有限制的,起限制作用的是服務器的處理程序的處理能力。
3.在ASP中,服務端獲取GET請求參數用Request.QueryString,獲取POST請求參數用Request.Form。在JSP中,用request.getParameter(\”XXXX\”)來獲取,雖然jsp中也有request.getQueryString()方法,但使用起來比較麻煩,比如:傳一個test.jsp?name=hyddd&password=hyddd,用request.getQueryString()得到的是:name=hyddd&password=hyddd。在PHP中,可以用GET和_POST分別獲取GET和POST中的數據,而REQUEST則可以獲取GET和POST兩種請求中的數據。值得注意的是,JSP中使用request和PHP中使用_REQUEST都會有隱患,這個下次再寫個文章總結。
4.POST的安全性要比GET的安全性高。注意:這里所說的安全性和上面GET提到的“安全”不是同個概念。上面“安全”的含義僅僅是不作數據修改,而這里安全的含義是真正的Security的含義,比如:通過GET提交數據,用戶名和密碼將明文出現在URL上,因為(1)登錄頁面有可能被瀏覽器緩存,(2)其他人查看瀏覽器的歷史紀錄,那么別人就可以拿到你的賬號和密碼了,除此之外,使用GET提交數據還可能會造成Cross-site request forgery攻擊。
總結一下,Get是向服務器發索取數據的一種請求,而Post是向服務器提交數據的一種請求,在FORM(表單)中,Method默認為”GET”,實質上,GET和POST只是發送機制不同,并不是一個取一個發!
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、伸展樹等。