無標題文章

http://blog.csdn.net/david21984/article/details/57451917

iOS 面試題(一)尋找最近公共 View

題目:找出兩個 UIView 的最近的公共 View,如果不存在,則輸出 nil 。




分析:這其實是數據結構里面的找最近公共祖先的問題。

一個UIViewController中的所有view之間的關系其實可以看成一顆樹,UIViewController的view變量是這顆樹的根節點,其它的view都是根節點的直接或間接子節點。

所以我們可以通過 view 的 superview 屬性,一直找到根節點。需要注意的是,在代碼中,我們還需要考慮各種非法輸入,如果輸入了 nil,則也需要處理,避免異常。以下是找到指定 view 到根 view 的路徑代碼:

+ (NSArray*)superViews:(UIView*)view {if(view ==nil) {return@[];? ? }NSMutableArray*result = [NSMutableArrayarray];while(view !=nil) {? ? ? ? [result addObject:view];? ? ? ? view = view.superview;? ? }return[resultcopy];}

然后對于兩個 view A 和 view B,我們可以得到兩個路徑,而本題中我們要找的是這里面最近的一個公共節點。

一個簡單直接的辦法:拿第一個路徑中的所有節點,去第二個節點中查找。假設路徑的平均長度是 N,因為每個節點都要找 N 次,一共有 N 個節點,所以這個辦法的時間復雜度是 O(N^2)。

+ (UIView*)commonView_1:(UIView*)viewA andView:(UIView*)viewB {NSArray*arr1 = [selfsuperViews:viewA];NSArray*arr2 = [selfsuperViews:viewB];for(NSUIntegeri =0; i < arr1.count; ++i) {UIView*targetView = arr1[i];for(NSUIntegerj =0; j < arr2.count; ++j) {if(targetView == arr2[j]) {returntargetView;? ? ? ? ? ? }? ? ? ? }? ? }returnnil;}

一個改進的辦法:我們將一個路徑中的所有點先放進 NSSet 中。因為 NSSet 的內部實現是一個 hash 表,所以查找元素的時間復雜度變成了 O(1),我們一共有 N 個節點,所以總時間復雜度優化到了 O(N)。

+ (UIView*)commonView_2:(UIView*)viewA andView:(UIView*)viewB {NSArray*arr1 = [selfsuperViews:viewA];NSArray*arr2 = [selfsuperViews:viewB];NSSet*set = [NSSetsetWithArray:arr2];for(NSUIntegeri =0; i < arr1.count; ++i) {UIView*targetView = arr1[i];if([set containsObject:targetView]) {returntargetView;? ? ? ? }? ? }returnnil;}

除了使用 NSSet 外,我們還可以使用類似歸并排序的思想,用兩個「指針」,分別指向兩個路徑的根節點,然后從根節點開始,找第一個不同的節點,第一個不同節點的上一個公共節點,就是我們的答案。代碼如下:

/* O(N) Solution */+ (UIView *)commonView_3:(UIView *)viewAandView:(UIView*)viewB {? ? NSArray *arr1 = [selfsuperViews:viewA];NSArray *arr2 = [selfsuperViews:viewB];NSIntegerp1= arr1.count -1;NSIntegerp2= arr2.count -1;UIView *answer = nil;while(p1>=0&&p2>=0) {if(arr1[p1] == arr2[p2]) {? ? ? ? ? ? answer = arr1[p1];}p1--;p2--;}? ? return answer;}

我們還可以使用 UIView 的 isDescendant 方法來簡化我們的代碼,不過這樣寫的話,時間復雜度應該也是 O(N^2) 的。lexrus 提供了如下的 Swift 版本的代碼:

/// without flatMapextensionUIView{funccommonSuperview(of view: UIView)->UIView? {iflets = superview {ifview.isDescendant(of: s) {returns? ? ? ? ? ? }else{returns.commonSuperview(of: view)? ? ? ? ? ? }? ? ? ? }returnnil}}

特別地,如果我們利用 Optinal 的 flatMap 方法,可以將上面的代碼簡化得更短,基本上算是一行代碼搞定。怎么樣,你學會了嗎?

extension UIView {funccommonSuperview(of view: UIView)->UIView? {returnsuperview.flatMap {? ? ? ? ? ? view.isDescendant(of: $0) ?? ? ? ? ? ? ? $0 : $0.commonSuperview(of: view)? ? ? ? }? ? }}

iOS 面試題(二)什么時候在 block 中不需要使用 weakSelf

問題:我們知道,在使用 block 的時候,為了避免產生循環引用,通常需要使用 weakSelf 與 strongSelf,寫下面這樣的代碼:

__weaktypeof(self) weakSelf =self;[selfdoSomeBlockJob:^{? ? __strongtypeof(weakSelf) strongSelf = weakSelf;if(strongSelf) {? ? ? ? ...? ? }}];

那么請問:什么時候在 block里面用self,不需要使用weakself?

當block本身不被self 持有,而被別的對象持有,同時不產生循環引用的時候,就不需要使用weakself了。最常見的代碼就是UIView的動畫代碼,我們在使用UIView animateWithDuration:animations方法 做動畫的時候,并不需要使用weakself,因為引用持有關系是:

UIView 的某個負責動畫的對象持有block,block 持有了self因為 self 并不持有 block,所以就沒有循環引用產生,因為就不需要使用 weak self 了。

[UIView animateWithDuration:0.2 animations:^{

self.alpha = 1;

}];

當動畫結束時,UIView會結束持有這個 block,如果沒有別的對象持有block的話,block 對象就會釋放掉,從而 block會釋放掉對于 self 的持有。整個內存引用關系被解除。

iOS 面試題(三)什么時候在 block 中不需要使用 weakSelf

我們知道,在使用 block 的時候,為了避免產生循環引用,通常需要使用 weakSelf 與 strongSelf,寫下面這樣的代碼:

__weaktypeof(self) weakSelf =self;[selfdoSomeBackgroundJob:^{? ? __strongtypeof(weakSelf) strongSelf = weakSelf;if(strongSelf) {? ? ? ? ...? ? }}];

那么請問:為什么 block 里面還需要寫一個 strong self,如果不寫會怎么樣?

在 block 中先寫一個 strong self,其實是為了避免在 block 的執行過程中,突然出現 self 被釋放的尷尬情況。通常情況下,如果不這么做的話,還是很容易出現一些奇怪的邏輯,甚至閃退。

我們以AFNetworking中的AFNetworkReachabilityManager.m的一段代碼舉例:

