Objective-C 和 Swift 面試題【轉】

前言

在 iOS 開發中,語言的選擇是最初的一步。

Objective-C 是蘋果為 iOS 和 Mac 開發量身定制的語言。它隨著 iPhone 的出現而大火,直到今天國內外大多數的 App 依然是用 Objective-C 在寫。它一度在 TIOBE 排行榜上位列第三名,僅次于 Java 和 C。其市場占有份額也遠超其他語言。看名字我們可以知道,它與 C 語言有千絲萬縷的聯系,事實上也確實如此:Objective-C 是 C 語言的超集,它在 C 語言主體上加上了面向對象的特性。這是為了 App 開發的方便,同時也兼顧了語言的整體性能。

2014年來,Swift 橫空出世,功能不斷完善,逐漸成為 Apple 全力主推的官方編程語言。自發布以來,Swift 已經歷經4個版本的迭代。在 TIOBE 編程語言排行榜上的目前位列12位,超過 Ruby 并遠遠甩開其上代語言 Objective-C。從性能上來說,它的速度是 Objective-C 的2.6倍,Python 的8.4倍。更重要的是,Swift 是一門開源的語言,它的質量和進步接受著整個業界的建議、監督、關注。無論從哪個角度講,Swift 都將取代 Objective-C,成為 iOS 開發的主流語言。

現在的面試中,傳統大廠如BAT對 Objective-C 的語言進行較多考察,日常開發也是以 Objective-C為主。而因為 Swift 的高歌猛進,我們日后會看到關于 Swift 的問題越來越多。本文收錄總結了常見的 Swift 和 Objective-C 的面試題,希望對大家有所幫助。

Objective-C Basics

1. 請說明并比較以下關鍵詞:strong, weak, assign, copy

strong表示指向并擁有該對象。其修飾的對象引用計數會增加1。該對象只要引用計數不為0則不會被銷毀。當然強行將其設為nil可以銷毀它。
weak表示指向但不擁有該對象。其修飾的對象引用計數不會增加。無需手動設置,該對象會自行在內存中銷毀。
assign主要用于修飾基本數據類型,如NSInteger和CGFloat,這些數值主要存在于棧上。
weak 一般用來修飾對象,assign一般用來修飾基本數據類型。原因是assign修飾的對象被釋放后,指針的地址依然存在,造成野指針,在堆上容易造成崩潰。而棧上的內存系統會自動處理,不會造成野指針。
copy與strong類似。不同之處是strong的復制是多個指針指向同一個地址,而copy的復制每次會在內存中拷貝一份對象,指針指向不同地址。copy一般用在修飾有可變對應類型的不可變對象上,如NSString, NSArray, NSDictionary。
Objective-C 中,基本數據類型的默認關鍵字是atomic, readwrite, assign;普通屬性的默認關鍵字是atomic, readwrite, strong。

2. 請說明并比較以下關鍵詞:__weak,__block

__weak與weak基本相同。前者用于修飾變量(variable),后者用于修飾屬性(property)。__weak主要用于防止block中的循環引用。
__block也用于修飾變量。它是引用修飾,所以其修飾的值是動態變化的,即可以被重新賦值的。__block用于修飾某些block內部將要修改的外部變量。
__weak和__block的使用場景幾乎與block息息相關。而所謂block,就是Objective-C對于閉包的實現。閉包就是沒有名字的函數,或者理解為指向函數的指針。

3. 請說明并比較以下關鍵詞:atomatic, nonatomic

atomic修飾的對象會保證setter和getter的完整性,任何線程對其訪問都可以得到一個完整的初始化后的對象。因為要保證操作完成,所以速度慢。它比nonatomic安全,但也并不是絕對的線程安全,例如多個線程同時調用set和get就會導致獲得的對象值不一樣。絕對的線程安全就要用 @synchronize。
nonatomic修飾的對象不保證setter和getter的完整性,所以多個線程對它進行訪問,它可能會返回未初始化的對象。正因為如此,它比atomic快,但也是線程不安全的。

4. 什么是ARC?

