Swift 面試題

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或者更高版本
下面代碼定義AddressPerson兩個類,創(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ā)生什么?錯在哪個地方?

I know it

答案

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)
}

口頭答疑

絕地戰(zhàn)士

初級

問題 #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ù)類型。

Two birds, one stone

問題 #4 - Swift1.0或者更高版本
在某些情況下,你不得不使用隱式解包可選?什么場合?為什么?
答案

下面情況下需要使用隱式解包可選:

  1. 當(dāng)屬性在初始化階段不能為空的時候。一個典型的例子是IB的輸出口,它總是要初始化的。使用它之前已經(jīng)在IB中配置,outlet確保在使用之前值不為空。
  2. 解決強引用循環(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
Believe

中級

問題 #1 - Swift1.0或者更高版本
Swift是面向?qū)ο笳Z言還是函數(shù)式語言?
答案

Swift是混合式語言,兩種都支持。
它實現(xiàn)面向?qū)ο蟮娜齻€基本特征:

  • 封裝
  • 繼承
  • 多態(tài)

和函數(shù)式語言相比,Swift有一些不同,雖然它滿足函數(shù)式語言基本要求,但并不是完全成熟的函數(shù)式編程語言。

問題 #2 - Swift1.0或者更高版本
下面功能,哪一個在Swift中有?

  1. 泛型類
  2. 泛型數(shù)據(jù)結(jié)構(gòu)
  3. 泛型協(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)的博客。

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

推薦閱讀更多精彩內(nèi)容