__weak__typeof(self)weakSelf=self;AFNetworkReachabilityStatusBlockcallback = ^(AFNetworkReachabilityStatus status) {? ? __strong __typeof(weakSelf)strongSelf= weakSelf;strongSelf.networkReachabilityStatus= status;if(strongSelf.networkReachabilityStatusBlock){strongSelf.networkReachabilityStatusBlock(status);}};

如果沒有strongSelf的那行代碼,那么后面的每一行代碼執行時,self都可能被釋放掉了,這樣很可能造成邏輯異常。

特別是當我們正在執行 strongSelf.networkReachabilityStatusBlock(status); 這個 block閉包時,如果這個 block 執行到一半時 self 釋放,那么多半情況下會 Crash。

這里有一篇文章詳細解釋了這個問題:點擊查看文章

昨天的讀者中,拓荒者 和 陳祥龍 同學在評論中也正確回答出了本題。

拓荒者:

1.在block里使用strongSelf是防止在block執行過程中self被釋放。 2.可以通過在執行完block代碼后手動把block置為nil來打破引用循環,AFNetworking就是這樣處理的,避免使用者不了解引用循環造成內存泄露。實際業務中暫時沒遇到這種需求,請巧哥指點什么情況下會有這種需求。

陳祥龍:

strongSelf 一般是在為了避免 block 回調時 weak Self變成了nil ,異步執行一些操作時可能會出現這種情況,不知道我說得對不對。因業務需要不能使用weakSelf 這種情況還真沒遇到過

另外,還有讀者提了兩個有意思的問題,大家可以思考一下:

Yuen 提問:“數組” 和 “字典” 的 enumeratXXXUsingBlock: 是否要使用 weakSelf 和 strongSelf 呢?

瀟湘雨同學提問:block 里 strong self 后,block 不是也會持有 self 嗎?而 self 又持有 block ,那不是又循環引用了?

iOS 面試題(四):block 什么時候需要構造循環引用

問題:有沒有這樣一個需求場景,block 會產生循環引用,但是業務又需要你不能使用 weak self? 如果有,請舉一個例子并且解釋這種情況下如何解決循環引用問題。

答案:需要不使用 weak self 的場景是:你需要構造一個循環引用,以便保證引用雙方都存在。比如你有一個后臺的任務,希望任務執行完后,通知另外一個實例。在我們開源的 YTKNetwork 網絡庫的源碼中,就有這樣的場景。

在 YTKNetwork 庫中,我們的每一個網絡請求 API 會持有回調的 block,回調的 block 會持有 self,而如果 self 也持有網絡請求 API 的話,我們就構造了一個循環引用。雖然我們構造出了循環引用,但是因為在網絡請求結束時,網絡請求 API 會主動釋放對 block 的持有,因此,整個循環鏈條被解開,循環引用就被打破了,所以不會有內存泄漏問題。代碼其實很簡單,如下所示:

//? YTKBaseRequest.m- (void)clearCompletionBlock {// nil out to break the retain cycle.self.successCompletionBlock =nil;self.failureCompletionBlock =nil;}

總結來說,解決循環引用問題主要有兩個辦法:

第一個辦法是「事前避免」,我們在會產生循環引用的地方使用 weak 弱引用,以避免產生循環引用。

第二個辦法是「事后補救」,我們明確知道會存在循環引用,但是我們在合理的位置主動斷開環中的一個引用,使得對象得以回收。

iOS 面試題(五):weak 的內部實現原理

問題:weak 變量在引用計數為0時,會被自動設置成 nil,這個特性是如何實現的?

答案:在Friday QA上,有一期專門介紹 weak的實現原理。

《Objective-C高級編程》一書中也介紹了相關的內容。

簡單來說,系統有一個全局的 CFMutableDictionary 實例,來保存每個對象的 weak 指針列表,因為每個對象可能有多個 weak 指針,所以這個實例的值是 CFMutableSet 類型。

剩下我們要做的,就是在引用計數變成 0 的時候,去這個全局的字典里面,找到所有的 weak 指針,將其值設置成 nil。如何做到這一點呢?Friday QA上介紹了一種類似 KVO 實現的方式。當對象存在 weak 指針時,我們可以將這個實例指向一個新創建的子類,然后修改這個子類的 release 方法,在 release 方法中,去從全局的 CFMutableDictionary 字典中找到所有的 weak 對象,并且設置成 nil。我摘抄了 Friday QA 上的實現的核心代碼,如下:

Classsubclass = objc_allocateClassPair(class, newNameC,0);Methodrelease=class_getInstanceMethod(class, @selector(release));Methoddealloc=class_getInstanceMethod(class, @selector(dealloc));class_addMethod(subclass, @selector(release), (IMP)CustomSubclassRelease, method_getTypeEncoding(release));class_addMethod(subclass, @selector(dealloc), (IMP)CustomSubclassDealloc, method_getTypeEncoding(dealloc));objc_registerClassPair(subclass);

當然,這并不代表蘋果官方是這么實現的,因為蘋果的這部分代碼并沒有開源。《Objective-C高級編程》一書中介紹了 GNUStep 項目中的開源代碼,思想也是類似的。所以我認為雖然實現細節會有差異,但是大致的實現思路應該差別不大。

iOS 面試題(六):自己寫的 view 成員,應該用 weak 還是 strong?

問題:我們知道,從 Storyboard 往編譯器拖出來的 UI 控件的屬性是 weak 的,如下所示

@property(weak,nonatomic)IBOutletUIButton*myButton;

那么,如果有一些 UI 控件我們要用代碼的方式來創建,那么它應該用 weak 還是 strong 呢?為什么?

答案:這是一道有意思的問題,這個問題是我當時和 Lancy 一起寫猿題庫 App 時產生的一次小爭論。簡單來說,這道題并沒有標準答案,但是答案背后的解釋卻非常有價值,能夠看出一個人對于引用計數,對于 view 的生命周期的理解是否到位。

從昨天的評論上,我們就能看到一些理解非常不到位的解釋,例如:

@spume 說:Storyboard 拖線使用 weak 是為了規避出現循環引用的問題。

這個理解是錯誤的,Storyboard 拖出來的控件即使是 strong 的,也不會有循環引用問題。

我認為 UI 控件用默認用 weak,根源還是蘋果希望只有這些 UI 控件的父 View 來強引用它們,而 ViewController 只需要強引用 ViewController.view 成員,則可以間接持有所有的 UI 控件。這樣有一個好處是:在以前,當系統收到 Memory Warning 時,會觸發 ViewController 的 viewDidUnload 方法,這樣的弱引用方式,可以讓整個 view 整體都得到釋放,也更方便重建時整體重新構造。

