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)書籍,開始準備在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測試之前煤蚌,請仔細閱讀并思考代碼耕挨。
答案:
在最后一行編譯器會提示錯誤,結(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)默認方法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()方法,并且提供了一個默認的實現(xiàn)认臊。在Lombardis實現(xiàn)中圃庭,這個方法被重寫了。因為這個方法在協(xié)議中聲明了失晴,在Runtime時能正確的被調(diào)用剧腻。
如果協(xié)議中沒有聲明makeMargherita()方法,但是在協(xié)議的擴展中又默認實現(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)用的是擴展中默認的方法儒旬。
問題 #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ī)則決定孰好孰壞寇蚊。通常推薦使用最適合的工具完成特定的目標,在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)兩個實例相互引用泊交,需要一個實例是非空引用。在這種情況下柱查,一個實例可以標注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)
但是如果你提供一個負整數(shù)的話,下面代碼會產(chǎn)生編譯錯誤髓绽。
let myNegative = UInt(-1)
知道計算機負數(shù)是用二進制補碼作為一個正數(shù)進行表示敛苇,你怎樣把Int類型的負整數(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)的博客。