譯文:Swift Interview Questions and Answers
Swift面試問題及答案-part2
原文鏈接 : Swift Interview Questions and Answers
譯文發布地址:
part1: http://www.lxweimin.com/p/e98d7dc625ff
part2:http://www.lxweimin.com/p/0b9bdffc2523
原文作者 : Antonio Bello
譯者 : lfb_CD
歡迎關注我的微博:http://weibo.com/lfbWb
寫在前面的話:
譯文中有許多鏈接和代碼是我為方便讀者閱讀添加的---我可是一名有情懷的譯者
Verbal questions 口頭的提問
到達這一步你已經很優秀了,但是你還不能自稱為絕地武士(意思是還不能認為自己很厲害了)任何人只要夠努力都可以解決上面那些代碼,但是你如何處理下面這些不斷出現的理論知識和實踐問題呢
回答這些問題仍然需要你在Playground里實際操作
初級
Question #1 - Swift 1.0 or later
什么是可選數據類型?它解決了什么問題?
答案:
可選數據類型是用來表示一個數據類型缺少具體值。在Objective-C中,只有引用類型才允許沒有具體值,用的是nil
來表示。比如float
數據類型沒有這樣的特性
Swift用可選數據類型擴展了值類型和引用數據類型。可選數據類型任何時候都允許擁有具體值或者為
nil
Question #2 - Swift 1.0 or later
什么時候使用結構體,我們什么時候又應該使用類呢?
答案:
有一個正在進行的討論,關于過度使用類超過結構體這究竟是好還是壞。
函數式編程傾向于多使用值數據類型,而面向對象編程更傾向于使用類。
在Swift中,類和結構體有很多不同的特性。可以得出下面這樣一份總結:
- 類支持繼承,結構體不行
- 類是引用類型,結構體是值類型
我們并沒有一個規則來判定使用哪一個是最好。一般的建議是在能夠達到你目標的前提下且使用到的代價最小(結構體比類節省內存空間)。除非你需要用到繼承或者是引用的語法,否則那就采用結構體。
更多關于類和結構體的細節問題請查閱這篇文章:
detailed post on the matter.(我們還在翻譯中..)
注意:在運行時,結構體比類具有更高性能的因為結構體的方法調用是靜態綁定的,而類的方法調用是在運行時動態解析。這是另一個很好的理由來使用結構體,而不是使用類。
Question #3 - Swift 1.0 or later
什么是泛型,它們又解決了什么問題?
答案:
泛型是用來使代碼能安全工作。在Swift中,泛型可以在函數數據類型和普通數據類型中使用,例如類、結構體或枚舉。
泛型解決了代碼復用的問題。有一種常見的情況,你有一個方法,需要一個類型的參數,你為了適應另一種類型的參數還得重新再寫一遍這個方法。
比如,在下面的代碼中,第二個方法是第一個方法的“克隆體”:
func areIntEqual(x: Int, _ y: Int) -> Bool {
return x == y
}
func areStringsEqual(x: String, _ y: String) -> Bool {
return x == y
}
areStringsEqual("ray", "ray") // true
areIntEqual(1, 1) // true
>一個Objective-C開發者可能會采用`NSObject`來解決問題:
>
import Foundation
func areTheyEqual(x: NSObject, _ y: NSObject) -> Bool {
return x == y
}
areTheyEqual("ray", "ray") // true
areTheyEqual(1, 1) // true
>這段代碼能達到了目的,但是編譯的時候并不安全。它允許一個字符串和一個整型數據進行比較:
>
areTheyEqual(1, "ray")
程序可能不會崩潰,但是允許一個字符串和一個整型數據進行比較可能不會得到想要的結果。
采用泛型的話,你可以將上面兩個方法合并為一個,并同時還保證了數據類型安全。這是實現代碼:
>
func areTheyEqual<T: Equatable>(x: T, _ y: T) -> Bool {
return x == y
}
areTheyEqual("ray", "ray")
areTheyEqual(1, 1)
### Question #4 - Swift 1.0 or later
偶爾你也會不可避免地使用隱式可選類型。那請問什么時候我們需要這么做?為什么需要這么做?
>#### 答案:
>最常見的情況是:
>>1. 不為nil的才能初始化它的值。一個典型的例子是一個界面生成器的出口(Interface Builder outlet),它總是在它的本體初始化后初始化。在這種情況下,如果它在界面構建器(Interface Builder)中正確地配置了,就能夠保證在使用前outlet不為nil的。
>>
>>2.為解決強引用循環問題(不知道循環引用是什么的可以看我們翻譯的Swift官方文檔[自動引用計數篇](http://wiki.jikexueyuan.com/project/swift/chapter2/16_Automatic_Reference_Counting.html))。當2個實例互相引用時,就需要一個不為nil的引用指向另一個實例。引用的一邊可以修飾為unowned,另一邊使用隱式可選類型,便可解決循環引用問題。
>>為方便大家理解我貼段代碼上來(原文是沒用的)
>>```
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
理解得不太清楚可以點開上面鏈接查看
小貼士:盡量不要使用隱式可選類型。使用它們會增加運行時崩潰的幾率。在某些情況下,出現崩潰也許是程序員需要這么做,這里也有一個更好的方法來達到同樣的效果,例如,使用fatalerror()。
Question #5 - Swift 1.0 or later
你知道有哪些解包的方式?它們是否是安全解包的?
答案:
ps:下面代碼為譯者本人為方便讀者閱讀而添加,如還有不理解的地方可以根據關鍵字搜索相關文檔
強制用!展開 -- 操作不安全
聲明隱式可選類型變量 -- 在許多情況下是不安全的
(var implicitlyUnwrappedString: String!)
>* optional binding -- 安全
>
> ```
var count: Int?
count = 100
if let validCount = count {
"count is " + String(validCount) //count is 100
} else {
"nil"
}
新的Swift2 guard聲明 -- 安全
自判斷鏈接--安全
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has (roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
>* nil -- 安全
## 中級
漸漸地你挑戰了這里。也許你之前問題解決得很好,但是讓我們看看你是否能很好地通過下面這些問題。
### Question #1 - Swift 1.0 or later
Swift是一種面向對象的語言還是一種面向函數的語言?
>#### 答案:
Swift是一種混合語言,同時支持這兩種范式。
>它實現了三種面向對象的基本原則
>
* 封裝
* 繼承
* 多態
>
當Swift作為一種面向函數的語言來理解時,會有不同卻相似的方式來定義它。
其中一種是較為常見的維基百科上的:"…a programming paradigm [...] that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data."
“……編程范式[……],將數學函數作為一種值,不需要考慮其中的狀態變化和數據變化。”
>你要覺得Swift是一個門成熟的面向函數的編程語言是比較牽強的,但它確實也具有很多面向函數編程的基本要素。
### Question #2 - Swift 1.0 or later
下列哪些特性是Swift含有的?
1. 泛型類
2. 泛型結構
3. 泛型接口
>#### 答案:
>* Swift中包括了上述的1和2。泛型可以在類,結構體,枚舉全局方法或者普通方法中使用。
>* 3用typealias實現了。它本身不是一個泛型類型,它是一個占位符名稱。它通常被稱為關聯類型,當一個協議聲明時使用。如果有不明白的可查看[喵神的文章](http://swifter.tips/typealias/)
### Question #3 - Swift 1.0 or later
在Objective-C語言中,常量可以被定義如下:
const int number = 0;
這是Swift對應的代碼:
let number = 0
請問它們有什么區別么?如果有,你能解釋下它們之間的區別么?
>#### 答案:
`const` 是一個變量在編譯時初始化的值或著是在編譯時解決初始化的。
>
`let`聲明的常數是在運行的時候創建,它最終可以被初始化為靜態或者動態的表達式。注意它的值只能被分配一次。
### Question #4 - Swift 1.0 or later
Swift聲明一個靜態屬性或靜態函數可以使用static來修飾。這是一個結構體的例子:
struct Sun {
static func illuminate() {}
}
對于類,可以使用static或class來修飾。他們可以達到同樣的目標,但實際上他們是不同的。你能解釋他們有什么不同嗎?
>#### 答案:
使用static聲明的一個靜態屬性或者方法并不可被覆蓋override(子類覆蓋父類的方法)。
使用class就可以覆蓋。
>
當用在類里的時候,static相當于class final
比如在下面這段代碼中你如果覆蓋`illuminate()`編譯器就會報錯
>
class Star {
class func spin() {}
static func illuminate() {}
}
class Sun : Star {
override class func spin() {
super.spin()
}
override static func illuminate() { // error: class method overrides a 'final' class method
super.illuminate()
}
}
### Question #5 - Swift 1.0 or later
可以使用擴展添加存儲屬性嗎?
>#### 答案:
no,這是不可能的。擴展可以為已經存在的數據類型添加新的行為,但是不允許改變類型本身或它的接口。
如果您添加了存儲的屬性,您需要額外的內存來存儲新的值。擴展不能完成這樣的任務。
##高級
噢,孩子,你是個聰明的人,對嗎?那就一步一步向上攀爬吧。
### Question #1 - Swift 1.2 or later
在Swift1.2中,你可以解釋一下聲明一個枚舉類型的泛型的問題么?
以一個含有兩個泛型參數`T`和`V`的枚舉`Either`為例,用`T`作為`Left`的相關值類型,`V`作為`Right`的相關值類型:
enum Either<T, V> {
case Left(T)
case Right(V)
}
小貼士:檢查這種情況應該在一個Xcode中的項目中,不是在Playground上。還得注意,這個問題是Swift 1.2相關的,所以你需要Xcode 6.4。
>#### 答案:
編譯失敗的錯誤消息:
>
unimplemented IR generation feature non-fixed multi-payload enum layout
出現的問題是,不能確定`T`需要的內存大小。分配內存大小時取決于`T`本身的數據類型,枚舉需要一個可知的固定大小的值類型。
>
最常用的解決方法是把泛型用引用類型進行包裝,一般起名為`Box`,代碼如下:
>
class Box<T> {
let value: T
init(_ value: T) {
self.value = value
}
}
enum Either<T, V> {
case Left(Box<T>)
case Right(Box<V>)
}
這個問題只在Swift 1.0或者以上出現,但是2.0已經解決了。
### Question #2 - Swift 1.0 or later
閉包是值類型還是引用類型的?
>#### 答案:
閉包是引用類型。如果一個閉包被分配給一個變量,該變量被復制到另一個變量,它們實際是引用的相同一個閉包并且它里面的參數列表也同樣會被復制。
### Question #3 - Swift 1.0 or later
`UInt`數據類型用于存儲整數。它實現了從一個帶符號的整數轉化為`UInt`的初始化方式:
init(_ value: Int)
然而,如果您提供了一個負值,例如下面的代碼會產生一個編譯錯誤:
let myNegative = UInt(-1)
我們知道負數在內部是使用補碼表示,你怎么才能把一個負數的`Int`轉化為`UInt`,同時保持它在內存中的表示形式?
>#### 答案:
這兒有一個初始化的方式:
>
UInt(bitPattern: Int)
### Question #4 - Swift 1.0 or later
你能描述一下你用Swift時遇到的循環引用么?你是怎么解決的?
>#### 答案:
循環引用是指兩個實例彼此強引用,導致內存泄漏,因為兩個實例都不會被收回。原因是只要有一個強引用,實例就不會被回收。你可以通過 `weak` 或者 `unowned` 引用 替換其中一個強引用,從而打破強引用循環
>
想了解更多可以查看我們翻譯的Swift官方文檔里的有關章節[自動引用計數篇](http://wiki.jikexueyuan.com/project/swift/chapter2/16_Automatic_Reference_Counting.html)
### Question #5 - Swift 2.0 or later
Swift2.0增添了一個新關鍵字實現遞歸枚舉。這里有一個枚舉包含一個`Node`,`Node`有兩個的相關的值類型,`T`和`List`:
enum List<T> {
case Node(T, List<T>)
}
請問那個可以實現遞歸枚舉的關鍵字是什么?
>#### 答案:
>
`indirect`
>
代碼如下:
>
enum List<T> {
indirect case Cons(T, List<T>)
}
# 之后的方向?
恭喜你看到了文章末尾,如果你確實不太清楚那些答案,希望你也不要感到沮喪!
其中的一些問題很復雜,Swift是一個非常豐富、且富有表現力的語言。我們還有很多需要學。此外,蘋果不斷改善Swift與添加新的功能,所以非常有可能還存在一些非常好用的但是我們并不知道的(言外之意就是讓我們多研究)。
To get to know Swift or build upon what you already know, be sure to check out our in-depth, tutorial-rich book, [Swift by Tutorials](http://www.raywenderlich.com/store/swift-by-tutorials), or sign up for our hands-on tutorial conference [RWDevCon](http://www.rwdevcon.com/)!
當然,最根本的資料當然還是由蘋果公司寫撰寫的[The Swift Programming Language](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/) (ps:肯定還有我們翻譯的[Swift中文版](http://wiki.jikexueyuan.com/project/swift/)撒!)
最后,使用一門語言才是學習語言最好的方式。只需要你在Playground上寫寫或在一個真正的項目使用Swift。Swift幾乎可以與objective-c無縫地混合,所以建立一個你已經非常熟悉的現有項目是一個很好的方法來學習Swift的來龍去脈。
感謝您的閱讀和解決以上這些問題所做出的努力!你也可以在評論中留下你的問題或者一些你的發現。我也不介意你提出一些你項目中遇到的問題。我們可以互相學習。咱們論壇上見!
***
第一次翻譯這么長的文章,也許會有翻譯錯誤的地方,大家可以在評論中幫忙指出。
另外,我在管理一個微信公眾號SwiftTips,每天發布一些Swift的文章什么的,歡迎關注