ARC全稱是 Automatic Reference Counting,是Objective-C的內存管理機制。簡單地來說,就是代碼中自動加入了retain/release,原先需要手動添加的用來處理內存管理的引用計數的代碼可以自動地由編譯器完成了。
ARC的使用是為了解決對象retain和release匹配的問題。以前手動管理造成內存泄漏或者重復釋放的問題將不復存在。
以前需要手動的通過retain去為對象獲取內存,并用release釋放內存。所以以前的操作稱為MRC (Manual Reference Counting)。

5. 什么情況下會出現循環引用?

循環引用是指2個或以上對象互相強引用,導致所有對象無法釋放的現象。這是內存泄漏的一種情況。舉個例子:

class Father
@interface Father: NSObject
@property (strong, nonatomic) Son *son;
@end
class Son
@interface Son: NSObject
@property (strong, nonatomic) Father *father;
@end

上述代碼有兩個類,分別為爸爸和兒子。爸爸對兒子強引用,兒子對爸爸強引用。這樣釋放兒子必須先釋放爸爸,要釋放爸爸必須先釋放兒子。如此一來,兩個對象都無法釋放。

解決方法是將Father中的Son對象屬性從strong改為weak。

內存泄漏可以用Xcode中的Debug Memory Graph去檢查,同時Xcode也會在runtime中自動匯報內存泄漏的問題。

6. 下面代碼中有什么bug?
  • (void)viewDidLoad {
    UILabel *alertLabel = [[UILabel alloc] initWithFrame:CGRectMake(100,100,100,100)];
    alertLabel.text = @"Wait 4 seconds...";
    [self.view addSubview:alertLabel];
    NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
    [backgroundQueue addOperationWithBlock:^{
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:4]];
    alertLabel.text = @"Ready to go!”
    }];
    }

Bug在于,在等了4秒之后,alertLabel并不會更新為Ready to Go。

原因是,所有UI的相關操作應該在主線程進行。當我們可以在一個后臺線程中等待4秒,但是一定要在主線程中更新alertLabel。

最簡單的修正如下:

  • (void)viewDidLoad {
    UILabel *alertLabel = [[UILabel alloc] initWithFrame:CGRectMake(100,100,100,100)];
    alertLabel.text = @"Wait 4 seconds...";
    [self.view addSubview:alertLabel];
    NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
    [backgroundQueue addOperationWithBlock:^{
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:4]];
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    alertLabel.text = @"Ready to go!”
    }];
    }];
    }
7. 以scheduledTimerWithTimeInterval的方式觸發的timer,在滑動頁面上的列表時,timer會暫停,為什么?該如何解決?

原因在于滑動時當前線程的runloop切換了mode用于列表滑動,導致timer暫停。

runloop中的mode主要用來指定事件在runloop中的優先級,有以下幾種:

·Default(NSDefaultRunLoopMode):默認,一般情況下使用;
·Connection(NSConnectionReplyMode):一般系統用來處理NSConnection相關事件,開發者一般用不到;
·Modal(NSModalPanelRunLoopMode):處理modal panels事件;
·Event Tracking(NSEventTrackingRunLoopMode):用于處理拖拽和用戶交互的模式。
·Common(NSRunloopCommonModes):模式合集。默認包括Default,Modal,Event Tracking三大模式,可以處理幾乎所有事件。
回到題中的情境。滑動列表時,runloop的mode由原來的Default模式切換到了Event Tracking模式,timer原來好好的運行在Default模式中,被關閉后自然就停止工作了。

解決方法其一是將timer加入到NSRunloopCommonModes中。其二是將timer放到另一個線程中,然后開啟另一個線程的runloop,這樣可以保證與主線程互不干擾,而現在主線程正在處理頁面滑動。示例代碼如下:

// 方法1
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 方法2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] run];
});

Objective-C 和 Swift 面試題

Swift Basics

8. 類(class)和結構體(struct)有什么區別?

Swift 中,類是引用類型,結構體是值類型。值類型在傳遞和賦值時將進行復制,而引用類型則只會使用引用對象的一個"指向"。所以他們兩者之間的區別就是兩個類型的區別。

舉個簡單的例子,代碼如下