但是首先 viewDidUnload 方法在 iOS 6 開始就被廢棄掉了,蘋果用了更簡單有效地方式來解決內存警告時的視圖資源釋放,具體如何做的呢?嗯,這個可以當作某一期的面試題展開介紹。總之就是,除非你特殊地操作 view 成員,ViewController.view 的生命期和 ViewController 是一樣的了。

所以在這種情況下,其實 UI 控件是不是 weak 其實關系并不大。當 UI 控件是 weak 時,它的引用計數是 1,持有它的是它的 superview,當 UI 控件是 strong 時,它的引用計數是 2,持有它的有兩個地方,一個是它的 superview,另一個是這個 strong 的指針。UI 控件并不會持有別的對象,所以,不管是手寫代碼還是 Storyboard,UI 控件是 strong 都不會有循環引用的。

那么回到我們的最初的問題,自己寫的 view 成員,應該用 weak 還是 strong?我個人覺得應該用 strong,因為用 weak 并沒有什么特別的優勢,加上上一篇面試題文章中,我們還看到,其實 weak 變量會有額外的系統維護開銷的,如果你沒有使用它的特別的理由,那么用 strong 的話應該更好。

另外有讀者也提到,如果你要做 Lazy 加載,那么你也只能選擇用 strong。

當然,如果你非要用 weak,其實也沒什么問題,只需要注意在賦值前,先把這個對象用 addSubView 加到父 view 上,否則可能剛剛創建完,它就被釋放了。

在我心目中,這才是我喜歡的面試題,沒有標準答案,每種方案各有各的特點,面試者能夠足夠分清楚每種方案的優缺點,結合具體的場景做選擇,這才是優秀的面試者。

1.懶加載的對象必須用strong的原因在于,如果使用weak,對象沒有被沒有被強引用,過了懶加載對象就會被釋放掉。

iOS 面試題(七):為什么 Objective-C 的方法調用要用方括號?

問題:為什么 Objective-C 的方法調用要用方括號 [obj foo],而不是別的語言常常使用的點 obj.foo ?

答案:

首先要說的是,Objective-C 的歷史相當久遠,如果你查 wiki 的話,你會發現:Objective-C 和 C++ 這兩種語言的發行年份都是 1983 年。在設計之初,二者都是作為 C 語言的面向對象的接班人,希望成為事實上的標準。最后結果大家都知道了,C++ 最終勝利了,而 Objective-C 在之后的幾十年中,基本上變成了蘋果自己家玩的玩具。不過最終,由于 iPhone 的出現,Objective-C 迎來了第二春,在 TOBIE 語言排行榜上,從 20 名開外一路上升,排名曾經超越過 C++,達到了第三名(下圖),但是隨著 Swift 的出現,Objective-C 的排名則一路下滑。

TOBIE排行版

Objective-C 在設計之初參考了不少 Smalltalk 的設計,而消息發送則是向 Smalltalk 學來的。Objective-C 當時采用了方括號的形式來表示發送消息,為什么沒有選擇用點呢?我個人覺得是,當時市面上并沒有別的面向對象語言的設計參考,而 Objective-C 「發明」了方括號的形式來給對象發消息,而 C++ 則「發明」了用點的方式來 “發消息”。有人可能會爭論說 C++ 的「點」并不是真正的發消息,但是其實二者都是表示「調用對象所屬的成員函數」。

另外,有讀者評論說使用方括號的形式是為了向下兼容 C 語言,我并不覺得中括號是唯一選擇,C++ 不也兼容了 C 語言么?Swift 不也可以調用 C 函數么?

最終,其實是 C++ 的「發明」顯得更舒服一些,所以后來的各種語言都借鑒了 C++ 的這種設計,也包括 Objective-C 在內。Objective-C 2.0 版本中,引入了 dot syntax,即:

a = obj.foo 等價于 a = [obj foo]

obj.foo = 1 則等價于 [obj setFoo:1]

Objective-C 其實在設計之中確實是比較特立獨行的,除了方括號的函數調用方式外,還包括比較長的,可讀性很強的函數命名風格。

我個人并不討厭 Objective-C 的這種設計,但是從 Swift 語言的設計來看,蘋果也開始放棄一些 Objective-C 的特點了,比如就去掉了方括號這種函數調用方式。

所以,回到我們的問題,我個人認為,答案就是:Objective-C 在 1983 年設計的時候,并沒有什么有效的效仿對象,于是就發明了一種有特點的函數調用方式,現在看起來,這種方式比點操作符還是略遜一籌。

大多數語言一旦被設計好,就很難被再次修改,應該說 Objective-C 發明在 30 年前,還是非常優秀的,它的面向對象化設計得非常純粹,比 C++ 要全面得多,也比 C++ 要簡單得多。

iOS 面試題(八):實現一個嵌套數組的迭代器

問題:

給你一個嵌套的 NSArray 數據,實現一個迭代器類,該類提供一個 next() 方法,可以依次的取出這個 NSArray 中的數據。

比如 NSArray 如果是 [1,[4,3],6,[5,[1,0]]], 則最終應該輸出:1, 4, 3, 6, 5, 1, 0 。

另外,實現一個 allObjects 方法,可以一次性取出所有元素。

給你一個嵌套的 NSArray 數據,實現一個迭代器類,該類提供一個 next() 方法,可以依次的取出這個 NSArray 中的數據。

解答:

本題的代碼稍長,完整的代碼我放在git上了,以下是講解。

先說第二問吧,第二問比較簡單:實現一個 allObjects 方法,可以一次性取出所有元素。

對于此問,我們可以實現一個遞歸函數,在函數中判斷數組中的元素是否又是數組,如果是的話,就遞歸調用自己,如果不是數組,則加入到一個 NSMutableArray 中即可。下面是示例代碼:

- (NSArray*)allObjects {NSMutableArray*result= [NSMutableArrayarray];? ? [self fillArray:_originArray into:result];returnresult;}- (void)fillArray:(NSArray*)arrayinto:(NSMutableArray*)result{for(NSUIntegeri =0; i

如果你還在糾結掌握遞歸有什么意義的話,歡迎翻翻我半年前寫的另一篇文章:遞歸的故事(上)遞歸的故事(下)

接下來讓我們來看第一問,在同學的回復中,我看到很多人用第二問的辦法,把數組整個另外保存一份,然后再記錄一個下標,每次返回其中一個。這個方法當然是可行的,但是大部分的迭代器通常都不會這么實現。因為這么實現的話,數組需要整個復制一遍,空間復雜度是 O(N)。

所以,我個人認為本題第一問更好的解法是:

記錄下遍歷的位置,然后每次遍歷時更新位置。由于本題中元素是一個嵌套數組,所以我們為了記錄下位置,就需要兩個變量:一個是當前正在遍歷的子數組,另一個是這個數組遍歷到的位置。

我在實現的時候,定義了一個名為 NSArrayIteratorCursor 的類來記錄這些內容,NSArrayIteratorCursor 的定義和實現如下:

@interfaceNSArrayIteratorCursor:NSObject@property(nonatomic)NSArray*array;@property(nonatomic)NSUIntegerindex;@end@implementationNSArrayIteratorCursor- (id)initWithArray:(NSArray*)array {self= [superinit];if(self) {? ? ? ? _array = array;? ? ? ? _index =0;? ? }returnself;}@end

由于數組在遍歷的時候可能產生遞歸,就像我們實現 allObjects 方法那樣。所以我們需要處理遞歸時的 NSArrayIteratorCursor 的保存,我在實現的時候,拿數組當作棧,來實現保存遍歷時的狀態。

最終,我實現了一個迭代器類,名字叫 NSArrayIterator,用于最終提供 next 方法的實現。這個類有兩個私有變量,一個是剛剛說的那個棧,另一個是原數組的引用。

@interfaceNSArrayIterator:NSObject- (id)initWithArray:(NSArray*)array;- (id)next;- (NSArray*)allObjects;@end@implementationNSArrayIterator{NSMutableArray*_stack;NSArray*_originArray;}

在初使化的時候,我們初始化遍歷位置的代碼如下:

- (id)initWithArray:(NSArray*)array {self= [superinit];if(self) {? ? ? ? _originArray = array;? ? ? ? _stack = [NSMutableArrayarray];? ? ? ? [selfsetupStack];? ? }returnself;}- (void)setupStack {NSArrayIteratorCursor*c = [[NSArrayIteratorCursoralloc] initWithArray:_originArray];? ? [_stack addObject:c];}

接下來就是最關鍵的代碼了,即實現 next 方法,在 next 方法的實現邏輯中,我們需要:

判斷棧是否為空,如果為空則返回 nil。

從棧中取出元素,看是否遍歷到了結尾,如果是的話,則出棧。

判斷第 2 步是否使棧為空,如果為空,則返回 nil。

終于拿到元素了,這一步判斷拿到的元素是否是數組。

如果是數組,則重新生成一個遍歷的 NSArrayIteratorCursor 對象,放到棧中。

重新從棧中拿出第一個元素,循環回到第 4 步的判斷。

如果到了這一步,說明拿到了一個非數組的元素,這樣就可以把元素返回,同時更新索引到下一個位置。

以下是相關的代碼,對于沒有算法基礎的同學,可能讀起來還是比較累,其實我寫起來也不快,所以希望你能多理解一下,其實核心思想就是手工操作棧的入棧和出棧:

- (id)next {//? 1. 判斷棧是否為空,如果為空則返回 nil。if([_stackcount] ==0) {returnnil;? ? }// 2. 從棧中取出元素,看是否遍歷到了結尾,如果是的話,則出棧。NSArrayIteratorCursor*c;c= [_stack lastObject];while(c.index ==c.array.count&& _stack.count>0) {? ? ? ? [_stack removeLastObject];c= [_stack lastObject];? ? }// 3. 判斷第 2 步是否使棧為空,如果為空,則返回 nil。if(_stack.count==0) {returnnil;? ? }// 4. 終于拿到元素了,這一步判斷拿到的元素是否是數組。id item =c.array[c.index];while([item isKindOfClass:[NSArrayclass]]){c.index++;// 5. 如果是數組,則重新生成一個遍歷的 NSArrayIteratorCursor 對象,放到棧中。NSArrayIteratorCursor*nc = [[NSArrayIteratorCursoralloc] initWithArray:item];? ? ? ? [_stack addObject:nc];// 6. 重新從棧中拿出第一個元素,循環回到第 4 步的判斷。c= nc;? ? ? ? item =c.array[c.index];? ? }// 7. 如果到了這一步,說明拿到了一個非數組的元素,這樣就可以把元素返回,同時更新索引到下一個位置。c.index++;returnitem;}

在讀者回復中,聽榆大叔 和 yiplee 同學用了類似的做法,他們的代碼在:

聽榆大叔yiplee

最終,我想說這個只是我個人想出來的解法,很可能不是最優的,甚至可能也有很多問題,比如,這個代碼有很多可以進一步 challenge 的地方:

這個代碼是線程安全的嗎?如果我們要實現一個線程安全的迭代器,應該怎么做?

如果在使用迭代器的時候,數組被修改了,會怎么樣?

如何檢測在遍歷元素的時候,數組被修改了?

如何避免在遍歷元素的時候,數組被修改?

如果大家有想出更好的解法,歡迎留言告訴我。

【續】iOS 面試題(八):實現一個嵌套數組的迭代器

昨天我的代碼,有一個 Bug,就是我沒有處理好嵌套的數組元素為空的情況,我寫了一個簡單的 TestCase,大家也可以試試自己的代碼是否處理好了這種情況:

- (void)testEmptyArray {? ? NSArray *arr = @[ @[ @[ ]], @[@[ @[ @[ ]]]]];? ? NSArrayIterator *c =[[NSArrayIterator alloc]initWithArray:arr];? ? XCTAssertEqualObjects(nil,[c next]);? ? XCTAssertEqualObjects(nil,[c next]);}

于是乎,我發現我的代碼可以再優化一下,用遞歸的方式來處理空數組的邏輯似乎是寫起來更簡單的,于是我優化之后的邏輯如下:

判斷棧是否為空,如果為空則返回 nil。

從棧中取出元素,看是否遍歷到了結尾,如果是的話,則出棧。

判斷第 2 步是否使棧為空,如果為空,則返回 nil。

終于拿到元素了,這一步判斷拿到的元素是否是數組。

如果是數組,則重新生成一個遍歷的 NSArrayIteratorCursor 對象,放到棧中,并且遞歸調用自己。

如果不是數組,就把元素返回,同時更新索引到下一個位置。

整個代碼也變得更短更清楚了一些,如下所示:

next 方法的實現:

- (id)next {//? 1. 判斷棧是否為空,如果為空則返回 nil。if(_stack.count==0) {returnnil;? ? }// 2. 從棧中取出元素,看是否遍歷到了結尾,如果是的話,則出棧。NSArrayIteratorCursor*c;c= [_stack lastObject];while(c.index ==c.array.count&& _stack.count>0) {? ? ? ? [_stack removeLastObject];c= [_stack lastObject];? ? }// 3. 判斷第2步是否使棧為空,如果為空,則返回 nil。if(_stack.count==0) {returnnil;? ? }// 4. 終于拿到元素了,這一步判斷拿到的元素是否是數組。id item =c.array[c.index];if([item isKindOfClass:[NSArrayclass]]){c.index++;// 5. 如果是數組,則重新生成一個遍歷的//? ? NSArrayIteratorCursor 對象,放到棧中, 然后遞歸調用 next 方法[selfsetupStackWithArray:item];return[selfnext];? ? }// 6. 如果到了這一步,說明拿到了一個非數組的元素,這樣就可以把元素返回,//? ? 同時更新索引到下一個位置。c.index++;returnitem;}初使化部分:- (id)initWithArray:(NSArray*)array {self= [superinit];if(self) {? ? ? ? _originArray = array;? ? ? ? _stack = [NSMutableArrayarray];? ? ? ? [selfsetupStackWithArray:array];? ? }returnself;}- (void)setupStackWithArray:(NSArray*)array {NSArrayIteratorCursor*c= [[NSArrayIteratorCursoralloc] initWithArray:array];? ? [_stack addObject:c];}

iOS 面試題(九):創建一個可以被取消執行的 block

問題:

我們知道 block 默認是不能被取消掉的,請你封裝一個可以被取消執行的 block wrapper 類,它的定義如下:

typedefvoid(^Block)();@interfaceCancelableObject:NSObject- (id)initWithBlock:(Block)block;- (void)start;- (void)cancel;@end

答案:這道題是從網上看到的,原題是創建一個可以取消執行的 block,我想到兩種寫法。

// 方法一:創建一個類,將要執行的 block 封裝起來,然后類的內部有一個 _isCanceled 變量,在執行的時候,檢查這個變量,如果 _isCanceled 被設置成 YES 了,則退出執行。typedefvoid(^Block)();@interfaceCancelableObject:NSObject- (id)initWithBlock:(Block)block;- (void)start;- (void)cancel;@end@implementationCancelableObject{BOOL_isCanceled;? ? Block _block;}- (id)initWithBlock:(Block)block {self= [superinit];if(self!=nil) {? ? ? ? _isCanceled =NO;? ? ? ? _block = block;? ? }returnself;}- (void)start {? ? __weaktypeof(self) weakSelf =self;dispatch_async(dispatch_get_global_queue(0,0),? ? ? ? ^{if(weakSelf) {typeof(self) strongSelf = weakSelf;if(!strongSelf->_isCanceled) {? ? ? ? ? ? ? (strongSelf->_block)();? ? ? ? ? }? ? ? ? }? ? });}- (void)cancel {? ? _isCanceled =YES;}@end

// 另外一種寫法,將要執行的 block 直接放到執行隊列中,但是讓其在執行前檢查另一個 isCanceled 的變量,然后把這個變量的修改實現在另一個 block 方法中,如下所示:typedefvoid(^CancelableBlock)();typedefvoid(^Block)();+ (CancelableBlock)dispatch_async_with_cancelable:(Block)block {? ? __blockBOOLisCanceled =NO;? ? CancelableBlock cb = ^() {? ? ? ? isCanceled =YES;? ? };dispatch_async(dispatch_get_global_queue(0,0), ^{if(!isCanceled) {? ? ? ? ? ? block();? ? ? ? }? ? });returncb;}

以上兩種方法都只能在 block 執行前有效,如果要在 block 執行中有效,只能讓 block 在執行中,有一個機制來定期檢查外部的變量是否有變化,而要做到這一點,需要改 block 執行中的代碼。在本例中,如果 block 執行中的代碼是通過參數傳遞進來的話,似乎并沒有什么辦法可以修改它了。

iOS 面試題(十):一個 Objective-C 對象的內存結構是怎樣的?

問題:一個 Objective-C 對象的內存結構是怎樣的?

答案:這是一道老題,或許很多人都準備過,其實如果不是被每個公司都考查的話,這道題可以看看候選人對于 iOS 背后底層原理的感興趣程度。真正對編程感興趣的同學,都會對這個多少有一些好奇,進而在網上搜索并學習這方面的資料。

以下是本題的簡單回答:

如果把類的實例看成一個C語言的結構體(struct),它首先包含的是一個 isa 指針,而類的其它成員變量依次排列在結構體中。排列順序如下圖所示:

為了驗證該說法,我們在Xcode中新建一個工程,在main.m中運行如下代碼:

#import@interfaceFather:NSObject{int_father;}@end@implementationFather@end@interfaceChild:Father{int_child;}@end@implementationChild@endintmain(intargc,char* argv[]){? Child * child = [[Child alloc] init];@autoreleasepool{// ...}}// 我們將斷點下在 @autoreleasepool 處,然后在Console中輸入p *child,則可以看到Xcode輸出如下內容,這與我們上面的說法一致。(lldb) p *child(Child) $0= {? (Father) Father = {? ? (NSObject)NSObject= {? ? ? (Class) isa = Child? ? }? ? (int) _father =0}? (int) _child =0}

因為對象在內存中的排布可以看成一個結構體,該結構體的大小并不能動態變化。所以無法在運行時動態給對象增加成員變量。

注:需要特別說明一下,通過 objc_setAssociatedObject 和 objc_getAssociatedObject方法可以變相地給對象增加成員變量,但由于實現機制不一樣,所以并不是真正改變了對象的內存結構。

iOS 面試題(11):對象內存結構中的 isa 指針是用來做什么的?

問題:Objective-C 對象內存結構中的 isa 指針是用來做什么的,有什么用?

答案:Objective-C 是一門面向對象的編程語言。每一個對象都是一個類的實例。在 Objective-C 語言的內部,每一個對象都有一個名為 isa 的指針,指向該對象的類。每一個類描述了一系列它的實例的特點,包括成員變量的列表,成員函數的列表等。每一個對象都可以接受消息,而對象能夠接收的消息列表是保存在它所對應的類中。

在 Xcode 中按Shift + Command + O, 然后輸入 NSObject.h 和 objc.h,可以打開 NSObject 的定義頭文件,通過頭文件我們可以看到,NSObject 就是一個包含 isa 指針的結構體,如下圖所示:

按照面向對象語言的設計原則,所有事物都應該是對象(嚴格來說 Objective-C 并沒有完全做到這一點,因為它有象 int, double 這樣的簡單變量類型,而 Swift 語言,連 int 變量也是對象)。在 Objective-C 語言中,每一個類實際上也是一個對象。每一個類也有一個名為 isa 的指針。每一個類也可以接受消息,例如代碼[NSObject alloc],就是向 NSObject 這個類發送名為alloc消息。

在 Xcode 中按Shift + Command + O, 然后輸入 runtime.h,可以打開 Class 的定義頭文件,通過頭文件我們可以看到,Class 也是一個包含 isa 指針的結構體,如下圖所示。(圖中除了 isa 外還有其它成員變量,但那是為了兼容非 2.0 版的 Objective-C 的遺留邏輯,大家可以忽略它。)

因為類也是一個對象,那它也必須是另一個類的實列,這個類就是元類 (metaclass)。元類保存了類方法的列表。當一個類方法被調用時,元類會首先查找它本身是否有該類方法的實現,如果沒有,則該元類會向它的父類查找該方法,直到一直找到繼承鏈的頭。

元類 (metaclass) 也是一個對象,那么元類的 isa 指針又指向哪里呢?為了設計上的完整,所有的元類的 isa 指針都會指向一個根元類 (root metaclass)。根元類 (root metaclass) 本身的 isa 指針指向自己,這樣就行成了一個閉環。上面提到,一個對象能夠接收的消息列表是保存在它所對應的類中的。在實際編程中,我們幾乎不會遇到向元類發消息的情況,那它的 isa 指針在實際上很少用到。不過這么設計保證了面向對象概念在 Objective-C 語言中的完整,即語言中的所有事物都是對象,都有 isa 指針。

我們再來看看繼承關系,由于類方法的定義是保存在元類 (metaclass) 中,而方法調用的規則是,如果該類沒有一個方法的實現,則向它的父類繼續查找。所以,為了保證父類的類方法可以在子類中可以被調用,所以子類的元類會繼承父類的元類,換而言之,類對象和元類對象有著同樣的繼承關系。

我很想把關系說清楚一些,但是這塊兒確實有點繞,我們還是來看圖吧,很多時候圖象比文字表達起來更為直觀。下面這張圖或許能夠讓大家對 isa 和繼承的關系清楚一些:

我們可以從圖中看出:

NSObject 的類中定義了實例方法,例如 -(id)init 方法 和 - (void)dealloc 方法。

NSObject 的元類中定義了類方法,例如 +(id)alloc 方法 和 + (void)load 、+ (void)initialize 方法。

NSObject 的元類繼承自 NSObject 類,所以 NSObject 類是所有類的根,因此 NSObject 中定義的實例方法可以被所有對象調用,例如 - (id)init 方法 和 - (void)dealloc 方法。

NSObject 的元類的 isa 指向自己。

isa swizzling 的應用

系統提供的 KVO 的實現,就利用了動態地修改 isa 指針的值的技術。在 蘋果的文檔

中可以看到如下描述:

Key-Value Observing Implementation Details

Automatic key-value observing is implemented using a technique called isa-swizzling.

The isa pointer, as the name suggests, points to the object’s class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.

When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.

You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.

iOS 面試題(12):按層遍歷二叉樹的節點

解題代碼都是使用 Swift 完成的,我也盡量在代碼中使用了 Swift 語言的一些特性,大家可以順便學學 Swift。

問題:給你一棵二叉樹,請按層輸出其的節點值,即:按從上到下,從左到右的順序。

例如,如果給你如下一棵二叉樹:

3/ \920/? \157輸出結果應該是:[? [3],? [9,20],? [15,7]]本題的Swift代碼模版如下:privateclassTreeNode{publicvarval:Intpublicvarleft:TreeNode?publicvarright:TreeNode?publicinit(_val:Int) {self.val = valself.left=nilself.right=nil}}classSolution{funclevelOrder(_root: TreeNode?)-> [[Int]] {? ? }}

解答:本題出自LeetCode第 102 題,是一個典型的有關遍歷的題目。為了按層遍歷,我們需要使用「隊列」,來將每一層的節點先保存下來,然后再依次處理。

因為我們不但需要按層來遍歷,還需要按層來輸出結果,所以我在代碼中使用了兩個隊列,分別名為 level 和 nextLevel,用于保存不同層的節點。

最終,整個算法邏輯是:

判斷輸入參數是否是為空。

將根節點加入到隊列 level 中。

如果 level 不為空,則:

3.1 將 level 加入到結果 ans 中。

3.2 遍歷 level 的左子節點和右子節點,將其加入到 nextLevel 中。

3.3 將 nextLevel 賦值給 level,重復第 3 步的判斷。

將 ans 中的節點換成節點的值,返回結果。

因為我們是用 Swift 來實現代碼,所以我使用了一些 Swift 語言的特性。例如:隊列中我們保存的是節點的數據結構,但是最終輸出的時候,我們需要輸出的是值,在代碼中,我使用了 Swift 的函數式的鏈式調用,將嵌套數組中的元素類型做了一次變換,如下所示:

letans = result.map{ $0.map{ $0.val }}另外,我們也使用了Swift特有的guard關鍵字,來處理參數的特殊情況。完整的參考代碼如下:////? Binary Tree Level Order Traversal.swift////? Created by Tang Qiao.//importFoundationprivateclassTreeNode{publicvarval:Intpublicvarleft:TreeNode?publicvarright:TreeNode?publicinit(_val:Int) {self.val = valself.left=nilself.right=nil}}privateclassSolution{funclevelOrder(_root: TreeNode?)-> [[Int]] {guardletroot = rootelse{return[]? ? ? ? }varresult = [[TreeNode]]()varlevel = [TreeNode]()? ? ? ? level.append(root)whilelevel.count!=0{? ? ? ? ? ? result.append(level)varnextLevel = [TreeNode]()fornodeinlevel {ifletleftNode = node.left{? ? ? ? ? ? ? ? ? ? nextLevel.append(leftNode)? ? ? ? ? ? ? ? }ifletrightNode = node.right{? ? ? ? ? ? ? ? ? ? nextLevel.append(rightNode)? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? ? ? level = nextLevel? ? ? ? }letans = result.map{ $0.map{ $0.val }}returnans? ? }}

微信中排版代碼非常不便,所以上述代碼也可以從我的 Gist 中找到:代碼Gist地址

完成這道題的同學,可以試著練習一下LeetCode的第 107 題,看看能不能只改動一行代碼,就把 107 題也解決掉。

iOS 面試題(13):求兩個鏈表表示的數的和

問題:給你兩個鏈表,分別表示兩個非負的整數。每個鏈表的節點表示一個整數位。

為了方便計算,整數的低位在鏈表頭,例如:123 在鏈表中的表示方式是:

3->2->1

現在給你兩個這樣結構的鏈表,請輸出它們求和之后的結果。例如:

輸入: (2->4->1) + (5->6->1)輸出:7->0->3

本題的 Swift 代碼模版如下:

privateclassListNode{publicvarval:Intpublicvarnext:ListNode?publicinit(_val:Int) {self.val = valself.next =nil}}classSolution{funcaddTwoNumbers(_l1: ListNode?,_l2: ListNode?)->ListNode? {? ? }}

考查點:本題出自 LeetCode 上的第 2 題。

這是我高中學習編程時最早接觸的一類題目,我們把這類題目叫做「高精度計算」,其實就是在計算機計算精度不夠時,模擬我們在紙上演算的方式來計算答案,然后獲得足夠精度的解。

我還記得我 7 年前第一次去網易有道面試的時候,就考查的是一道類似的高精度計算題目,比這道題復雜得多,我當時用了一個比較笨的辦法,加上當時還是用 C++ 寫的,內存分配和釋放寫起來也比較麻煩,最后寫了兩頁 A4 紙才寫完。

這道題其實完全不考查什么「算法」,人人都知道怎么計算,但是它考察了「將想法轉換成代碼」的能力,新手通常犯的毛病就是:意思都明白,但是寫不出來代碼。所以,這類題目用來過濾菜鳥確實是挺有效的辦法。

答案:本題的做法其實沒什么特別,就是直接計算。計算的時候需要考慮到以下這些情況:

兩個整數長度不一致的情況。

進位的情況。當進位產生時,我們需要保存一個標志位,以便在計算下一位的和的時候,加上進位。

當計算完后,如果還有進位,需要處理最后結果加一位的情況。

以下是完整的代碼,我使用了一些 Swift 語言的特性,比如用 flatMap 來減少對于 Optional 類型值為 nil 的判斷。

privateclassListNode{publicvarval:Intpublicvarnext:ListNode?publicinit(_val:Int) {self.val = valself.next =nil}}privateclassSolution{privatefuncgetNodeValue(_node: ListNode?)->Int{returnnode.flatMap { $0.val } ??0}funcaddTwoNumbers(_l1: ListNode?,_l2: ListNode?)->ListNode? {ifl1 ==nil|| l2 ==nil{returnl1 ?? l2? ? ? ? }varp1 = l1varp2 = l2letresult:ListNode? =ListNode(0)varcurrent = resultvarextra =0whilep1 !=nil|| p2 !=nil|| extra !=0{vartot = getNodeValue(p1) +? ? ? ? ? ? ? ? ? ? ? getNodeValue(p2) + extra? ? ? ? ? ? extra = tot /10tot = tot %10letsum:ListNode? =ListNode(tot)? ? ? ? ? ? current!.next = sum? ? ? ? ? ? current = sum? ? ? ? ? ? p1 = p1.flatMap { $0.next }? ? ? ? ? ? p2 = p2.flatMap { $0.next }? ? ? ? }returnresult!.next? ? }}

以上代碼也可以從我的 Gist 中找到:Gist

偷偷告訴你一個小秘密,Gist 里面的代碼我稍微修改了兩行,最終性能就從打敗 LeetCode 10% 的提交變成了打敗 LeetCode 50% 的提交,如果你感興趣,可以自己仔細對比一下。

iOS 面試題(14):計算有多少個島嶼

問題:在一個地圖中,找出一共有多少個島嶼。

我們用一個二維數組表示這個地圖,地圖中的 1 表示陸地,0 表示水域。一個島嶼是指由上下左右相連的陸地,并且被水域包圍的區域。

你可以假設地圖的四周都是水域。

例一:一共有1個島嶼。11110110101100000000

例二:一共有3個島嶼。11000110000010000011

答案:這是 LeetCode 上的 第 200 題,我們可以用一種被稱為「種子填充」(floodfill)的辦法來解決此題。

具體的做法是:

遍歷整個地圖,找到一個未被標記過的,值為 1 的坐標。

從這個坐標開始,從上下左右四個方向,標記相鄰的 1 。

把這些相鄰的坐標,都標記下來,遞歸的進行標記,以便把相鄰的相鄰塊也能標記上。

待標記全部完成之后,將島嶼的計數 +1。

回到第 1 步。如果第 1 步無法找到未標記的坐標,則結束。

雖然思路簡單,但是實現起來代碼量也不算小。這里有一些小技巧:

我們可以將上下左右四個方向的偏移量保存在數組中,這樣在計算位置的時候,寫起來更簡單一些。

遞歸的標記過程可以用深度優先搜索(DFS)或者寬度優先搜索(BFS)。

以下是完整的參考代碼:

privateclassSolution{privatevarflag: [[Int]]privatevaranswer:Intprivatevarmovex : [Int] {return[-1,1,0,0]? ? }privatevarmovey : [Int] {return[0,0, -1,1]? ? }init() {? ? ? ? flag = [[Int]]()? ? ? ? answer =0}funcdfs(_grid: [[Character]] ,_x: Int,_y: Int){foriin0..<4{lettox = x + movex[i]lettoy = y + movey[i]iftox >=0&& tox < grid.count&& toy >=0&& toy < grid[0].count&& grid[tox][toy] =="1"&& flag[tox][toy] ==0{? ? ? ? ? ? flag[tox][toy] =1dfs(grid, tox, toy)? ? ? ? ? ? }? ? ? ? }? ? }funcnumIslands(_grid: [[Character]])->Int{? ? ? ? answer =0flag = grid.map{? ? ? ? ? ? $0.map{_inreturn0}}foriin0..

Swift 的參數默認是不能修改值的,但是如果是 C++ 語言的話,我們可以直接在地圖上做標記。因為地圖只有 0 和 1 兩種值,我們可以用 2 表示「標記過的陸地」,這樣就省略了額外的標記數組。以下是我寫的一個 C++ 的示例程序:

class Solution {public:? ? void fillLands(vector>&grid,intpx,intpy) {intmovex[] = {0,0,1,-1};intmovey[] = {-1,1,0,0};? ? ? ? queue> q;? ? ? ? q.push(make_pair(px, py));grid[px][py] ='2';while(!q.empty()) {? ? ? ? ? ? pair item = q.front();? ? ? ? ? ? q.pop();inttox, toy;for(inti =0; i <4; ++i) {? ? ? ? ? ? ? ? tox = item.first + movex[i];? ? ? ? ? ? ? ? toy = item.second + movey[i];if(tox >=0&& tox =0&& toy >&grid) {intans =0;for(inti =0; i

iOS 面試題(16):解釋垃圾回收的原理

摘要: 問題 我們知道,Android 手機通常使用 Java 來開發,而 Java 是使用垃圾回收這種內存管理方式。 那么,ARC 和垃圾回收對比,有什么優點和缺點? 考查點 此題其實是考查大家的知識面,雖然做 iOS 開發并不需要用到垃圾回收這種內存管理…

問題

我們知道,Android 手機通常使用 Java 來開發,而 Java 是使用垃圾回收這種內存管理方式。 那么,ARC 和垃圾回收對比,有什么優點和缺點?

考查點

此題其實是考查大家的知識面,雖然做 iOS 開發并不需要用到垃圾回收這種內存管理機制。但是垃圾回收被使用得非常普遍,不但有 Java,還包括 JavaScript, C#,Go 等語言。

如果兩個候選人,一個人只會 iOS 開發,另一個人不但會 iOS 開發,對別的語言或技術也有興趣了解,那我通常更傾向于后者。而且事實常常是,由于后者對計算機興趣更濃,他在 iOS 上也通常專研得比前者更多。

垃圾回收簡介

作為 iOS 開發者,了解一下這個世界上除了 ARC 之外最流行的內存管理方式,還是挺有價值的。所以我盡量簡單給大家介紹一下。

垃圾回收(Garbage Collection,簡稱 GC)這種內存管理機制最早由圖靈獎獲得者 John McCarthy 在 1959 年提出,垃圾回收的理論主要基于一個事實:大部分的對象的生命期都很短。

所以,GC 將內存中的對象主要分成兩個區域:Young 區和 Old 區。對象先在 Young 區被創建,然后如果經過一段時間還存活著,則被移動到 Old 區。(其實還有一個 Perm 區,但是內存回收算法通常不涉及這個區域)

Young 區和 Old 區因為對象的特點不一樣,所以采用了兩種完全不同的內存回收算法。

Young 區的對象因為大部分生命期都很短,每次回收之后只有少部分能夠存活,所以采用的算法叫 Copying 算法,簡單說來就是直接把活著的對象復制到另一個地方。Young 區內部又分成了三塊區域:Eden 區 , From 區 , To 區。每次執行 Copying 算法時,即將存活的對象從 Eden 區和 From 區復制到 To 區,然后交換 From 區和 To 區的名字(即 From 區變成 To 區,To 區變成 From 區)。

Old 區的對象因為都是存活下來的老司機了,所以如果用 Copying 算法的話,很可能 90% 的對象都得復制一遍了,不劃算啊!所以 Old 區的回收算法叫 Mark-Sweep 算法。簡單來說,就是只是把不用的對象先標記(Mark)出來,然后回收(Sweep),活著的對象就不動它了。因為大部分對象都活著,所以回收下來的對象并不多。但是這個算法會有一個問題:它會產生內存碎片,所以它一般還會帶有整理內存碎片的邏輯,在算法中叫做 Compact。如何整理呢?早年用過 Windows 的硬盤碎片整理程序的朋友可能能理解,其實就是把對象插到這些空的位置里。這里面還涉及很多優化的細節,我就不一一展開了。

講完主要的算法,接下來 GC 需要解決的問題就只剩下如何找出需要回收的垃圾對象了。為了避免 ARC 解決不了的循環引用問題,GC 引入了一個叫做「可達性」的概念,應用這個概念,即使是有循環引用的垃圾對象,也可以被回收掉。下面就給大家介紹一下這個概念。

當 GC 工作時,GC 認為當前的一些對象是有效的,這些對象包括:全局變量,棧里面的變量等,然后 GC 從這些變量出發,去標記這些變量「可達」的其它變量,這個標記是一個遞歸的過程,最后就像從樹根的內存對象開始,把所有的樹枝和樹葉都記成可達的了。那除了這些「可達」的變量,別的變量就都需要被回收了。

聽起來很牛逼對不對?那為什么蘋果不用呢?實際上蘋果在 OS X 10.5 的時候還真用了,不過在 10.7 的時候把 GC 換成了 ARC。那么,GC 有什么問題讓蘋果不能忍,這就是:垃圾回收的時候,整個程序需要暫停,英文把這個過程叫做:Stop the World。所以說,你知道 Android 手機有時候為什么會卡吧,GC 就相當于春運的最后一天返城高峰。當所有的對象都需要一起回收時,那種體驗肯定是當時還在世的喬布斯忍受不了的。

看看下面這幅漫畫,真實地展現出 GC 最尷尬的情況(漫畫中提到的 Full GC,就是指執行 Old 區的內存回收):

當然,事實上經過多年的發展,GC 的回收算法一直在被優化,人們想了各種辦法來優化暫停的時間,所以情況并沒有那么糟糕。

答案

ARC 相對于 GC 的優點:

ARC 工作在編譯期,在運行時沒有額外開銷。

ARC 的內存回收是平穩進行的,對象不被使用時會立即被回收。而 GC 的內存回收是一陣一陣的,回收時需要暫停程序,會有一定的卡頓。

ARC 相對于 GC 的缺點:

GC 真的是太簡單了,基本上完全不用處理內存管理問題,而 ARC 還是需要處理類似循環引用這種內存管理問題。

GC 一類的語言相對來說學習起來更簡單。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,753評論 0 9
  • 【2017年最新】? iOS面試題及答案 設計模式是什么? 你知道哪些設計模式,并簡要敘述? 設計模式是一種編碼經...
    紫色冰雨閱讀 618評論 0 1
  • [※]@property中有哪些屬性關鍵字? 有3類。ARC下默認是(atomic, assign, readwr...
    Icyjade_White閱讀 292評論 0 1
  • 第五天…… 提前了兩天開學啊,教育局啊,你為何如此不公啊,為何給人以希望又毀之以失望啊T^T
    愛雪的雨閱讀 168評論 0 0
  • 大一的暑假除了沒有對自己高考分數的擔憂,對填志愿的茫然無措和成為萌新口中的學長學姐外,似乎沒有什么不同。該...
    皖南醫學院閱讀 285評論 0 0