1. 什么是 ARC? (ARC 是為了解決什么問題而誕生的?)
ARC 是 Automatic Reference Counting 的縮寫, 即自動引用計數(shù). 這是蘋果在 iOS5 中引入的內(nèi)存管理機制. Objective-C 和 Swift 使用 ARC 追蹤和管理應(yīng)用的內(nèi)存使用. 這一機制使得開發(fā)者無需鍵入 retain 和 release , 這不僅能夠降低程序崩潰和內(nèi)存泄露的風(fēng)險, 而且可以減少開發(fā)者的工作量, 能夠大幅度提升程序的 流暢性 和 可預(yù)測性 . 但是 ARC 不適用于 Core Foundation 框架中, 仍然需要手動管理內(nèi)存.
2. 以下 keywords 有什么區(qū)別: assign vs weak , __block vs __weak
assign 和 weak 是用于在聲明屬性時, 為屬性指定內(nèi)存管理的語義.
assign 用于簡單的賦值, 不改變屬性的引用計數(shù), 用于 Objective-C 中的 NSInteger , CGFloat 以及 C 語言中 int , float , double 等數(shù)據(jù)類型.
weak 用于對象類型, 由于 weak 同樣不改變對象的引用計數(shù)且不持有對象實例, 當(dāng)該對象廢棄時, 該弱引用自動失效并且被賦值為 nil , 所以它可以用于避免兩個強引用產(chǎn)生的 循環(huán)引用 導(dǎo)致內(nèi)存無法釋放的問題.
__block 和 __weak 之間的卻是確實極大的, 不過它們都用于修飾變量.
前者用于指明當(dāng)前聲明的變量在被 block 捕獲之后, 可以在 block 中改變變量的值. 因為在 block 聲明的同時會截獲該 block 所使用的全部自動變量的值, 而這些值只在 block 中 只具有"使用權(quán)"而不具有"修改權(quán)" . 而 __block 說明符就為 block 提供了變量的修改權(quán).
后者是 所有權(quán)修飾符 , 什么是所有權(quán)修飾符? 這里涉及到另一個問題, 因為在 ARC 有效時, id 類型和對象類型同 C 語言中的其他類型不同, 必須附加所有權(quán)修飾符. 所有權(quán)修飾符一種有 4 種:
__strong
__weak
__unsafe_unretained
__autorelease
__weak 與 weak 的區(qū)別只在于, 前者用于變量的聲明, 而后者用于屬性的聲明.
3. __block 在 ARC 和非 ARC 下含義一樣嗎毫捣?
__block 在 ARC 下捕獲的變量會被 block retain , 這樣可能導(dǎo)致循環(huán)引用, 所以必須要使用弱引用才能解決該問題. 而在非 ARC 下, 可以直接使用 __block 說明符修飾變量, 因為在非 ARC 下, block 不會 retain 捕獲的變量.
4. 使用 nonatomic 一定是線程安全的嗎?
nonatomic 的內(nèi)存管理語義是 非原子 的, 非原子的操作本來就是線程不安全的, 而 atomic 的操作是原子的, 但是 并不意味著它是線程安全的 , 它會增加正確的幾率, 能夠更好的避免線程的錯誤, 但是它仍然是線程不安全的.
當(dāng)使用 nonatomic 的時候, 屬性的 setter 和 getter 操作是非原子的, 所以當(dāng)多個線程同時對某一屬性進行讀和寫的操作, 屬性的最終結(jié)果是不能預(yù)測的.
當(dāng)使用 atomic 時, 雖然對屬性的讀和寫是原子的, 但是仍然可能出現(xiàn)線程錯誤: 當(dāng)線程 A 進行寫操作, 這時其他線程的讀或?qū)懖僮鲿驗樵摬僮鞯倪M行而等待. 當(dāng) A 線程的寫操作結(jié)束后, B 線程進行寫操作, 然后當(dāng) A 線程進行讀操作時, 卻獲得了在 B 線程中的值, 這就破壞了線程安全, 如果有線程 C 在 A 線程讀操作前 release 了該屬性, 那么還會導(dǎo)致程序崩潰. 所以僅僅使用 atomic 并不會使得線程安全, 我們還需要為線程添加 lock 來確保線程的安全.
atomic 都不是一定線程安全的, nonatomic 就更不必多說了.
5. 描述一個你遇到過的 retain cycle 例子.
6. + (void)load; 和 + (void)initialize; 有什么用處想际?
當(dāng)類對象被引入項目時, runtime 會向每一個類對象發(fā)送 load 消息. load 方法還是非常的神奇的, 因為它會在 每一個類甚至分類 被引入時僅調(diào)用一次, 調(diào)用的順序是父類優(yōu)先于子類, 子類優(yōu)先于分類. 而且 load 方法不會被類自動繼承, 每一個類中的 load 方法都不需要像 viewDidLoad 方法一樣調(diào)用父類的方法. 由于 load 方法會在類被 import 時調(diào)用一次, 而這時往往是改變類的行為的最佳時機. 我在 DKNightVersion 中使用 method swizlling 來修改原有的方法時, 就是在分類 load 中實現(xiàn)的.
initialize 方法和 load 方法有一些不同, 它雖然也會在整個 runtime 過程中調(diào)用一次, 但是它是在 該類的第一個方法執(zhí)行之前 調(diào)用, 也就是說 initialize 的調(diào)用是 惰性 的, 它的實現(xiàn)也與我們在平時使用的惰性初始化屬性時基本相同. 我在實際的項目中并沒有遇到過必須使用這個方法的情況, 在該方法中主要做 靜態(tài)變量的設(shè)置 并用于 確保在實例初始化前某些條件必須滿足 .
7. 為什么其他語言里叫函數(shù)調(diào)用, Objective-C 中是給對象發(fā)送消息 (談下對 runtime 的理解)
我們在其他語言中比如說: C, Python, Java, C++, Haskell ... 中提到函數(shù)調(diào)用或者方法調(diào)用(面向?qū)ο?. 函數(shù)調(diào)用是在編譯期就已經(jīng)決定了會調(diào)用哪個函數(shù)(方法), 編譯器在編譯期就能檢查出函數(shù)的執(zhí)行是否正確.
然而 Objective-C(ObjC) 是一門動態(tài)的語言, 整個 ObjC 語言都是盡可能的將所有的工作推遲到運行時才決定. 它基于 runtime 來工作, runtime 就是 ObjC 的靈魂, 其核心就是消息發(fā)送 objc_msgSend .
What makes Objective-C truly powerful is its runtime.
所有的消息都會在運行時才會確定, [obj message] 在運行時會被轉(zhuǎn)化為 objc_msgSend(id self, SEL cmd, ...) 來執(zhí)行, 它會在運行時從 選擇子表中尋找對應(yīng)的選擇子 并將選擇子與實現(xiàn)進行綁定. 而如果沒有找到對應(yīng)的實現(xiàn), 就會進入類似黑魔法的消息轉(zhuǎn)發(fā)流程. 調(diào)用 + (BOOL)resolveInstanceMethod:(SEL)aSelector 方法, 我們可以在這個方法中 為類動態(tài)地生成方法 .
我們幾乎可以使用 runtime 魔改 Objective-C 中的一切: class property object ivar method protocol , 而下面就是它的主要應(yīng)用:
內(nèi)省
為分類動態(tài)的添加屬性
使用方法調(diào)劑修改原有的方法實現(xiàn)
...
8. 什么是 Method Swizzling?
method swizzling 實際上就是一種在運行時動態(tài)修改原有方法的技術(shù), 它實際上是基于 ObjC runtime 的特性, 而 method swizzling 的核心方法就是 method_exchangeImplementations(SEL origin, SEL swizzle) . 使用這個方法就可以在運行時動態(tài)地改變原有的方法實現(xiàn), 在 DKNigtVersion (為 iOS 應(yīng)用添加夜間模式) 中能夠看到大量 method swizzling 的使用, 方法的調(diào)用時機就是在上面提到的 load 方法中, 不在 initialize 方法中改變方法實現(xiàn)的原因是 initialize 可能會被子類所繼承并重新執(zhí)行最終導(dǎo)致錯誤 , 而 load 并不會被繼承并重新執(zhí)行.
9. UIView 和 CALayer 有什么關(guān)系?
看到這個問題不禁想到大一在網(wǎng)易面試時的經(jīng)歷, 當(dāng)時的兩位面試官就問了我這么一個問題, UIView 和 CALayer 是什么關(guān)系, 為什么要這么設(shè)計? 我已經(jīng)忘記了當(dāng)時是怎么回答的. 隱約記得當(dāng)時說每一個 UIView 都會對應(yīng)一個 CALayer 至于為什么這樣, 當(dāng)時的我實在是太弱無法回答出來了.
每一個 UIView 的身后對應(yīng)一個 Core Animation 框架中的 CALayer .
Many of the methods you call on UIView simply delegate to the layer
在 iOS 上 當(dāng)你處理一個一個有一個的 UIView 時實際上是在操作 CALayer . 盡管有的時候你并不知道 (直接操作 CALayer 并不會在對效率有著顯著的提升).
UIView 實際上就是對 CALayer 的輕量級的封裝. UIView 繼承自 UIResponder 處理來自用戶的事件; CALayer 繼承自 NSObject 主要用于圖層的渲染和動畫. 這么設(shè)計有以下幾個原因:
你可以通過操作 UIView 在一個更高的層級上處理與用戶的交互, 觸摸, 點擊, 拖拽等事件, 這些都是在 UIKit 這個層級上完成的.
UIView 和 NSView(AppKit) 的實現(xiàn)極其不同, 而使用 Core Animation 可以實現(xiàn)底層代碼地重用, 因為在 Mac 和 iOS 平臺上都使用著近乎相同的 Core Animation 代碼, 這樣我們可以對這個層級進行抽象在兩種平臺上產(chǎn)生 UIKit 和 AppKit 用于不同平臺的框架.
使用 CALayer 的唯一原因大概是便于移植到不同的平臺, 如果僅僅使用 Core Animation 層級, 處理用戶的交互時間需要寫更多的代碼.
10. 如何高性能的給 UIImageView 加個圓角? (不準(zhǔn)說 layer.cornerRadius !)
一般情況下給 UIImageView 或者說 UIKit 的控件添加圓角都是改變 clipsToBounds 和 layer.cornerRadius , 這樣大約兩行代碼就可以解決. 但是, 這樣使用這樣的方法會 強制 Core Animation 提前渲染屏幕的離屏繪制 , 而離屏繪制就會為性能帶來負面影響.
我們也可以使用另一種比較復(fù)雜的方式來為圖片添加圓角, 這里就用到了貝塞爾曲線.
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 中曾經(jīng)使用這個方法來繪制圖形, 但是具體怎么做的, 我已經(jīng)忘記了.
這個方法的主要作用是根據(jù)傳入的 rect 來繪制圖像 參見文檔 . 這個方法的默認實現(xiàn)沒有做任何事情, 我們 可以 在這個方法中使用 Core Graphics 和 UIKit 來繪制視圖的內(nèi)容.
這個方法的調(diào)用機制也是非常特別. 當(dāng)你調(diào)用 setNeedsDisplay 方法時, UIKit 將會把當(dāng)前圖層標(biāo)記為 dirty, 但還是會顯示原來的內(nèi)容, 直到下一次的視圖渲染周期, 才會重新建立 Core Graphics 上下文, 然后將內(nèi)存中的數(shù)據(jù)恢復(fù)出來, 使用 CGContextRef 進行繪制.
12. ASIHttpRequest 或者 SDWebImage 里面給 UIImageView 加載圖片的邏輯是什么樣的?
我曾經(jīng)閱讀過 SDWebImage 的源代碼, 就在這里對如何給 UIImageView 加載圖片做一個總結(jié)吧, SDWebImage 中為 UIView 提供了一個分類叫做 WebCache, 這個分類中有一個最常用的接口, sd_setImageWithURL:placeholderImage: , 這個分類同時提供了很多類似的方法, 這些方法最終會調(diào)用一個同時具有 option progressBlock completionBlock 的方法, 而在這個類最終被調(diào)用的方法首先會檢查是否傳入了 placeholderImage 以及對應(yīng)的參數(shù), 并設(shè)置 placeholderImage .
然后會獲取 SDWebImageManager 中的單例調(diào)用一個 downloadImageWithURL:... 的方法來獲取圖片, 而這個 manager 獲取圖片的過程有大體上分為兩部分, 它首先會在 SDWebImageCache 中尋找圖片是否有對應(yīng)的緩存, 它會以 url 作為數(shù)據(jù)的索引先在內(nèi)存中尋找是否有對應(yīng)的緩存, 如果緩存未命中就會在磁盤中利用 MD5 處理過的 key 來繼續(xù)查詢對應(yīng)的數(shù)據(jù), 如果找到了, 就會把磁盤中的緩存?zhèn)浞莸絻?nèi)存中.
然而, 假設(shè)我們在內(nèi)存和磁盤緩存中都沒有命中, 那么 manager 就會調(diào)用它持有的一個 SDWebImageDownloader 對象的方法 downloadImageWithURL:... 來下載圖片, 這個方法會在執(zhí)行的過程中調(diào)用另一個方法 addProgressCallback:andCompletedBlock:fotURL:createCallback: 來存儲下載過程中和下載完成的回調(diào), 當(dāng)回調(diào)塊是第一次添加的時候, 方法會實例化一個 NSMutableURLRequest 和 SDWebImageDownloaderOperation , 并將后者加入 downloader 持有的下載隊列開始圖片的異步下載.
而在圖片下載完成之后, 就會在主線程設(shè)置 image, 完成整個圖像的異步下載和配置.
13. 設(shè)計一個簡單的圖片內(nèi)存緩存器 (包含移除策略)
待我閱讀完 path 開源的 FastImageCache 的源代碼就來回答.
14. 講講你用Instrument優(yōu)化動畫性能的經(jīng)歷
15. loadView 的作用是什么?
This method loads or creates a view and assigns it to the view property.
loadView 是 UIViewController 的實例方法, 我們永遠不要直接調(diào)用這個方法 [self loadView] . 這在蘋果的 官方文檔 中已經(jīng)明確的寫出了. loadView 會在獲取視圖控制器的 view 但是卻得到 nil 時被調(diào)用.
loadView 的具體實現(xiàn)會做下面兩件事情中的一件:
如果你的視圖控制器關(guān)聯(lián)了一個 storyboard, 那么它就會加載 storyboard 中的視圖.
如果試圖控制器沒有關(guān)聯(lián)的 storyboard, 那么就會創(chuàng)建一個空的視圖, 并分配給 view 屬性
如果你需要覆寫 loadView 方法:
你需要創(chuàng)建一個根視圖.
創(chuàng)建并初始化 view 的子視圖, 調(diào)用 addSubview: 方法將它們添加到父視圖上.
如果你使用了自動布局, 提供足夠的約束來保證視圖的位置.
將根視圖分配給 view 屬性.
永遠不要在這個方法中調(diào)用 [super loadView] .
16. viewWillLayoutSubviews 的作用是什么?
viewWillLayoutSubviews 方法會在視圖的 bounds 改變時, 視圖會調(diào)整子視圖的位置, 我們可以在視圖控制器中覆寫這個方法在視圖放置子視圖前做出改變, 當(dāng)屏幕的方向改變時, 這個方法會被調(diào)用.
17. GCD 里面有哪幾種 Queue? 背后的線程模型是什么樣的?
GCD 中 Queue 的種類還要看我們怎么進行分類, 如果根據(jù)同一時間內(nèi)處理的操作數(shù)分類的話, GCD 中的 Queue 分為兩類
Serial Dispatch Queue
Concurrent Dispatch Queue
一類是串行派發(fā)隊列, 它只使用一個線程, 會等待當(dāng)前執(zhí)行的操作結(jié)束后才會執(zhí)行下一個操作, 它按照追加的順序進行處理. 另一類是并行派發(fā)隊列, 它同時使用多個線程, 如果當(dāng)前的線程數(shù)足夠, 那么就不會等待正在執(zhí)行的操作, 使用多個線程同時執(zhí)行多個處理.
另外的一種分類方式如下:
Main Dispatch Queue
Global Dispatch Queue
Custom Dispatch Queue
主線程只有一個, 它是一個串行的進程. 所有追加到 Main Dispatch Queue 中的處理都會在 RunLoop 在執(zhí)行. Global Dispatch Queue 是所有應(yīng)用程序都能使用的并行派發(fā)隊列, 它有 4 個執(zhí)行優(yōu)先級 High, Default, Low, Background. 當(dāng)然我們也可以使用 dispatch_queue_create 創(chuàng)建派發(fā)隊列.
18. Core Data 或者 sqlite 的讀寫是分線程的嗎? 死鎖如何解決?
Core Data 和 sqlite 這兩個我還真沒深入用過, 我只在小的玩具應(yīng)用上使用過 Core Data, 但是發(fā)現(xiàn)這貨實在是太難用了, 我就果斷放棄了, sqlite 我也用過, 每次輸入 SQL 語句的時候我多想吐槽, 寫一些簡單的還好, 復(fù)雜的就直接 Orz 了. 所以我一般會使用 levelDB 對進行數(shù)據(jù)的持久存儲.
數(shù)據(jù)庫讀取操作一般都是多線程的, 在對數(shù)據(jù)進行讀取的時候, 我們要確保當(dāng)前的狀態(tài)不會被修改, 所以加鎖, 防止由于線程競爭而出現(xiàn)的錯誤. 在 Core Data 中使用并行的最重要的規(guī)則是: 每一個 NSManagedObjectContext 必須只從創(chuàng)建它的進程中訪問 .
19. http 的 POST 和 GET 有什么區(qū)別?
根據(jù) HTTP 協(xié)議的定義 GET 類型的請求是冪等的, 而 POST 請求是有副作用的, 也就是說 GET 用于獲取一些資源, 而 POST 用于改變一些資源, 這可能會創(chuàng)建新的資源或更新已有的資源.
POST 請求比 GET 請求更加的安全, 因為你不會把信息添加到 URL 上的查詢字符串上. 所以使用 GET 來收集密碼或者一些敏感信息并不是什么好主意.
最后, POST 請求比 GET 請求也可以傳輸更多的信息.
20. 什么是 Binary search tree, 它的時間復(fù)雜度是多少?
二叉搜索樹是一棵以二叉樹來組織的, 它搜索的時間復(fù)雜度 $O(h)$ 與樹的高度成正比, 最壞的運行時間是 $\\Theta(\\lg n)$.
1.什么是arc培漏?(arc是為了解決什么問題誕生的?)
首先解釋ARC: automatic reference counting自動引用計數(shù)胡本。
ARC幾個要點:
在對象被創(chuàng)建時 retain count +1牌柄,在對象被release時 retain count -1.當(dāng)retain count 為0 時,銷毀對象侧甫。
程序中加入autoreleasepool的對象會由系統(tǒng)自動加上autorelease方法珊佣,如果該對象引用計數(shù)為0,則銷毀披粟。
那么ARC是為了解決什么問題誕生的呢咒锻?這個得追溯到MRC手動內(nèi)存管理時代說起。
MRC下內(nèi)存管理的缺點:
1.當(dāng)我們要釋放一個堆內(nèi)存時守屉,首先要確定指向這個堆空間的指針都被release了惑艇。(避免提前釋放)
2.釋放指針指向的堆空間,首先要確定哪些指針指向同一個堆拇泛,這些指針只能釋放一次滨巴。(MRC下即誰創(chuàng)建,誰釋放俺叭,避免重復(fù)釋放)
3.模塊化操作時恭取,對象可能被多個模塊創(chuàng)建和使用,不能確定最后由誰去釋放熄守。
4.多線程操作時蜈垮,不確定哪個線程最后使用完畢
2.請解釋以下keywords的區(qū)別: assign vs weak, __block vs __weak
assign適用于基本數(shù)據(jù)類型,weak是適用于NSObject對象裕照,并且是一個弱引用攒发。
assign其實也可以用來修飾對象,那么我們?yōu)槭裁床挥盟亟希恳驗楸籥ssign修飾的對象在釋放之后晨继,指針的地址還是存在的,也就是說指針并沒有被置為nil搬俊。如果在后續(xù)的內(nèi)存分配中紊扬,剛好分到了這塊地址蜒茄,程序就會崩潰掉。
而weak修飾的對象在釋放之后餐屎,指針地址會被置為nil檀葛。所以現(xiàn)在一般弱引用就是用weak。
首先__block是用來修飾一個變量腹缩,這個變量就可以在block中被修改(參考block實現(xiàn)原理)
__block:使用__block修飾的變量在block代碼快中會被retain(ARC下屿聋,MRC下不會retain)
__weak:使用__weak修飾的變量不會在block代碼塊中被retain
同時,在ARC下藏鹊,要避免block出現(xiàn)循環(huán)引用 __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會自動設(shè)成nil)
而後者是ARC的環(huán)境下為了相容4.x的解決方案竿痰。
所以上面的範(fàn)例中
__block MyClass* temp = …;? ? // MRC環(huán)境下使用
__weak MyClass* temp = …;? ? // ARC但只支援iOS5.0以上的版本
__unsafe_retained MyClass* temp = …;? //ARC且可以相容4.x以後的版本
4.使用nonatomic一定是線程安全的嗎脆粥?()
不是的。
atomic原子操作影涉,系統(tǒng)會為setter方法加鎖变隔。 具體使用 @synchronized(self){//code }
nonatomic不會為setter方法加鎖。
atomic:線程安全蟹倾,需要消耗大量系統(tǒng)資源來為屬性加鎖
nonatomic:非線程安全匣缘,適合內(nèi)存較小的移動設(shè)備
5.描述一個你遇到過的retain cycle例子。
block中的循環(huán)引用:一個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會自動調(diào)用每個類的兩個方法夏哭。+load會在類初始加載時調(diào)用检柬,+initialize會在第一次調(diào)用類的類方法或?qū)嵗椒ㄖ氨徽{(diào)用献联。這兩個方法是可選的,且只有在實現(xiàn)了它們時才會被調(diào)用何址。
共同點:兩個方法都只會被調(diào)用一次里逆。
7.為什么其他語言里叫函數(shù)調(diào)用, objective c里則是給對象發(fā)消息(或者談下對runtime的理解)
先來看看怎么理解發(fā)送消息的含義:
曾經(jīng)覺得Objc特別方便上手用爪,面對著 Cocoa 中大量 API原押,只知道簡單的查文檔和調(diào)用。還記得初學(xué) Objective-C 時把[receiver message]當(dāng)成簡單的方法調(diào)用偎血,而無視了“發(fā)送消息”這句話的深刻含義诸衔。于是[receiver message]會被編譯器轉(zhuǎn)化為:
objc_msgSend(receiver, selector)
如果消息含有參數(shù)盯漂,則為:
objc_msgSend(receiver, selector, arg1, arg2, ...)
如果消息的接收者能夠找到對應(yīng)的selector,那么就相當(dāng)于直接執(zhí)行了接收者這個對象的特定方法笨农;否則就缆,消息要么被轉(zhuǎn)發(fā),或是臨時向接收者動態(tài)添加這個selector對應(yīng)的實現(xiàn)內(nèi)容谒亦,要么就干脆玩完崩潰掉竭宰。
現(xiàn)在可以看出[receiver message]真的不是一個簡簡單單的方法調(diào)用。因為這只是在編譯階段確定了要向接收者發(fā)送message這條消息份招,而receive將要如何響應(yīng)這條消息切揭,那就要看運行時發(fā)生的情況來決定了。
Objective-C 的 Runtime 鑄就了它動態(tài)語言的特性锁摔,這些深層次的知識雖然平時寫代碼用的少一些廓旬,但是卻是每個 Objc 程序員需要了解的。
Objc Runtime使得C具有了面向?qū)ο竽芰Ρ陕诔绦蜻\行時創(chuàng)建嗤谚,檢查,修改類怔蚌、對象和它們的方法巩步。可以使用runtime的一系列方法實現(xiàn)桦踊。
順便附上OC中一個類的數(shù)據(jù)結(jié)構(gòu) /usr/include/objc/runtime.h
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指針指向Meta Class椅野,因為Objc的類的本身也是一個Object,為了處理這個關(guān)系籍胯,r? ? ? untime就創(chuàng)造了Meta Class竟闪,當(dāng)給類發(fā)送[NSObject alloc]這樣消息時,實際上是把這個消息發(fā)給了Class Object
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息杖狼,默認為0
long info OBJC2_UNAVAILABLE; // 類信息炼蛤,供運行期使用的一些位標(biāo)識
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; // 方法緩存,對象接到一個消息會根據(jù)isa指針查找消息對象蝶涩,這時會在method? ? ? Lists中遍歷理朋,如果cache了,常用的方法調(diào)用時就能夠提高調(diào)用的效率绿聘。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
OC中一個類的對象實例的數(shù)據(jù)結(jié)構(gòu)(/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發(fā)送消息時嗽上,Runtime庫會根據(jù)object的isa指針找到這個實例object所屬于的類,然后在類的方法列表以及父類方法列表尋找對應(yīng)的方法運行熄攘。id是一個objc_object結(jié)構(gòu)類型的指針兽愤,這個類型的對象能夠轉(zhuǎn)換成任何一種對象。
然后再來看看消息發(fā)送的函數(shù):objc_msgSend函數(shù)
在引言中已經(jīng)對objc_msgSend進行了一點介紹,看起來像是objc_msgSend返回了數(shù)據(jù)浅萧,其實objc_msgSend從不返回數(shù)據(jù)而是你的方法被調(diào)用后返回了數(shù)據(jù)逐沙。下面詳細敘述下消息發(fā)送步驟:
檢測這個 selector 是不是要忽略的。比如 Mac OS X 開發(fā)洼畅,有了垃圾回收就不理會 retain,release 這些函數(shù)了酱吝。
檢測這個 target 是不是 nil 對象。ObjC 的特性是允許對一個 nil 對象執(zhí)行任何一個方法不會 Crash土思,因為會被忽略掉务热。
如果上面兩個都過了,那就開始查找這個類的 IMP己儒,先從 cache 里面找崎岂,完了找得到就跳到對應(yīng)的函數(shù)去執(zhí)行。
如果 cache 找不到就找一下方法分發(fā)表闪湾。
如果分發(fā)表找不到就到超類的分發(fā)表去找冲甘,一直找,直到找到NSObject類為止途样。
如果還找不到就要開始進入動態(tài)方法解析了江醇,后面會提到。
后面還有:
動態(tài)方法解析resolveThisMethodDynamically
消息轉(zhuǎn)發(fā)forwardingTargetForSelector
詳情可參考 http://www.reibang.com/p/620022378e97
8.什么是method swizzling?
Method Swizzling 原理(方法攪拌何暇?)
在Objective-C中調(diào)用一個方法陶夜,其實是向一個對象發(fā)送消息,查找消息的唯一依據(jù)是selector的名字裆站。利用Objective-C的動態(tài)特性条辟,可以實現(xiàn)在運行時偷換selector對應(yīng)的方法實現(xiàn),達到給方法掛鉤的目的宏胯。
每個類都有一個方法列表羽嫡,存放著selector的名字和方法實現(xiàn)的映射關(guān)系。IMP有點類似函數(shù)指針肩袍,指向具體的Method實現(xiàn)杭棵。
方法指向
我們可以利用 method_exchangeImplementations 來交換2個方法中的IMP,
我們可以利用 class_replaceMethod 來修改類氛赐,
我們可以利用 method_setImplementation 來直接設(shè)置某個方法的IMP魂爪,
……
歸根結(jié)底,都是偷換了selector的IMP鹰祸,如下圖所示:
方法交換
詳情:http://blog.csdn.net/yiyaaixuexi/article/details/9374411
9.UIView和CALayer是啥關(guān)系甫窟?
1.UIView是iOS系統(tǒng)中界面元素的基礎(chǔ)密浑,所有的界面元素都繼承自它蛙婴。它本身完全是由CoreAnimation來實現(xiàn)的 (Mac下似乎不是這樣)。它真正的繪圖部分尔破,是由一個叫CALayer(Core Animation Layer)的類來管理街图。 UIView本身浇衬,更像是一個CALayer的管理器,訪問它的跟繪圖和跟坐標(biāo)有關(guān)的屬性餐济,例如frame耘擂,bounds等 等,實際上內(nèi)部都是在訪問它所包含的CALayer的相關(guān)屬性絮姆。
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樹形結(jié)構(gòu)矮燎,也可以向它的layer上添加子layer定血,來完成某些特殊的表 示。例如下面的代碼
grayCover = [[CALayer alloc] init];
grayCover.backgroundColor = [[[UIColor blackColor] colorWithAlphaComponent:0.2] CGColor];
[self.layer addSubLayer: grayCover];
1
2
3
4
5
會在目標(biāo)View上敷上一層黑色的透明薄膜诞外。
4.UIView的layer樹形在系統(tǒng)內(nèi)部澜沟,被系統(tǒng)維護著三份copy(這段理解有點吃不準(zhǔn))。
邏輯樹峡谊,就是代碼里可以操縱的倔喂,例如更改layer的屬性等等就在這一份。
動畫樹靖苇,這是一個中間層席噩,系統(tǒng)正在這一層上更改屬性,進行各種渲染操作贤壁。
顯示樹悼枢,這棵樹的內(nèi)容是當(dāng)前正被顯示在屏幕上的內(nèi)容。
這三棵樹的邏輯結(jié)構(gòu)都是一樣的脾拆,區(qū)別只有各自的屬性馒索。
10. 如何高性能的給UIImageView加個圓角?(不準(zhǔn)說layer.cornerRadius!)
我覺得應(yīng)該是使用Quartz2D直接繪制圖片,得把這個看看名船。
步驟:
a绰上、創(chuàng)建目標(biāo)大小(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進行強制重繪;而且不止一次获印,每次單點事件觸發(fā)兩次執(zhí)行述雾。這樣的話從性能的角度來說,對CPU和內(nèi)存來說都是欠佳的兼丰。特別是如果在我們的界面上有多個這樣的UIButton實例玻孟。
12. ASIHttpRequest或者SDWebImage里面給UIImageView加載圖片的邏輯是什么樣的?
詳見SDWebImage的實現(xiàn)流程 http://www.cnblogs.com/6duxz/p/4159572.html
13. 麻煩你設(shè)計個簡單的圖片內(nèi)存緩存器(移除策略是一定要說的)
圖片的內(nèi)存緩存鳍征,可以考慮將圖片數(shù)據(jù)保存到一個數(shù)據(jù)模型中黍翎。所以在程序運行時這個模型都存在內(nèi)存中撇叁。
移除策略:釋放數(shù)據(jù)模型對象验辞。
14. 講講你用Instrument優(yōu)化動畫性能的經(jīng)歷吧(別問我什么是Instrument)
可以參考iOS App性能優(yōu)化
15. loadView是干嘛用的诈嘿?
當(dāng)你訪問一個ViewController的view屬性時帘睦,如果此時view的值是nil,那么慕蔚,ViewController就會自動調(diào)用loadView這個方法梧乘。這個方法就會加載或者創(chuàng)建一個view對象趟济,賦值給view屬性戴差。
loadView默認做的事情是:如果此ViewController存在一個對應(yīng)的nib文件送爸,那么就加載這個nib。否則暖释,就創(chuàng)建一個UIView對象袭厂。
如果你用Interface Builder來創(chuàng)建界面,那么不應(yīng)該重載這個方法球匕。
如果你想自己創(chuàng)建view對象纹磺,那么可以重載這個方法。此時你需要自己給view屬性賦值亮曹。你自定義的方法不應(yīng)該調(diào)用super橄杨。如果你需要對view做一些其他的定制操作秘症,在viewDidLoad里面去做。
=========================================
根據(jù)上面的文檔可以知道讥珍,有兩種情況:
1、如果你用了nib文件窄瘟,重載這個方法就沒有太大意義衷佃。因為loadView的作用就是加載nib。如果你重載了這個方法不調(diào)用super蹄葱,那么nib文件就不會被加載氏义。如果調(diào)用了super,那么view已經(jīng)加載完了图云,你需要做的其他事情在viewDidLoad里面做更合適惯悠。
2、如果你沒有用nib竣况,這個方法默認就是創(chuàng)建一個空的view對象克婶。如果你想自己控制view對象的創(chuàng)建,例如創(chuàng)建一個特殊尺寸的view丹泉,那么可以重載這個方法情萤,自己創(chuàng)建一個UIView對象,然后指定 self.view = myView; 但這種情況也沒有必要調(diào)用super摹恨,因為反正你也不需要在super方法里面創(chuàng)建的view對象筋岛。如果調(diào)用了super,那么就是浪費了一些資源而已
參考:http://www.cnblogs.com/dyllove98/archive/2013/06/06/3123005.html
16. viewWillLayoutSubView你總是知道的晒哄。
橫豎屏切換的時候睁宰,系統(tǒng)會響應(yīng)一些函數(shù),其中 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
通過上述一個函數(shù)就知道橫豎屏切換的接口了柒傻。
注意:viewWillLayoutSubviews只能用在ViewController里面,在view里面沒有響應(yīng)较木。
17. GCD里面有哪幾種Queue诅愚?你自己建立過串行queue嗎?背后的線程模型是什么樣的劫映?
1.主隊列 dispatch_main_queue(); 串行 违孝,更新UI
2.全局隊列 dispatch_global_queue(); 并行,四個優(yōu)先級:background泳赋,low雌桑,default,high
3.自定義隊列 dispatch_queue_t queue ; 可以自定義是并行:DISPATCH_QUEUE_CONCURRENT或者串行DISPATCH_QUEUE_SERIAL
18. 用過coredata或者sqlite嗎祖今?讀寫是分線程的嗎校坑?遇到過死鎖沒拣技?咋解決的?
參考:CoreData與SQLite的線程安全
19. http的post和get啥區(qū)別耍目?(區(qū)別挺多的膏斤,麻煩多說點)
1.GET請求的數(shù)據(jù)會附在URL之后(就是把數(shù)據(jù)放置在HTTP協(xié)議頭中),以?分割URL和傳輸數(shù)據(jù)邪驮,參數(shù)之間以&相連莫辨,如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0%E5%A5%BD。如果數(shù)據(jù)是英文字母/數(shù)字毅访,原樣發(fā)送沮榜,如果是空格,轉(zhuǎn)換為+喻粹,如果是中文/其他字符蟆融,則直接把字符串用BASE64加密,得出如:%E4%BD%A0%E5%A5%BD守呜,其中%XX中的XX為該符號以16進制表示的ASCII型酥。
POST把提交的數(shù)據(jù)則放置在是HTTP包的包體中。
2.”GET方式提交的數(shù)據(jù)最多只能是1024字節(jié)查乒,理論上POST沒有限制冕末,可傳較大量的數(shù)據(jù),IIS4中最大為80KB侣颂,IIS5中為100KB”档桃??憔晒!
以上這句是我從其他文章轉(zhuǎn)過來的藻肄,其實這樣說是錯誤的,不準(zhǔn)確的:
(1).首先是”GET方式提交的數(shù)據(jù)最多只能是1024字節(jié)”拒担,因為GET是通過URL提交數(shù)據(jù)嘹屯,那么GET可提交的數(shù)據(jù)量就跟URL的長度有直接關(guān)系了。而實際上从撼,URL不存在參數(shù)上限的問題州弟,HTTP協(xié)議規(guī)范沒有對URL長度進行限制。這個限制是特定的瀏覽器及服務(wù)器對它的限制低零。IE對URL長度的限制是2083字節(jié)(2K+35)婆翔。對于其他瀏覽器,如Netscape掏婶、FireFox等啃奴,理論上沒有長度限制,其限制取決于操作系統(tǒng)的支持雄妥。
注意這是限制是整個URL長度最蕾,而不僅僅是你的參數(shù)值數(shù)據(jù)長度依溯。[見參考資料5]
(2).理論上講,POST是沒有大小限制的瘟则,HTTP協(xié)議規(guī)范也沒有進行大小限制黎炉,說“POST數(shù)據(jù)量存在80K/100K的大小限制”是不準(zhǔn)確的,POST數(shù)據(jù)是沒有限制的醋拧,起限制作用的是服務(wù)器的處理程序的處理能力慷嗜。
3.在ASP中,服務(wù)端獲取GET請求參數(shù)用Request.QueryString趁仙,獲取POST請求參數(shù)用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中的數(shù)據(jù)辕羽,而REQUEST則可以獲取GET和POST兩種請求中的數(shù)據(jù)。值得注意的是垄惧,JSP中使用request和PHP中使用_REQUEST都會有隱患刁愿,這個下次再寫個文章總結(jié)。
4.POST的安全性要比GET的安全性高到逊。注意:這里所說的安全性和上面GET提到的“安全”不是同個概念铣口。上面“安全”的含義僅僅是不作數(shù)據(jù)修改,而這里安全的含義是真正的Security的含義觉壶,比如:通過GET提交數(shù)據(jù)脑题,用戶名和密碼將明文出現(xiàn)在URL上,因為(1)登錄頁面有可能被瀏覽器緩存铜靶,(2)其他人查看瀏覽器的歷史紀(jì)錄叔遂,那么別人就可以拿到你的賬號和密碼了,除此之外争剿,使用GET提交數(shù)據(jù)還可能會造成Cross-site request forgery攻擊已艰。
總結(jié)一下,Get是向服務(wù)器發(fā)索取數(shù)據(jù)的一種請求蚕苇,而Post是向服務(wù)器提交數(shù)據(jù)的一種請求旗芬,在FORM(表單)中,Method默認為”GET”捆蜀,實質(zhì)上疮丛,GET和POST只是發(fā)送機制不同幔嫂,并不是一個取一個發(fā)!
20. 我知道你大學(xué)畢業(yè)過后就沒接觸過算法數(shù)據(jù)結(jié)構(gòu)了誊薄,但是請你一定告訴我什么是Binary search tree? search的時間復(fù)雜度是多少履恩?
Binary search tree:二叉搜索樹。
主要由四個方法:(用C語言實現(xiàn)或者Python)
1.search:時間復(fù)雜度為O(h)呢蔫,h為樹的高度
2.traversal:時間復(fù)雜度為O(n)切心,n為樹的總結(jié)點數(shù)。
3.insert:時間復(fù)雜度為O(h)片吊,h為樹的高度绽昏。
4.delete:最壞情況下,時間復(fù)雜度為O(h)+指針的移動開銷俏脊。
可以看到全谤,二叉搜索樹的dictionary operation的時間復(fù)雜度與樹的高度h相關(guān)。所以需要盡可能的降低樹的高度爷贫,由此引出平衡二叉樹Balanced binary tree认然。它要求左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹漫萄。這樣就可以將搜索樹的高度盡量減小卷员。常用算法有紅黑樹、AVL腾务、Treap毕骡、伸展樹等。