答卓同學的Swift 面試題

題目: 卓同學的 Swift 面試題

翻了 喵神SwiftGG 的資料, 借鑒了 Arco_vvEnum 的回答, ...算是查漏補缺

1. class 和 struct 的區別

  • 結構體(和枚舉, 元組)是值類型, 類(和函數, 閉包)是引用類型.
  • 類可以通過繼承來共享代碼. 而結構體(和枚舉)不能被繼承.
    結構體共享代碼可以用組合, 泛型以及協議擴展

2. 不通過繼承, 代碼復用(共享)的方式有哪些

  • 協議擴展
  • 泛型
  • 組合

3. Set 獨有的方法有哪些

  • SetArray, Dictionary 是 Swift 提供的三種基本集合類型,
    數據類型必須明確, 且遵循 Hashable, 被實現為泛型集合
    Array 是有序數據集;
    Set 是無序無重復數據的集;
    Dictionary 是無序的鍵值對的集

    當集合元素順序不重要或者希望每個元素只出現一次時可以使用 Set

  • 操作方法

    • a.intersection(b) a 與 b 的交集
    • a.symmetricDifference(b) a 與 b 的對等差分(a 與 b 獨有元素的并集)
    • a.union(b) a 與 b 的并集
    • a.subtracting(b) a 與 b 的差集(屬于 a 不屬于 b)
  • 判斷方法

    • a.isSubset(of: b) a 是否是 b 的子集
    • a.isSuperset(of: b) a 是否包含 b
    • a.isStrictSubset(of: b) a 是否是 b 的子集 并且 a 與 b 不相等
    • a.isStrictSuperset(of: b) a 是否包含 b 并且 a 與 b 不相等
    • a.isDisjoint(with: b) a 是否與 b 沒有交集

4. 實現一個 min 函數, 返回兩個元素較小的元素

func min<T: Comparable>(a: T, b: T) -> T {
    return a < b ? a : b
}

5. map, filter, reduce 的作用

  • map 函數能夠被集合調用,它接受一個閉包作為參數,作用于數組中的每個元素。閉包返回一個變換后的元素,接著將所有這些變換后的元素組成一個新的集合(集合類型與值可以改變, 但是數量不變)并返回;
  • filter (過濾器) 傳入一個返回 Bool 類型的函數 保留返回值為 true 的元素
  • reduce 中的參數為兩個:一個初始值、一個 combine 閉包. reduce 把集合中所有的值合并成 一個新值, 該值類型可以與原元素值不同

6. map 與 flatmap 的區別

  • map 返回的集合元素數量與原集合一致
  • flatMap 返回后的集合中不存在 nil, 可以把 Optional 解包
  • flatMap 可以把集合降維

7. 什么是 copy on write

  • Swift 中的寫時復制是指,值類型只在 引用不唯一 并且 被改動 前進行復制
    傳統意義上的值類型會在被傳遞或者被賦值給其他變量時就發生復制行為,但是這將會帶來極大的,也是不必要的性能損耗。
    寫時復制將在值被傳遞和賦值給變量時首先檢查其引用計數,如果引用計數為 1 (唯一引用),那么意味著并沒有其他變量持有該值,對當前值的復制也就可以完全避免,以此在保持值類型不可變性的優良特性的同時,保證使用效率。

8. 如何獲取當前代碼的函數名和行號

