Swift語言至今誕生有一年多的時間了,已經(jīng)成為當(dāng)前最流行語言之一。雖然它的語法簡單好用,但實際上Swift是一門非常復(fù)雜的語言。因為它不僅是面向?qū)ο蟮耐瑫r又是函數(shù)式編程語言。本文主要介紹Swift常見的一些面試問題,你可以用這些問題向面試者提問,也可以用來測試你自己目前所掌握的Swift知識,如果你不清楚問題答案的話也不用太擔(dān)心,因為每個問題下面都有相應(yīng)的答案。
問題主要分為兩個部分,筆試題和口頭問題。
筆試題:可以通過發(fā)送Email方式進行編程測試,問題可以包含一小段代碼測試。
口頭問題:可以通過手機等方式進行面對面的交流,因為這些問題更適合用語言進行交流。
而且每個部分又分為三個等級:
初級:適用于剛接觸Swift的學(xué)習(xí)者,已經(jīng)讀過一本或者兩本Swift相關(guān)書籍,開始準(zhǔn)備在App中使用Swift語言。
中級:適用于對Swift語言概念非常感興趣的學(xué)習(xí)者,已經(jīng)讀過許多Swift博客文章并且想進一步深入學(xué)習(xí)Swift語言。
高級:適用于高級學(xué)者,已經(jīng)對Swift語言很熟悉,喜歡探尋語言,想進一步挑戰(zhàn)自我,喜歡高級技術(shù)者。
如果你想知道這些問題的答案,建議你最好打開Playground,親自實現(xiàn)編碼。下面這些問題的答案都在Xcode 7.0 beta 6上測試過。
筆試題
初級
你好,現(xiàn)在開始基礎(chǔ)測試。
問題 #1 - Swift1.0或者更高版本
用下面方法寫一個for循環(huán)是最好的方法嗎?
for var i = 0; i < 5; i++ {
print("Hello!")
}
答案:
for _ in 0...4 {
print("Hello!")
}
Swift實現(xiàn)了兩種范圍操作符,分別為閉區(qū)間操作符和半開區(qū)間操作符。前者包括范圍內(nèi)的所有元素。例如下面例子包含了0到4所有元素。
0...4
而半開區(qū)間操作符不會包括最后一個元素。下面的例子同樣包括了0到4所有元素
0..<5
問題 #2 - Swift1.0或者更高版本
考慮以下這段代碼
struct Tutorial {
var difficulty: Int = 1
}
var tutorial1 = Tutorial()
var tutorial2 = tutorial1
tutorial2.difficulty = 2
tutorial1.difficulty和tutorial2.difficulty的值有什么不同,如果Tutorial是一個類,又有什么不同?為什么?
答案:
tutorial1.difficulty的值是1,而tutorial2.difficulty的值是2
在Swift中,結(jié)構(gòu)體是值類型而不是引用類型,它是值copy。
下面這行代碼會首先創(chuàng)建一份tutorial1的copy,然后再賦值給tutorial2
var tutorial2 = tutorial1
從這行代碼可以看出,tutorial2 的變化不會影響tutorial1
如果Tutorial是一個類,tutorial1.difficulty和tutorial2.difficulty的值都是2。
在Swift中,類是引用類型,tutorial1的變化會影響到tutorial2,反之亦然。
問題 #3 - Swift1.0或者更高版本
用var聲明view1和用let聲明view2,在下面的例子中有什么不同?,最后一行Code能否編譯通過?
import UIKit
var view1 = UIView()
view1.alpha = 0.5
let view2 = UIView()
view2.alpha = 0.5 // Will this line compile?
答案:
因為view1是一個變量,所以可以用UIView的實例進行賦值,用let關(guān)鍵字只能賦值一次,所以下面代碼不能編譯成功。
view2 = view1 // 錯誤:view2是不可變的
但是,UIView是基于類引用的,所以view2的屬性是可以改變的(最后一行代碼可以編譯通過)
let view2 = UIView()
view2.alpha = 0.5 // Yes!
問題 #4 - Swift1.0或者更高版本
下面Code是把數(shù)組按字母順序進行排序,看起來有些復(fù)雜,你能用閉包簡化它嗎?
let animals = ["fish", "cat", "chicken", "dog"]
let sortedAnimals = animals.sort { (one: String, two: String) -> Bool in
return one < two
}
答案:
首先是可以簡化閉包的參數(shù),因為在閉包中,系統(tǒng)是可以通過類型推斷方式推算出參數(shù)的類型。所以你可以去掉參數(shù)的類型:
let sortedAnimals = animals.sort { (one, two) -> Bool in return one < two }
返回類型也可以推算出來,所以可以去掉閉包的返回類型:
let sortedAnimals = animals.sort { (one, two) in return one < two }
可以用$i符號替換掉參數(shù)的名字,代碼然后就變成這樣:
let sortedAnimals = animals.sort { return $0 < $1 }
在單語句的閉包中,關(guān)鍵字return也可以省略。最后一條語句的返回值就變成閉包的返回值:
let sortedAnimals = animals.sort { $0 < $1 }
Oops, 到目前,是不是非常簡單了,但實際上并非如此。
對字符串來說,有一個字符串比較函數(shù),定義如下:
func <(lhs: String, rhs: String) -> Bool
使用這個函數(shù)可以讓你的Code更加簡潔, 如下:
let sortedAnimals = animals.sort(<)
注意:以上每一步的代碼編譯運行后都會輸出同樣的結(jié)果,你可以選擇使用單字節(jié)的閉包。
問題 #5 - Swift1.0或者更高版本
下面代碼定義Address和Person兩個類,創(chuàng)建Ray和Brian兩個實例。
class Address {
var fullAddress: String
var city: String
init(fullAddress: String, city: String) {
self.fullAddress = fullAddress
self.city = city
}
}
class Person {
var name: String
var address: Address
init(name: String, address: Address) {
self.name = name
self.address = address
}
}
var headquarters = Address(fullAddress: "123 Tutorial Street", city: "Appletown")
var ray = Person(name: "Ray", address: headquarters)
var brian = Person(name: "Brian", address: headquarters)
假如Brian要搬遷到新的地址居住,所以你會這樣更新他的住址:
brian.address.fullAddress = "148 Tutorial Street"
這樣做的話會發(fā)生什么?錯在哪個地方?
答案:
Ray也會更新地址。因為類Address是引用類型,headquarters是同一個實例,不論你是修改ray的地址還是brian的地址,都會改變headquarters地址。
解決方法是為brian新創(chuàng)建一個地址。或者聲明Address為結(jié)構(gòu)體而不是類。
中級
下面提升一下難度的等級
問題 #1 - Swift2.0或者更高版本
思考一下以下代碼:
var optional1: String? = nil
var optional2: String? = .None
nil和.None有什么不同?變量optional1和optional2有什么不同?
答案:
nil和.None是一樣的。當(dāng)可選變量沒有值時,Optional.None(.None for short)是一般初始化可選變量的方式,而nil則是另一種寫法。
事實上,下面條件語句輸出是true:
nil == .None // On Swift 1.x this doesn't compile. You need Optional<Int>.None
記著下面代碼說明enumeration是一個可選類型:
enum Optional<T> {
case None
case Some(T)
}
問題 #2 - Swift1.0或者更高版本
下面是用類和結(jié)構(gòu)體實現(xiàn)溫度計的例子:
public class ThermometerClass {
private(set) var temperature: Double = 0.0
public func registerTemperature(temperature: Double) {
self.temperature = temperature
}
}
let thermometerClass = ThermometerClass()
thermometerClass.registerTemperature(56.0)
public struct ThermometerStruct {
private(set) var temperature: Double = 0.0
public mutating func registerTemperature(temperature: Double) {
self.temperature = temperature
}
}
let thermometerStruct = ThermometerStruct()
thermometerStruct.registerTemperature(56.0)
這段代碼在哪個地方會出現(xiàn)編譯錯誤?為什么?
小提示:在Playground測試之前,請仔細(xì)閱讀并思考代碼。
答案:
在最后一行編譯器會提示錯誤,結(jié)構(gòu)體ThermometerStruct聲明一個可變的函數(shù)修改內(nèi)部變量 temperature,但是registerTemperature卻被一個用let創(chuàng)建的實例所調(diào)用,用let定義的變量是不可變的,所以編譯通不過。
在結(jié)構(gòu)體中,改變內(nèi)部狀態(tài)的方法必須用mutating聲明,而且不允許用不可變的實例調(diào)用它。
問題 #3 - Swift1.0或者更高版本
下面的代碼打印輸出是什么?為什么?
var thing = "cars"
let closure = { [thing] in
print("I love \(thing)")
}
thing = "airplanes"
closure()
答案:
結(jié)果會打印出“I love cars”。當(dāng)聲明閉包的時候,捕獲列表會創(chuàng)建一份thing的copy,所以被捕獲到的值是不會改變的,即使你改變thing的值。
如果你去掉閉包中的捕獲列表,編譯器會使用引用代替copy。在這種情況下,當(dāng)閉包被調(diào)用時,變量的值是可以改變的。示例如下:
var thing = "cars"
let closure = {
print("I love \(thing)")
}
thing = "airplanes"
closure() // Prints "I love airplanes"
問題 #4 - Swift2.0或者更高版本
這是一個全局函數(shù),用來記錄數(shù)組中唯一值的數(shù)量。
func countUniques<T: Comparable>(array: Array<T>) -> Int {
let sorted = array.sort(<)
let initial: (T?, Int) = (.None, 0)
let reduced = sorted.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) }
return reduced.1
}
它使用<和==操作符,所以T類型需要遵循Comparable協(xié)議。
可以像下面這樣調(diào)用它:
countUniques([1, 2, 3, 3]) // result is 3
重寫這個函數(shù),讓它作為數(shù)組Array的方法,使得可以這樣調(diào)用:
[1, 2, 3, 3].countUniques() // should print 3
答案:
在Swift2.0中,泛型是可以被擴展的,但需要類型約束限制,如果泛型不滿足約束,那么擴展也是不可見或者不可訪問的。
因此,全局函數(shù)countUniques可以被重寫為數(shù)組Array的擴展:
extension Array where Element: Comparable {
func countUniques() -> Int {
let sorted = sort(<)
let initial: (Element?, Int) = (.None, 0)
let reduced = sorted.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) }
return reduced.1
}
}
注意這個被重寫的方法只有當(dāng)泛型的類型Element實現(xiàn)了Comparable協(xié)議才是有效的。例如,當(dāng)你用裝滿UIView的數(shù)組調(diào)用這個方法時編譯器會提示錯誤:
import UIKit
let a = [UIView(), UIView()]
a.countUniques() // compiler error here because UIView doesn't implement Comparable
問題 #5 - Swift2.0或者更高版本
下面是一個計算兩個可選類型除法的函數(shù)。在計算之前滿足以下三個條件:
- 被除數(shù)不能為空(not nil)
- 除數(shù)不能為空(not nil)
- 除數(shù)不能為0
func divide(dividend: Double?, by divisor: Double?) -> Double? {
if dividend == .None {
return .None
}
if divisor == .None {
return .None
}
if divisor == 0 {
return .None
}
return dividend! / divisor!
}
這段代碼能夠按照預(yù)期的那樣進行工作,但同時存在兩個問題:
- 前置條件,可以考慮利用guard關(guān)鍵字
- 使用了強制解包
改進這個函數(shù),使用guard條件并且避免使用強制解包.
答案:
在Swift2.0中引用guard語句關(guān)鍵字,如果不滿足時,guard會提供一個退出路徑。這個關(guān)鍵字在判斷預(yù)置條件時非常有用,能更加清晰表達條件,不需要采用金字塔似的多重嵌套if語句,下面是一個例子:
guard dividend != .None else { return .None }
它還可以用在可選綁定上,能夠訪問解包后的變量:
guard let dividend = dividend else { return .None }
因此重寫后的divide函數(shù)如下:
func divide(dividend: Double?, by divisor: Double?) -> Double? {
guard let dividend = dividend else { return .None }
guard let divisor = divisor else { return .None }
guard divisor != 0 else { return .None }
return dividend / divisor
}
注意在最后一行沒有隱士解包操作符,是因為dividend和divisor已經(jīng)被解包存儲,并且是非可選不可變的。
而且可以成組地監(jiān)視guard條件,可以讓函數(shù)變得更加簡單:
func divide(dividend: Double?, by divisor: Double?) -> Double? {
guard let dividend = dividend, divisor = divisor where divisor != 0 else { return .None }
return dividend / divisor
}
現(xiàn)在只有兩個guard條件,因為使用where語句判斷解包后的變量divisor是否為0。
高級
問題 #1 - Swift1.0或者更高版本
思考以下用結(jié)構(gòu)體定義的溫度計:
public struct Thermometer {
public var temperature: Double
public init(temperature: Double) {
self.temperature = temperature
}
}
創(chuàng)建一個實例,可以用下面的方式:
var t: Thermometer = Thermometer(temperature:56.8)
但最好是使用下面方式進行初始化:
var thermometer: Thermometer = 56.8
這可以做到嗎?應(yīng)該怎樣做?
答案:
Swift定義了以下協(xié)議,通過使用賦值操作符,用字面值直接初始化:
- NilLiteralConvertible
- BooleanLiteralConvertible
- IntegerLiteralConvertible
- FloatLiteralConvertible
- UnicodeScalarLiteralConvertible
- ExtendedGraphemeClusterLiteralConvertible
- StringLiteralConvertible
- ArrayLiteralConvertible
- DictionaryLiteralConvertible
采用相對應(yīng)的協(xié)議,提供一個公有的構(gòu)造器,允許字面值方式初始化它。在這個溫度計這個例子中,需要實現(xiàn)FloatLiteralConvertible協(xié)議:
extension Thermometer : FloatLiteralConvertible {
public init(floatLiteral value: FloatLiteralType) {
self.init(temperature: value)
}
}
現(xiàn)在可以使用float值創(chuàng)建一個實例了:
var thermometer: Thermometer = 56.8
問題 #2 - Swift1.0或者更高版本
Swift中定義了運算操作符和邏輯操作符,用于操作不同的類型。當(dāng)然,你可以自定義一些運算操作符,比如一元或者二元的。
定義一個^^冪操作符,并且同時滿足以下要求:
- 用兩個Int型整數(shù)作為參數(shù)。
- 返回冪函數(shù)的值,第一個參數(shù)為底數(shù),第二個參數(shù)為指數(shù)。
- 實現(xiàn)潛在的溢出錯誤。
答案:
創(chuàng)建一個自定義運算符需要兩步:聲明和實現(xiàn)。
聲明使用關(guān)鍵字operator,用于指定類型(一元或者二元),然后指定運算符的結(jié)合性和優(yōu)先級。
在本例中,運算符是^^,類型是infix,結(jié)合性是右結(jié)合,優(yōu)先級設(shè)置為155,鑒于乘法和除法的優(yōu)先級是150。以下是運算符的聲明:
infix operator ^^ { associativity right precedence 155 }
實現(xiàn)如下:
func ^^(lhs: Int, rhs: Int) -> Int {
let l = Double(lhs)
let r = Double(rhs)
let p = pow(l, r)
return Int(p)
}
注意一點,實現(xiàn)沒有考慮溢出的情況,如果運算結(jié)果超過Int.max,將會產(chǎn)生運行時錯誤。
問題 #3 - Swift1.0或者更高版本
你能像下面這樣用原始值定義枚舉類型嗎?為什么?
enum Edges : (Double, Double) {
case TopLeft = (0.0, 0.0)
case TopRight = (1.0, 0.0)
case BottomLeft = (0.0, 1.0)
case BottomRight = (1.0, 1.0)
}
答案:
不能,原始值的類型必須遵循以下條件:
- 實現(xiàn)Equatable協(xié)議
- 必須是以下文字轉(zhuǎn)換的類型:
- Int
- String
- Character
在以上代碼中,原始數(shù)值類型是元組類型,即使元組中的數(shù)值滿足條件,也是不兼容的。
問題 #4 - Swift2.0或者更高版本
思考以下代碼,定義了結(jié)構(gòu)體Pizza,協(xié)議Pizzeria,在協(xié)議擴展中實現(xiàn)默認(rèn)方法makeMargherita()。
struct Pizza {
let ingredients: [String]
}
protocol Pizzeria {
func makePizza(ingredients: [String]) -> Pizza
func makeMargherita() -> Pizza
}
extension Pizzeria {
func makeMargherita() -> Pizza {
return makePizza(["tomato", "mozzarella"])
}
}
然后定義一個餐館Lombardis:
struct Lombardis: Pizzeria {
func makePizza(ingredients: [String]) -> Pizza {
return Pizza(ingredients: ingredients)
}
func makeMargherita() -> Pizza {
return makePizza(["tomato", "basil", "mozzarella"])
}
}
下面代碼創(chuàng)建了Lombardis的兩個實例。哪一個會使用“basil”來做披薩?
let lombardis1: Pizzeria = Lombardis()
let lombardis2: Lombardis = Lombardis()
lombardis1.makeMargherita()
lombardis2.makeMargherita()
答案:
兩個都會。協(xié)議Pizzeria聲明了makeMargherita()方法,并且提供了一個默認(rèn)的實現(xiàn)。在Lombardis實現(xiàn)中,這個方法被重寫了。因為這個方法在協(xié)議中聲明了,在Runtime時能正確的被調(diào)用。
如果協(xié)議中沒有聲明makeMargherita()方法,但是在協(xié)議的擴展中又默認(rèn)實現(xiàn)了 這個方法會怎樣?
protocol Pizzeria {
func makePizza(ingredients: [String]) -> Pizza
}
extension Pizzeria {
func makeMargherita() -> Pizza {
return makePizza(["tomato", "mozzarella"])
}
}
在這種情況下,只有l(wèi)ombardis2能用“basil”來做披薩,而lombardis1則不會,因為它調(diào)用的是擴展中默認(rèn)的方法。
問題 #5 - Swift2.0或者更高版本
下面代碼會有編譯錯誤,你能指出在哪個地方嗎?為什么?
struct Kitten {
}
func showKitten(kitten: Kitten?) {
guard let k = kitten else {
print("There is no kitten")
}
print(k)
}
提示:有三種方式可以fix它
答案:
在guard中else語句體中需要有退出路徑,用return返回,或者拋出一個異常或者調(diào)用@noreturn。最簡單的方式是return語句:
func showKitten(kitten: Kitten?) {
guard let k = kitten else {
print("There is no kitten")
return
}
print(k)
}
下面版本是拋出異常:
enum KittenError: ErrorType {
case NoKitten
}
struct Kitten {
}
func showKitten(kitten: Kitten?) throws {
guard let k = kitten else {
print("There is no kitten")
throw KittenError.NoKitten
}
print(k)
}
try showKitten(nil)
最后一個方法是調(diào)用@noreturn中的一個函數(shù)fatalError():
struct Kitten {
}
func showKitten(kitten: Kitten?) {
guard let k = kitten else {
print("There is no kitten")
fatalError()
}
print(k)
}
口頭答疑
初級
問題 #1 - Swift1.0或者更高版本
可選是什么?可以解決什么問題?
答案:
可選可以使得任何類型的變量都能表達缺省值。在Objective-C中,缺省值只適用于引用類型,通常被指定為nil。對于基礎(chǔ)類型的變量(例如int, float)則沒有這個功能,
而Swift把缺省值概念擴展到引用類型和值類型中。一個可選變量在任何時候可以有值或者為nil。
問題 #2 - Swift1.0或者更高版本
什么時候使用結(jié)構(gòu)體?什么時候使用類?
答案:
目前關(guān)于使用類還是結(jié)構(gòu)體這個問題,有許多的爭論。在函數(shù)式編程傾向于使用值類型,而面向?qū)ο缶幊讨懈矚g用類。
在Swift中,類和結(jié)構(gòu)體有很多不相同的功能,主要有下面幾點:
- 類支持繼承,而結(jié)構(gòu)體不支持。
- 類是引用類型,結(jié)構(gòu)體是值類型
并沒有統(tǒng)一的規(guī)則決定孰好孰壞。通常推薦使用最適合的工具完成特定的目標(biāo),在swift中比較好的做法是使用結(jié)構(gòu)體,除非你需要用到繼承或者引用的時候才使用類。
主要因為在運行時,結(jié)構(gòu)體的性能優(yōu)于類的,結(jié)構(gòu)體的方法調(diào)用是靜態(tài)綁定的,而類是在Runtime時動態(tài)解析的。
問題 #3 - Swift1.0或者更高版本
泛型是什么?用來解決什么問題?
答案:
泛型可以使某一個類型的算法能夠更安全的工作。在Swift中泛型可以用在函數(shù)和數(shù)據(jù)類型上,如類,結(jié)構(gòu)體和枚舉類型。
泛型還能解決代碼重復(fù)的問題。普遍現(xiàn)象是當(dāng)你已經(jīng)有一個帶參數(shù)的方法,但你又不得不再重新寫一遍有著類似類型的方法。
在下面的例子中,第二個函數(shù)就像是第一個函數(shù)的“clone”,它只是把傳入?yún)?shù)的類型從字符串變?yōu)檎汀?/p>
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的開發(fā)者可能會想到用NSObject可以解決這個問題:
import Foundation
func areTheyEqual(x: NSObject, _ y: NSObject) -> Bool {
return x == y
}
areTheyEqual("ray", "ray") // true
areTheyEqual(1, 1) // true
雖然這種方式是能解決問題,但是在編譯期間是不安全的。因為它會允許比較String類型和Integer類型,就像下面這樣:
areTheyEqual(1, "ray")
雖然應(yīng)用程序不會crash,但是允許字符串和整型進行比較,會出現(xiàn)讓你想不到的結(jié)果。
使用泛型的話,可以把兩個函數(shù)合成一個,同時又能保證類型是安全的。下面是一個具體實現(xiàn):
func areTheyEqual<T: Equatable>(x: T, _ y: T) -> Bool {
return x == y
}
areTheyEqual("ray", "ray")
areTheyEqual(1, 1)
這個例子是測試兩個數(shù)是否相等,你可以限制傳入?yún)?shù)類型是任意類型,只要這個類型實現(xiàn)了Equatable協(xié)議。這段代碼可以得到你想要的結(jié)果并且能防止傳入不同參數(shù)類型。
問題 #4 - Swift1.0或者更高版本
在某些情況下,你不得不使用隱式解包可選?什么場合?為什么?
答案:
下面情況下需要使用隱式解包可選:
- 當(dāng)屬性在初始化階段不能為空的時候。一個典型的例子是IB的輸出口,它總是要初始化的。使用它之前已經(jīng)在IB中配置,outlet確保在使用之前值不為空。
- 解決強引用循環(huán)的問題,當(dāng)兩個實例相互引用,需要一個實例是非空引用。在這種情況下,一個實例可以標(biāo)注unowned,另一個使用隱式解包可選。
提示:不要使用隱式解包可選,除非你必須使用時。如果使用它不當(dāng)時,會增加Runtime時crash的機率。
問題 #5 - Swift1.0或者更高版本
解包可選類型方法是什么?它們的安全性怎樣?
提示:有7種方法
答案:
- forced unwrapping ! operator -- unsafe
- implicitly unwrapped variable declaration -- unsafe in many cases
- optional binding -- safe
- optional chaining -- safe
- nil coalescing operator -- safe
- new Swift 2.0 guard statement -- safe
- new Swift 2.0 optional pattern -- safe
中級
問題 #1 - Swift1.0或者更高版本
Swift是面向?qū)ο笳Z言還是函數(shù)式語言?
答案:
Swift是混合式語言,兩種都支持。
它實現(xiàn)面向?qū)ο蟮娜齻€基本特征:
- 封裝
- 繼承
- 多態(tài)
和函數(shù)式語言相比,Swift有一些不同,雖然它滿足函數(shù)式語言基本要求,但并不是完全成熟的函數(shù)式編程語言。
問題 #2 - Swift1.0或者更高版本
下面功能,哪一個在Swift中有?
- 泛型類
- 泛型數(shù)據(jù)結(jié)構(gòu)
- 泛型協(xié)議
答案:
1和2在Swift中有。泛型可以用在類,結(jié)構(gòu)體,枚舉類型,全局函數(shù)和方法中。
3可以通過關(guān)鍵字typealias部分實現(xiàn)。它并不是泛型,只是占位符名字而已。它常被看作是關(guān)聯(lián)數(shù)據(jù)類型,采用協(xié)議時才會被定義。
問題 #3 - Swift1.0或者更高版本
在Objective-C中,一個常量可以像下面進行聲明:
const int number = 0;
下面是Swift中的:
let number = 0
他們之間有什么不同嗎?如果是的,你能解釋一下嗎?
答案:
const是一個變量在編譯期間被初始化值或者在編譯期間表達式的值。
通過let關(guān)鍵字創(chuàng)建常量是在Runtime時初始化的,它能夠用用靜態(tài)的或者動態(tài)表達式的結(jié)果初始化。注意它的值只能被初始化一次。
問題 #4 - Swift1.0或者更高版本
聲明一個靜態(tài)屬性或者函數(shù),你可以使用關(guān)鍵字static來修飾值類型。以下是一個結(jié)構(gòu)體的例子:
struct Sun {
static func illuminate() {}
}
對于類來說,你可以使用static或者class修飾符。雖然它們完成同樣的功能,但實際上是不同的。你能解釋一下它們之間有什么不同嗎?
答案:
使用static關(guān)鍵字,靜態(tài)屬性和靜態(tài)函數(shù)是不能被重寫的,但當(dāng)使用class關(guān)鍵字,你可以重寫屬性和函數(shù)。
其實,對于類來說,static關(guān)鍵字是class final的別名而已。
例如,你編譯下面這些code時,當(dāng)你要重寫illuminate()函數(shù)時,編譯器提示錯誤:
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()
}
}
問題 #5 - Swift1.0或者更高版本
使用extension可以增加存儲屬性嗎?解釋一下
答案:
是不能的。extension是用來給存在的類型添加新行為的,并不能改變類型或者接口本身。如果你增加存儲屬性,你需要額外的內(nèi)存空間存儲新的值。extension是不能管理這樣的任務(wù)的。
高級
問題 #1 - Swift1.2
在Swift1.2中,你能解釋一下用泛型聲明枚舉類型的問題嗎?以兩個泛型T和V的枚舉類型Either為例,Left為關(guān)聯(lián)T類型,Right關(guān)聯(lián)V類型。
enum Either<T, V> {
case Left(T)
case Right(V)
}
提示:檢驗這個問題要用Xcode工程,不要在Playground中。注意這個問題和Swift1.2有關(guān),你需要使用Xcode6.4版本。
答案:
編譯的時候會提示以下問題:
unimplemented IR generation feature non-fixed multi-payload enum layout
問題在于不能提前知道T分配內(nèi)存大小,主要取決于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>)
}
這個問題只會影響Swift1.0或之后版本,在Swift2.0中已經(jīng)解決。
問題 #2 - Swift1.0或者更高版本
閉包是值類型還是引用類型?
答案:
閉包是引用類型。如果一個閉包賦值給一個變量,這個變量又復(fù)制一份copy給另一個變量,那么變量所引用的閉包和捕獲的列表也會copy一份。
問題 #3 - Swift1.0或者更高版本
UInt類型用來存儲無符號整數(shù)。它實現(xiàn)如下一個用有符號的整數(shù)構(gòu)造器:
init(_ value: Int)
但是如果你提供一個負(fù)整數(shù)的話,下面代碼會產(chǎn)生編譯錯誤。
let myNegative = UInt(-1)
知道計算機負(fù)數(shù)是用二進制補碼作為一個正數(shù)進行表示,你怎樣把Int類型的負(fù)整數(shù)轉(zhuǎn)為UInt數(shù)?
答案:
已經(jīng)有一個初始化器可以完成:
UInt(bitPattern: Int)
問題 #4 - Swift1.0或者更高版本
你能描述一下在Swift中哪些地方會產(chǎn)生循環(huán)引用?有什么解決辦法嗎?
當(dāng)兩個實例之間相互進行強引用的時候,就會引起循環(huán)引用。兩個實例都不會釋放內(nèi)存,就會造成內(nèi)存泄露。可用weak或者unowned打破實例之間的強引用問題,這樣兩個實例才有機會釋放內(nèi)存空間。
問題 #5 - Swift1.0或者更高版本
Swift2.0引用了一個新關(guān)鍵字能產(chǎn)生遞歸枚舉類型。下面是一個帶有Node節(jié)點的枚舉類型,Node關(guān)聯(lián)值類型,T和list:
enum List<T> {
case Node(T, List<T>)
}
那個關(guān)鍵字是什么?
答案:
關(guān)鍵字indirect允許遞歸枚舉類型,像下面這樣:
enum List<T> {
indirect case Cons(T, List<T>)
}
結(jié)束語
所有的Swfit資源都來自于官方文檔《The Swift Programming Language》,學(xué)習(xí)一門語言最好的方法是使用它。所以可以在Playground使用它或者在實際項目中運用它,Swift能夠和Objective-C進行無縫對接。本文是翻譯[Ray](1. http://www.raywenderlich.com/110982/swift-interview-questions-answers)的博客。