class Temperature {
var value: Float = 37.0
}
class Person {
var temp: Temperature?

func sick() {
temp?.value = 41.0
}
}
let A = Person()
let B = Person()

let temp = Temperature()
A.temp = temp
B.temp = temp
A.sick()

上面這段代碼,由于 Temperature 是 class ,為引用類型,故 A 的 temp 和 B 的 temp指向同一個對象。A 的 temp修改了,B 的 temp 也隨之修改。這樣 A 和 B 的 temp 的值都被改成了41.0。如果將 Temperature 改為 struct,為值類型,則 A 的 temp 修改不影響 B 的 temp。

內存中,引用類型諸如類是在堆(heap)上,而值類型諸如結構體實在棧(stack)上進行存儲和操作。相比于棧上的操作,堆上的操作更加復雜耗時,所以蘋果官方推薦使用結構體,這樣可以提高 App 運行的效率。

class有這幾個功能struct沒有的:

class可以繼承,這樣子類可以使用父類的特性和方法
類型轉換可以在runtime的時候檢查和解釋一個實例的類型
可以用deinit來釋放資源
一個類可以被多次引用
struct也有這樣幾個優勢:

結構較小,適用于復制操作,相比于一個class的實例被多次引用更加安全。
無須擔心內存memory leak或者多線程沖突問題

9. Swift 是面向對象還是函數式的編程語言?

Swift 既是面向對象的,又是函數式的編程語言。

說 Swift 是面向對象的語言,是因為 Swift 支持類的封裝、繼承、和多態,從這點上來看與 Java 這類純面向對象的語言幾乎毫無差別。

說 Swift 是函數式編程語言,是因為 Swift 支持 map, reduce, filter, flatmap 這類去除中間狀態、數學函數式的方法,更加強調運算結果而不是中間過程。

10. 請說明并比較以下關鍵詞:Open, Public, Internal, File-private, Private

Swift 有五個級別的訪問控制權限,從高到底依次為比如 Open, Public, Internal, File-private, Private。

他們遵循的基本原則是:高級別的變量不允許被定義為低級別變量的成員變量。比如一個 private 的 class 中不能含有 public 的 String。反之,低級別的變量卻可以定義在高級別的變量中。比如 public 的 class 中可以含有 private 的 Int。

Open 具備最高的訪問權限。其修飾的類和方法可以在任意 Module 中被訪問和重寫;它是 Swift 3 中新添加的訪問權限。
Public 的權限僅次于 Open。與 Open 唯一的區別在于它修飾的對象可以在任意 Module 中被訪問,但不能重寫。
Internal 是默認的權限。它表示只能在當前定義的 Module 中訪問和重寫,它可以被一個 Module 中的多個文件訪問,但不可以被其他的 Module 中被訪問。
File-private 也是 Swift 3 新添加的權限。其被修飾的對象只能在當前文件中被使用。例如它可以被一個文件中的 class,extension,struct 共同使用。
Private 是最低的訪問權限。它的對象只能在定義的作用域內使用。離開了這個作用域,即使是同一個文件中的其他作用域,也無法訪問。

11. 請說明并比較以下關鍵詞:strong, weak, unowned

Swift 的內存管理機制與 Objective-C一樣為 ARC(Automatic Reference Counting)。它的基本原理是,一個對象在沒有任何強引用指向它時,其占用的內存會被回收。反之,只要有任何一個強引用指向該對象,它就會一直存在于內存中。

strong 代表著強引用,是默認屬性。當一個對象被聲明為 strong 時,就表示父層級對該對象有一個強引用的指向。此時該對象的引用計數會增加1。
weak 代表著弱引用。當對象被聲明為 weak 時,父層級對此對象沒有指向,該對象的引用計數不會增加1。它在對象釋放后弱引用也隨即消失。繼續訪問該對象,程序會得到 nil,不虧崩潰
unowned 與弱引用本質上一樣。唯一不同的是,對象在釋放后,依然有一個無效的引用指向對象,它不是 Optional 也不指向 nil。如果繼續訪問該對象,程序就會崩潰。
加分回答:

weak 和 unowned 的引入是為了解決由 strong 帶來的循環引用問題。簡單來說,就是當兩個對象互相有一個強指向去指向對方,這樣導致兩個對象在內存中無法釋放(詳情請參考第3章第3節第8題)。

weak 和 unowned 的使用場景有如下差別:

當訪問對象時該對象可能已經被釋放了,則用 weak。比如 delegate 的修飾。
當訪問對象確定不可能被釋放,則用 unowned。比如 self 的引用。
實際上為了安全起見,很多公司規定任何時候都使用 weak 去修飾。

13. 用 Swift 實現或(||)操作

這題解法很多,下面給出一種最直接的解法:

func ||(left: Bool, right: Bool) –> Bool {
if left {
return true
} else {
return right
}
}

上面這種解法勉強正確,但是并不高效。或(||)操作的本質是當左邊為真的時候,我們無需計算右邊。而上面這種事先,是將右邊默認值預先準備好,再傳入進行操作。當右邊值的計算十分復雜時會 造成了性能上的浪費。所以,上面這種做法違反了或(||)操作的本質。正確的實現方法如下:

func ||(left: Bool, right: @autoclosure () -> Bool) –> Bool {
if left {
return true
} else {
return right()
}
}

autoclosure 可以將右邊值的計算推遲到判定left 為 false 的時候,這樣就可以避免第一種方法帶來的不必要開銷了。

14. 實現一個函數。求一個整型二維數組中所有元素之和

func sumPairs(_ nums: [[Int]]) -> Int {
return nums.flatMap { $0 }.reduce(0) { $0 + $1 }
}

Swift 有函數式編程的思想。其中 flatMap, map, reduce, filter 是其代表的方法。本題中考察了 flatMap 的降維思路,以及 reduce 的基本使用。相比于一般的 for 循環,這樣的寫法要更加得簡潔漂亮。

Objective-C 和 Swift 面試題

Swift vs. Objective-C

15. 說說Swift為什么將String,Array,Dictionary設計成值類型?

要解答這個問題,就要和Objective-C中相同的數據結構設計進行比較。Objective-C中,字符串,數組,字典,皆被設計為引用類型。

值類型相比引用類型,最大的優勢在于內存使用的高效。值類型在棧上操作,引用類型在堆上操作。棧上的操作僅僅是單個指針的上下移動,而堆上的操作則牽涉到合并、移位、重新鏈接等。也就是說Swift這樣設計,大幅減少了堆上的內存分配和回收的次數。同時copy-on-write又將值傳遞和復制的開銷降到了最低。
String,Array,Dictionary設計成值類型,也是為了線程安全考慮。通過Swift的let設置,使得這些數據達到了真正意義上的“不變”,它也從根本上解決了多線程中內存訪問和操作順序的問題。
設計成值類型還可以提升API的靈活度。例如通過實現Collection這樣的協議,我們可以遍歷String,使得整個開發更加靈活高效。

16. 在Swift和Objective-C的混編項目中,如何在Swift文件中調用Objective-C文件中已經定義的方法?如何在Objective-C文件中調用Swift文件中定義的方法?

Swift中若要使用Objective-C代碼,可以在ProjectName-Bridging-Header.h里添加Objective-C的頭文件名稱,Swift文件中即可調用相應的Objective-C代碼。一般情況Xcode會在Swift項目中第一次創建Objective-C文件時自動創建ProjectName-Bridging-Header.h文件。
Objective-C中若要調用Swift代碼,可以導入Swift生成的頭函數ProjectName-Swift.h來實現。
Swift文件中若要規定固定的方法或屬性暴露給Objective-C使用,可以在方法或屬性前加上@objc來聲明。如果該類是NSObject子類,那么Swift會在非private的方法或屬性前自動加上@objc。

17. 用Swift 將協議(protocol)中的部分方法設計成可選(optional),該怎樣實現?

@optional 和 @required 是 Objective-C 中特有的關鍵字。

Swift中,默認所有方法在協議中都是必須實現的。而且,協議里方法不可以直接定義 optional。先給出兩種解決方案:

在協議和方法前都加上 @objc 關鍵字,然后再在方法前加上 optional 關鍵字。該方法實際上是把協議轉化為Objective-C的方式然后進行可選定義。示例如下:

@objc protocol SomeProtocol {
func requiredFunc()
@objc optional func optionalFunc()
}

用擴展(extension)來規定可選方法。Swift中,協議擴展(protocol extension)可以定義部分方法的默認實現,這樣這些方法在實際調用中就是可選實現的了。示例如下:

protocol SomeProtocol {
func requiredFunc()
func optionalFunc()
}
extension SomeProtocol {
func optionalFunc() {
print(“Dumb Implementation”)
}
}
Class SomeClass: SomeProtocol {
func requiredFunc() {
print(“Only need to implement the required”)
}
}

18. 要給一個UIButton增加一個點擊后抖動的效果,該怎樣實現?

解決方案有三種。個人推薦用protocol來解決。

實現一個自定義的UIButton類,在其中添加點擊抖動效果的方法(shake方法)
寫一個UIButton或者UIView的拓展(extension),然后在其中增加shake方法
定義一個protocol,然后在協議擴展(protocol extension)中添加shake方法
分析這三種方法:

在自定義的類中添加shake方法擴展性不好。如果shake方法被用在其他地方,又要在其他類中再添加一遍shake方法,這樣代碼復用性差。
在extension中實現雖然解決了代碼復用性問題,但是可讀性比較差。團隊開發中并不是所有人都知道這個extension中存在shake方法,同時隨著功能的擴展,extension中新增的方法會層出不窮,它們很難歸類管理。
用協議定義解決了復用性、可讀性、維護性三個難題。協議的命名(例如Shakeable)直接可以確定其實現的UIButton擁有相應shake功能;通過協議擴展,可以針對不同類實現特定的方法,可維護性也大大提高;因為協議擴展通用于所有實現對象,所以代碼復用性也很高。

19. 試比較Swift和Objective-C中的初始化方法(init)有什么異同?

一言以蔽之,Swift中的初始化方法更加嚴格和準確。

Objective-C中,初始化方法無法保證所有成員變量都完成初始化;編譯器對屬性設置并無警告,但是實際操作中會出現初始化不完全的問題;初始化方法與普通方法并無實際差別,可以多次調用。
Swift中,初始化方法必須保證所有optional的成員變量都完成初始化。同時新增convenience和required兩個修飾初始化方法的關鍵詞。convenience只是提供一種方便的初始化方法,必須通過調用同一個類中designated初始化方法來完成。required是強制子類重寫父類中所修飾的初始化方法。

20. 談談對Objective-C和Swift 動態特性的理解

runtime其實就是Objective-C的動態機制。runtime執行的是編譯后的代碼,這時它可以動態加載對象、添加方法、修改屬性、傳遞信息等等。具體過程是在Objective-C中對象調用方法時,如[self.tableview reload],發生了兩件事。

編譯階段,編譯器(compiler)會把這句話翻譯成objc_msgSend(self.tableview, @selector(reload)),把消息發送給self.tableview。
運行階段,接收者self.tableview會響應這個消息,期間可能會直接執行、轉發消息,也可能會找不到方法崩潰。
所以整個流程是編譯器翻譯 –> 給接收者發送消息 –> 接收者響應消息三個流程。

如[self.tableview reload]中,self.tableview就是接收者,reload就是消息,所以方法調用的格式在編譯器看來是[receiver message]。

其中接收者如何響應代碼,就發生在運行時(runtime)。runtime執行的是編譯后的代碼,這時它可以動態加載對象、添加方法、修改屬性、傳遞信息等等,runtime的運行機制就是Objective-C的動態特性。

Swift目前被公認為是一門靜態語言。它的動態特性都是通過橋接OC來實現。如果要把動態特性寫得更Swift一點,可以用protocol來處理,比如OC中的reflection這樣寫:
if ([someImage respondsToSelector:@selector(shake)]) {
[someImage performSelector:shake];
}
Swift 中可以這樣寫:

if let shakeableImage = someImage as? Shakeable {
shakeableImage.shake()
}

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

推薦閱讀更多精彩內容