func getInfo(name: String = #function, line: Int = #line) {}

getInfo()

9. 如何聲明一個只能被類 conform 的 protocol

protocol MyProtocol: class {}

10. guard 使用場景

  • 解包, 避免 if let else 嵌套

11. defer 使用場景

defer 聲明一個 block,當前代碼執行的閉包退出時會執行該 block。

使用場景: 函數返回是一定要執行一些代碼, 就放在defer代碼塊里面

12. String 與 NSString 的關系與區別

Stringstruct, 是值類型, 性能比 NSString 占優
NSString 繼承自 NSObject, 是引用類型 擁有動態特性

String 可以使用 characters 進行枚舉

13. 怎樣獲取一個 String 的長度

let length1 = "string".characters.count
let length2 = "string".data(using: .utf8)
let length3 = ("string" as NSString).length

14. 如何截取 String 的某段字符串

extension String {

  /// 截取字符串(包括邊界)
  ///
  /// - Parameter range: 截取的邊界
  /// - Returns: 截取后的字符串
  func subString(_ lower: Int, _ upper: Int) -> String {

    let startOffSet = lower < 0 ? 0 : lower

    let endOffSet   = upper > self.characters.count ? self.characters.count : upper

    let start = self.index(self.startIndex, offsetBy: startOffSet)
    let end = self.index(self.startIndex, offsetBy: endOffSet)

    let subRange = Range.init(uncheckedBounds: (lower: start, upper: end))

    return self.substring(with: subRange)
  }
}

15. throws 和 rethrows 的用法與作用

  • rethrowthrows 表示該函數里可能會拋出異常, 調用該函數時需要處理此異常
  • rethrows 一般用在參數中含有可以 throws 的方法的高階函數中
  • throws 另一個 throws 時,應該將前者改為 rethrows, 提高可讀性與準確性
//符合拋出異常條件時
func doSomething() throws {
    if somthingIsError {
      throw CustomError
    }
    //或者配合 do catch / try / try?
  }

func methodRethrows(num: Int, f: (Int) throws -> ()) rethrows {
    try f(num)
  }

16. try? 和 try! 是什么意思

在可能發生異常的語句前加 try

  • try! 表示強制執行, 確定其不會拋出異常, 如果調用中出現異常, 程序將崩潰, 這與對 Optional 值用 ! 進行強制解包的行為是一致的.
  • try? 會返回一個 Optional 值, 如果運行成功, 會包含這條語句的返回值, 否則將返回 nil, 并且意味著無視了錯誤的具體類型

17. associatedtype 的作用

  • 被用作協議中的泛型約束

    在協議中使用 associatedtype 可以讓實現此協議的對象限定協議方法中參數的類型

18. 什么時候使用 final

  • final 關鍵字可以用在 class, func 或者 var 前面進行修飾, 表示不允許對該內容進行繼承或者重寫操作.

19. pubic 和 open 的區別

  • 只有被 open 標記的內容才能在別的框架中被繼承或者重寫

20. 聲明只有一個參數沒有返回值閉包的別名

  • typealias myBlock = (String) -> Void

21. Self 的使用場景

  • 在協議中使用表示當前類型, 類似 associatedtype 的作用, 所以使用 Self 的協議只能被用作泛型約束, 不能用作獨立類型, 因為只有獲知實際類型 Self 才有意義
protocol Equatable {
     static func ==(lhs: Self, rhs: Self) -> Bool
}
  • 擴展協議時用來做類型約束
protocol MyProtocol { }

extension MyProtocol where Self: UIView { }

22. dynamic 的作用

Swift 中使用 KVO 僅限于 NSObject 的子類
該類屬性添加 dynamic 關鍵字
可以把該屬性添加到動態查找列表中 使其可以被動態調用, 即可實現KVO

23. 什么時候使用 @objc

使用@objc 修飾后的類型, 可以直接被 Objective-C 調用

  • 可以被 @objc 修飾的類型:

    • 未嵌套的類
    • 協議
    • 非泛型枚舉 (僅限于原始值為整形的枚舉)
    • 類和協議中的屬性和方法
    • 構造器和析構器
    • 下標
  • Swift 中聲明繼承 NSObject 的類, 會被自動加上 @objc 修飾

  • Swift 中使用中文類名時, 若想讓 Objective-C 調用

@objc(MyClass)
class 我的類: NSObject {
@objc(greeting:)
func 打招呼(名字: String) {
print("哈嘍,(名字)")
}
}


- 若想在協議中實現 `optional` 方法 除了擴展協議, 還可以直接 添加 `@objc` 修飾符, 在方法前添加 `optional` 關鍵字

@objc protocol CounterDataSource {
optional func incrementForCount(count: Int) -> Int
optional var fixedIncrement: Int { get }
}


###24. Optional (可選型) 是怎么實現的

- `Optional` 是一個泛型枚舉, 有兩個枚舉成員, 一個是 `.some(Wrapped)`, 一個是 `.none`,
- 實現 `ExpressibleByNilLiteral` 協議, 可以接收 `nil`

###25. 如何自定義下標獲取

class MyClass {
private let a = ["a", "b"]

subscript(index: Int) -> String {
    return a[index]
}

}


###26. ?? 的作用
- 語法糖. 嘗試取得 `Optional` 值時, 聲明當 `Optional` 為 `.none` 時, 使用 `??` 右邊的值.
- 另外右邊的值為閉包時, 被 `@autoclosure` 優化, 獲取值若不為 `nil`, 則不會被執行

###27. lazy 的作用
- 延遲計算, 實現惰性初始化, 如果不訪問該屬性, 它的缺省值就不會被計算, 可以用閉包做初始化時可以調用 `self`, 因為當它需要計算出來的時候, `self` 已經初始化完成, 并且該閉包屬于瞬發閉包, 不會產生循環引用
- 由于它在沒有值的情況下被初始化, 然后被訪問時改變自己的值, 所以該屬性不能是 `lazy let` 
- 聲明在全局作用域下 和 類型屬性(聲明為 `static let` , 而非實例屬性) 的常量是自動具有惰性( `lazy` ) 的, 并且線程安全
- `SequenceType` 和 `CollectionType` 協議都有一個 `lazy` 計算屬性, 它能返回一個特殊的 `LazySequence` 或者 `LazyCollection`; 這種方法只能被用在 `map`, `flatMap`, `filter` 這樣的高階函數中, 是一種惰性的方式. 實現當值被訪問時才會調用傳入的函數, 對于龐大的序列, 可以提高很多效率

func double(x: Int) -> Int {
print("Computing double value of (x)…")
return 2*x
}
let doubleArray = array.lazy.map(increment).map(double)
print(doubleArray[3])

只用當 `array[3]` 被訪問時, `double(increment(array[3]))` 才會執行

###28. 一個類型表示選項, 可以同時表示有幾個選項選中(類似 UIViewAnimationOptions), 用什么類型表示

- 實現 `OptionSet` 協議的結構體 只需要一個整型的原始值(`rawValue`)

###29. inout 的作用
- Swift 常規方法中經常用到的是值傳遞。
值傳遞最明顯的后果便是無法對原數據進行直接修改。
如果我們需要處理后的數據結果,那么就需要重新定義一個變量來接收值。
在原數據被廢棄的情況下,這樣既增多了代碼量,也產生了空間大量浪費。
因此 Swift 提供了關鍵字修飾 `inout` 來申明數據地址傳遞,也稱之為引用傳遞。  

###30. Error 如果兼容 NSError 需要做什么操作

- 定義 `CustomNSError` 協議, 繼承自 `Error` 用來橋接原來 `NSError` 中的 `code`、`domain`、`UserInfo`.

public protocol CustomNSError : Error {

/// The domain of the error.
public static var errorDomain: String { get }

/// The error code within the given domain.
public var errorCode: Int { get }

/// The user-info dictionary.
public var errorUserInfo: [String : Any] { get }

}


如果想讓我們的自定義 `Error` 可以轉成 `NSError`,實現 `CustomNSError` 就可以完整的 `as NSError`.

###31. 下面的代碼都用了哪些語法糖 / [1, 2, 3].map{ $0 * 2 }
`[1, 2, 3].map{ $0 * 2 }`

- 數組的語法糖 `[1, 2, 3]` 表示 `Array<Int>` 類型的數組
- 唯一參數尾隨閉包, 可以省略括號
- `$0` 表示第一個參數, 省略了參數名的聲明
- 單行表達式閉包可以省略 `return`

###32. 什么是高階函數
- 以函數為參數的函數就是高階函數
- 接收函數為參數, 并返回另一個函數的高階函數為柯里化(Curry)函數

###33. 如何解決引用循環
- 使其中一個引用對象屬性聲明為 `weak` 或 `unowned`

###34. 下面的代碼會不會崩潰,說出原因

var mutableArray = [1,2,3]
for _ in mutableArray {
mutableArray.removeLast()
}

- 不會崩潰, 引用唯一, 不會復制, 執行三次 `.removeLast()` 每次都會重新尋找下標

###35. 給集合中元素是字符串的類型增加一個擴展方法,應該怎么聲明

extension Collection where Iterator.Element == String {
func whatever() { }
}
["", ""].whatever()


###36. 定義靜態方法時關鍵字 static 和 class 有什么區別

- 被 `class` 修飾的方法可以被子類重寫
- `static` 可以修飾結構體, 枚舉和類的靜態方法, `class` 只能修飾類的靜態方法

---
##高級
###1. 一個 Sequence 的索引是不是一定從 0 開始?
- 不一定 可以自定義 `Generator` 改變其初始值, 還可以自定義排序方法
- 比如 `ArraySlice`
###2. 數組都實現了哪些協議

- `RandomAccessCollection` 
- `MutableCollection` : `MutableIndexable`, `Collection`
- ...還是看文檔吧

###3. 如何自定義模式匹配
- 實現 `~=` 運算符, 第一個參數是 `case` 對象, 第二個參數是 `switch` 的對象

###4. autoclosure 的作用
- 把一句表達式自動封裝成一個閉包

func f(_ foo: @autoclosure ()->Bool) { }

f(true)


###5. 編譯選項 whole module optmization 優化了什么
- 減少編譯時間, 編譯時對同屬一個 Module 的源碼做整體分析, 而不是單文件分析
- 排除從不調用的函數

###6. 下面代碼中 mutating 的作用是什么

struct Person {

var name: String {
    mutating get {
        return store
    }
}

}

這個...寫不寫 `mutating` 的區別 不知道

###7. 如何讓自定義對象支持字面量初始化
- ExpressibleByNilLiteral
- ExpressibleByIntegerLiteral
- ExpressibleByFloatLiteral
- ExpressibleByBooleanLiteral
- ExpressibleByStringLiteral
- ExpressibleByArrayLiteral
- ExpressibleByDictionaryLiteral
- 實現對應協議, 即可支持對應字面量初始化

###8. dynamic framework 和 static framework 的區別是什么
- 動態庫文件名后綴:.dylib和.framework; 靜態庫文件名后綴:.a和.framework
- 靜態庫:鏈接時,靜態庫會被完整地復制到可執行文件中,被多次使用就有多份冗余拷貝
- 系統動態庫:鏈接時不復制,程序運行時由系統動態加載到內存,供程序調用,系統只加載一次,多個程序共用,節省內存

---

##哲學
###1. 為什么數組索引越界會崩潰,而字典用下標取值時 key 沒有對應值的話返回的是 nil 不會崩潰。
"一款語言必須要有一些基礎的可以完全信任的東西,否則就會陷入循環驗證的悖論。所以第一題的答案就是,數組取下標被設計為可以完全信任的操作。" -- [Enum](http://www.lxweimin.com/u/bbb690a0f0fb) 

###2.  一個函數的參數類型只要是數字(Int、Float)都可以,要怎么表示

func myMethod<T>(_ value: T) where T: ExpressibleByIntegerLiteral, T: ExpressibleByFloatLiteral {

}

-- [Arco_vv](http://www.lxweimin.com/u/07d93406ec39)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容