46 道 Swift 常見面試題解
- 問題區
1.1 基礎
1、class 和 struct 的區別
2、不通過繼承,代碼複用(共享)的方式有哪些
3、Set 獨有的方法有哪些?
4、實現一個 min 函數,返回兩個元素較小的元素
5、map、filter、reduce 的作用
6、map 與 flatmap 的區別
7、什麼是 copy on write
8、如何獲取當前代碼的函數名和行號
9、如何聲明一個只能被類 conform 的 protocol
10、guard 使用場景
11、defer 使用場景
12、String 與 NSString 的關係與區別
13、怎麼獲取一個 String 的長度
14、如何截取 String 的某段字符串
15、throws 和 rethrows 的用法與作用
16、try?和 try!是什麼意思
17、associatedtype 的作用
18、什麼時候使用 final
19、public 和 open 的區別
20、聲明一個只有一個參數沒有返回值閉包的別名
21、定義靜態方法時關鍵字 static 和 class 有什麼區別
22、Self 的使用場景
23、dynamic 的作用
24、什麼時候使用 @objc
25、Optional(可選型) 是用什麼實現的
26、如何自定義下標獲取
27、?? 的作用
28、lazy 的作用
29、一個類型表示選項,可以同時表示有幾個選項選中(類似 UIViewAnimationOptions ),用什麼類型表示
30、inout 的作用
31、Error 如果要兼容 NSError 需要做什麼操作
32、下面的代碼都用了哪些語法糖
[1, 2, 3].map{ $0 * 2 }
33、什麼是高階函數
34、如何解決引用循環
35、下面的代碼會不會崩潰,說出原因
var mutableArray = [1,2,3]
for _ in mutableArray {
mutableArray.removeLast()
}
36、給集合中元素是字符串的類型增加一個擴展方法,應該怎麼聲明
1.2 高級
1、一個 Sequence 的索引是不是一定從 0 開始?
2、數組都實現了哪些協議
3、如何自定義模式匹配
4、autoclosure 的作用
5、編譯選項 whole module optmization 優化了什麼
6、下面代碼中 mutating 的作用是什麼
struct Person {
var name: String {
mutating get {
return store
}
}
}
7、如何讓自定義對象支持字面量初始化
8、dynamic framework 和 static framework 的區別是什麼
9、爲什麼數組索引越界會崩潰,而字典用下標取值時 key 沒有對應值的話返回的是 nil 不會崩潰。
10、一個函數的參數類型只要是數字(Int、Float)都可以,要怎麼表示。
- 基礎題解答區
2.1 class 和 struct 的區別
class 爲類, struct 爲結構體, 類是引用類型, 結構體爲值類型, 結構體不可以繼承
2.2 不通過繼承,代碼複用(共享)的方式有哪些
擴展, 全局函數
2.3 Set 獨有的方法有哪些?
// 定義一個 set
let setA: Set<Int> = [1, 2, 3, 4, 4]// {1, 2, 3, 4}, 順序可能不一致, 同一個元素只有一個值
let setB: Set<Int> = [1, 3, 5, 7, 9]// {1, 3, 5, 7, 9}
// 取並集 A | B
let setUnion = setA.union(setB)// {1, 2, 3, 4, 5, 7, 9}
// 取交集 A & B
let setIntersect = setA.intersection(setB)// {1, 3}
// 取差集 A - B
let setRevers = setA.subtracting(setB) // {2, 4}
// 取對稱差集, A XOR B = A - B | B - A
let setXor = setA.symmetricDifference(setB) //{2, 4, 5, 7, 9}
2.4 實現一個 min 函數,返回兩個元素較小的元素
func myMin<T: Comparable>(_ a: T, _ b: T) -> T {
return a < b ? a : b
}
myMin(1, 2)
2.5 map、filter、reduce 的作用
map 用於映射, 可以將一個列表轉換爲另一個列表
[1, 2, 3].map{"\($0)"}// 數字數組轉換爲字符串數組
["1", "2", "3"]
filter 用於過濾, 可以篩選出想要的元素
[1, 2, 3].filter{$0 % 2 == 0} // 篩選偶數
// [2]
reduce 合併
[1, 2, 3].reduce(""){$0 + "\($1)"}// 轉換爲字符串並拼接
// "123"
組合示例
(0 ..< 10).filter{$0 % 2 == 0}.map{"\($0)"}.reduce(""){$0 + $1}
// 02468
2.6 map 與 flatmap 的區別
flatmap 有兩個實現函數實現,
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
這個方法, 中間的函數返回值爲一個可選值, 而 flatmap 會丟掉那些返回值爲 nil 的值
例如
["1", "@", "2", "3", "a"].flatMap{Int($0)}
// [1, 2, 3]
["1", "@", "2", "3", "a"].map{Int($0) ?? -1}
//[Optional(1), nil, Optional(2), Optional(3), nil]
另一個實現
public func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Iterator.Element] where SegmentOfResult : Sequence
中間的函數, 返回值爲一個數組, 而這個 flapmap 返回的對象則是一個與自己元素類型相同的數組
func someFunc(_ array:[Int]) -> [Int] {
return array
}
[[1], [2, 3], [4, 5, 6]].map(someFunc)
// [[1], [2, 3], [4, 5, 6]]
[[1], [2, 3], [4, 5, 6]].flatMap(someFunc)
// [1, 2, 3, 4, 5, 6]
其實這個實現, 相當於是在使用 map 之後, 再將各個數組拼起來一樣的
[[1], [2, 3], [4, 5, 6]].map(someFunc).reduce([Int]()) {$0 + $1}
// [1, 2, 3, 4, 5, 6]
2.7 什麼是 copy on write
寫時複製, 指的是 swift 中的值類型, 並不會在一開始賦值的時候就去複製, 只有在需要修改的時候, 纔去複製。
2.8 如何獲取當前代碼的函數名和行號
以上這些都是特殊的字面量, 多用於調試輸出日誌
2.9 如何聲明一個只能被類 conform 的 protocol
聲明協議的時候, 加一個 class 即可
如
protocol SomeClassProtocl: class {
func someFunction()
}
2.10 guard 使用場景
guard 和 if 類似, 不同的是, guard 總是有一個 else 語句, 如果表達式是假或者值綁定失敗的時候, 會執行 else 語句, 且在 else 語句中一定要停止函數調用
例如
guard 1 + 1 == 2 else {
fatalError("something wrong")
}
常用使用場景爲, 用戶登錄的時候, 驗證用戶是否有輸入用戶名密碼等
guard let userName = self.userNameTextField.text,
let password = self.passwordTextField.text else {
return
}
2.11 defer 使用場景
defer 語句塊中的代碼, 會在當前作用域結束前調用, 常用場景如異常退出後, 關閉數據庫連接
func someQuery()
-> ([
Result
], [
Result
]){
let db = DBOpen("xxx")
defer {
db.close()
}
guard results1 = db.query("query1") else {
return nil
}
guard results2 = db.query("query2") else {
return nil
}
return (results1, results2)
}
需要注意的是, 如果有多個 defer, 那麼後加入的先執行
func someDeferFunction() {
defer {
print("\(#function)-end-1-1")
print("\(#function)-end-1-2")
}
defer {
print("\(#function)-end-2-1")
print("\(#function)-end-2-2")
}
if true {
defer {
print("if defer")
}
print("if end")
}
print("function end")
}
someDeferFunction()
// 輸出
// if end
// if defer
// function end
// someDeferFunction()-end-2-1
// someDeferFunction()-end-2-2
// someDeferFunction()-end-1-1
// someDeferFunction()-end-1-2
2.12 String 與 NSString 的關係與區別
NSString 與 String 之間可以隨意轉換
let someString = "123"
let someNSString = NSString(string: "n123")
let strintToNSString = someString as NSString
let nsstringToString = someNSString as String
String 是結構體, 值類型, NSString 是類, 引用類型。
通常, 沒必要使用 NSString 類, 除非你要使用一些特有方法, 例如使用 pathExtension
屬性
2.13 怎麼獲取一個 String 的長度
不考慮編碼, 只是想知道字符的數量, 用characters.count
"hello".characters.count // 5
"你好".characters.count // 2
"こんにちは".characters.count // 5
如果想知道在某個編碼下佔多少字節, 可以用
"hello".lengthOfBytes(using: .ascii) // 5
"hello".lengthOfBytes(using: .unicode) // 10
"你好".lengthOfBytes(using: .unicode) // 4
"你好".lengthOfBytes(using: .utf8) // 6
"こんにちは".lengthOfBytes(using: .unicode) // 10
"こんにちは".lengthOfBytes(using: .utf8) // 15
2.14 如何截取 String 的某段字符串
swift 中, 有三個取子串函數,
substring:to
, substring:from
, substring:with
.
let simpleString = "Hello, world"
simpleString.substring(to: simpleString.index(simpleString.startIndex, offsetBy: 5))
// hello
simpleString.substring(from: simpleString.index(simpleString.endIndex, offsetBy: -5))
// world
simpleString.substring(with: simpleString.index(simpleString.startIndex, offsetBy: 5) ..< simpleString.index(simpleString.endIndex, offsetBy: -5))
// ,
2.15 throws 和 rethrows 的用法與作用
throws 用在函數上, 表示這個函數會拋出錯誤.
有兩種情況會拋出錯誤, 一種是直接使用 throw 拋出, 另一種是調用其他拋出異常的函數時, 直接使用 try xx 沒有處理異常.
如
enum DivideError: Error {
case EqualZeroError;
}
func divide(_ a: Double, _ b: Double) throws -> Double {
guard b != Double(0) else {
throw DivideError.EqualZeroError
}
return a / b
}
func split(pieces: Int) throws -> Double {
return try divide(1, Double(pieces))
}
rethrows 與 throws 類似, 不過只適用於參數中有函數, 且函數會拋出異常的情況, rethrows 可以用 throws 替換, 反過來不行
如
func processNumber(a: Double, b: Double, function: (Double, Double) throws -> Double) rethrows -> Double {
return try function(a, b)
}
2.16 try?和 try!是什麼意思
這兩個都用於處理可拋出異常的函數, 使用這兩個關鍵字可以不用寫do catch
.
區別在於, try? 在用於處理可拋出異常函數時, 如果函數拋出異常, 則返回 nil, 否則返回函數返回值的可選值, 如:
print(try? divide(2, 1))
// Optional(2.0)
print(try? divide(2, 0))
// nil
而 try! 則在函數拋出異常的時候崩潰, 否則則返會函數返回值, 相當於 (try? xxx)!, 如:
print(try! divide(2, 1))
// 2.0
print(try! divide(2, 0))
// 崩潰
2.17 associatedtype 的作用
簡單來說就是 protocol 使用的泛型
例如定義一個列表協議
protocol ListProtcol {
associatedtype Element
func push(_ element:Element)
func pop(_ element:Element) -> Element?
}
實現協議的時候, 可以使用 typealias 指定爲特定的類型, 也可以自動推斷, 如
class IntList: ListProtcol {
typealias Element = Int // 使用 typealias 指定爲 Int
var list = [Element]()
func push(_ element: Element) {
self.list.append(element)
}
func pop(_ element: Element) -> Element? {
return self.list.popLast()
}
}
class DoubleList: ListProtcol {
var list = [Double]()
func push(_ element: Double) {// 自動推斷
self.list.append(element)
}
func pop(_ element: Double) -> Double? {
return self.list.popLast()
}
}
使用泛型也可以
class AnyList<T>: ListProtcol {
var list = [T]()
func push(_ element: T) {
self.list.append(element)
}
func pop(_ element: T) -> T? {
return self.list.popLast()
}
}
可以使用 where 字句限定 Element 類型, 如:
extension ListProtcol where Element == Int {
func isInt() ->Bool {
return true
}
}
2.18 什麼時候使用 final
final 用於限制繼承和重寫. 如果只是需要在某一個屬性前加一個 final。
如果需要限制整個類無法被繼承, 那麼可以在類名之前加一個 final
2.19 public 和 open 的區別
這兩個都用於在模塊中聲明需要對外界暴露的函數, 區別在於, public 修飾的類, 在模塊外無法繼承, 而 open 則可以任意繼承, 公開度來說, public < open
2.20 聲明一個只有一個參數沒有返回值閉包的別名
沒有返回值也就是返回值爲Void
typealias SomeClosuerType = (String) -> (Void)
let someClosuer: SomeClosuerType = { (name: String) in
print("hello,", name)
}
someClosuer("world")
// hello, world
2.21 定義靜態方法時關鍵字 static 和 class 有什麼區別
static 定義的方法不可以被子類繼承, class 則可以
class AnotherClass {
static func staticMethod(){}
class func classMethod(){}
}
class ChildOfAnotherClass: AnotherClass {
override class func classMethod(){}
//override static func staticMethod(){}// error
}
2.22 Self 的使用場景
Self 通常在協議中使用, 用來表示實現者或者實現者的子類類型.
例如, 定義一個複製的協議
protocol CopyProtocol {
func copy() -> Self
}
如果是結構體去實現, 要將 Self 換爲具體的類型
struct SomeStruct: CopyProtocol {
let value: Int
func copySelf() -> SomeStruct {
return SomeStruct(value: self.value)
}
}
如果是類去實現, 則有點複雜, 需要有一個 required 初始化方法
class SomeCopyableClass: CopyProtocol {
func copySelf() -> Self {
return type(of: self).init()
}
required init(){}
}
2.23 dynamic 的作用
由於 swift 是一個靜態語言, 所以沒有 Objective-C 中的消息發送這些動態機制, dynamic 的作用就是讓 swift 代碼也能有 Objective-C 中的動態機制, 常用的地方就是 KVO 了, 如果要監控一個屬性, 則必須要標記爲 dynamic
2.24 什麼時候使用 @objc
@objc 用途是爲了在 Objective-C 和 Swift 混編的時候, 能夠正常調用 Swift 代碼. 可以用於修飾類, 協議, 方法, 屬性。
常用的地方是在定義 delegate 協議中, 會將協議中的部分方法聲明爲可選方法, 需要用到 @objc
@objc protocol OptionalProtocol {
@objc optional func optionalFunc()
func normalFunc()
}
class OptionProtocolClass: OptionalProtocol {
func normalFunc() {
}
}
let someOptionalDelegate: OptionalProtocol = OptionProtocolClass()
someOptionalDelegate.optionalFunc?()
2.25 Optional(可選型) 是用什麼實現的
Optional 是一個泛型枚舉
大致定義如下:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
除了使用let someValue: Int? = nil
之外, 還可以使用let optional1: Optional<Int> = nil
來定義
2.26 如何自定義下標獲取
實現 subscript 即可, 如
extension AnyList {
subscript(index: Int) -> T{
return self.list[index]
}
subscript(indexString: String) -> T?{
guard let index = Int(indexString) else {
return nil
}
return self.list[index]
}
}
索引除了數字之外, 其他類型也是可以的
2.27 ?? 的作用
可選值的默認值, 當可選值爲 nil 的時候, 會返回後面的值. 如
let someValue = optional1 ?? 0
2.28 lazy 的作用
懶加載, 當屬性要使用的時候, 纔去完成初始化
如
class LazyClass {
lazy var someLazyValue: Int = {
print("lazy init value")
return 1
}()
var someNormalValue: Int = {
print("normal init value")
return 2
}()
}
let lazyInstance = LazyClass()
print(lazyInstance.someNormalValue)
print(lazyInstance.someLazyValue)
// 打印輸出
// normal init value
// 2
// lazy init value
// 1
2.29 一個類型表示選項,可以同時表示有幾個選項選中(類似 UIViewAnimationOptions ),用什麼類型表示
需要實現自 OptionSet, 一般使用 struct 實現. 由於 OptionSet 要求有一個不可失敗的 init(rawValue:) 構造器, 而 枚舉無法做到這一點 (枚舉的原始值構造器是可失敗的, 而且有些組合值, 是沒辦法用一個枚舉值表示的)
struct SomeOption: OptionSet {
let rawValue: Int
static let option1 = SomeOption(rawValue: 1 << 0)
static let option2 = SomeOption(rawValue:1 << 1)
static let option3 = SomeOption(rawValue:1 << 2)
}
let options: SomeOption = [.option1, .option2]
2.30 inout 的作用
輸入輸出參數, 如:
func swap( a: inout Int, b: inout Int) {
let temp = a
a = b
b = temp
}
var a = 1
var b = 2
print(a, b)// 1 2
swap(a: &a, b: &b)
print(a, b)// 2 1
2.31 Error 如果要兼容 NSError 需要做什麼操作
其實直接轉換就可以, 例如SomeError.someError as NSError
但是這樣沒有錯誤碼, 描述等等, 如果想和 NSError 一樣有這些東西, 只需要實現 LocalizedError
和CustomNSError
協議, 有些方法有默認實現, 可以略過, 如:
enum SomeError: Error, LocalizedError, CustomNSError {
case error1, error2
public var errorDescription: String? {
switch self {
case .error1:
return "error description error1"
case .error2:
return "error description error2"
}
}
var errorCode: Int {
switch self {
case .error1:
return 1
case .error2:
return 2
}
}
public static var errorDomain: String {
return "error domain SomeError"
}
public var errorUserInfo: [String : Any] {
switch self {
case .error1:
return ["info": "error1"]
case .error2:
return ["info": "error2"]
}
}
}
print(SomeError.error1 as NSError)
// Error Domain=error domain SomeError Code=1 "error description error1" UserInfo={info=error1}
2.32 下面的代碼都用了哪些語法糖
[1, 2, 3].map{ $0 * 2 }
[1, 2, 3] 使用了, Array 實現的 ExpressibleByArrayLiteral 協議, 用於接收數組的字面值
map{xxx} 使用了閉包作爲作爲最後一個參數時, 可以直接寫在調用後面, 而且, 如果是唯一參數的話, 圓括號也可以省略
閉包沒有聲明函數參數, 返回值類型, 數量, 依靠的是閉包類型的自動推斷
閉包中語句只有一句時, 自動將這一句的結果作爲返回值
2.33 什麼是高階函數
一個函數如果可以以某一個函數作爲參數, 或者是返回值, 那麼這個函數就稱之爲高階函數, 如 map, reduce, filter
2.34 如何解決引用循環
-
轉換爲值類型, 只有類會存在引用循環, 所以如果能不用類, 是可以解引用循環的
-
delegate 使用 weak 屬性
-
閉包中, 對有可能發生循環引用的對象, 使用 weak 或者 unowned, 修飾
2.35 下面的代碼會不會崩潰,說出原因
var mutableArray = [1,2,3]
for _ in mutableArray {
mutableArray.removeLast()
}
不會, 原理不清楚, 就算是把 removeLast(), 換成 removeAll() , 這個循環也會執行三次, 估計是在一開始, for
in 就對 mutableArray 進行了一次值捕獲, 而 Array 是一個值類型 , removeLast() 並不能修改捕獲的值。
2.36 給集合中元素是字符串的類型增加一個擴展方法,應該怎麼聲明
使用 where 子句, 限制 Element 爲 String
extension Array where Element == String {
var isStringElement:Bool {
return true
}
}
["1", "2"].isStringElement
//[1, 2].isStringElement// error
- 高級題解答區
3.1 一個 Sequence 的索引是不是一定從 0 開始?
不一定, 兩個 for in 並不能保證都是從 0 開始, 且輸出結果一致, 官方文檔如下:
Repeated Access
The Sequence protocol makes no requirement on conforming types regarding
whether they will be destructively consumed by iteration. As a
consequence, don’t assume that multiple for-in loops on a sequence
will either resume iteration or restart from the beginning:
for element in sequence {
if ... some condition { break }
}
for element in sequence {
// No defined behavior
}
有些同學還是不太理解, 我寫了一個 demo 當作參考
class Countdown: Sequence, IteratorProtocol {
var count: Int
init(count: Int) {
self.count = count
}
func next() -> Int? {
if count == 0 {
return nil
} else {
defer { count -= 1 }
return count
}
}
}
var countDown = Countdown(count: 5)
print("begin for in 1")
for c in countDown {
print(c)
}
print("end for in 1")
print("begin for in 2")
for c in countDown {
print(c)
}
print("end for in 2")
最後輸出的結果是
begin for in 1
5
4
3
2
1
end for in 1
begin for in 2
end for in 2
很明顯, 第二次沒有輸出任何結果, 原因就是在第二次 for in 的時候, 並沒有將count
重置。
3.2 數組都實現了哪些協議
MutableCollection, 實現了可修改的數組, 如a[1] = 2
ExpressibleByArrayLiteral, 實現了數組可以從 [1, 2, 3] 這種字面值初始化的能力
3.3 如何自定義模式匹配
待更新,暫時沒有最優解,讀者可在文末留言
3.4 autoclosure 的作用
自動閉包, 會自動將某一個表達式封裝爲閉包. 如
func autoClosureFunction(_ closure: @autoclosure () -> Int) {
closure()
}
autoClosureFunction(1)
3.5 編譯選項 whole module optmization 優化了什麼
編譯器可以跨文件優化編譯代碼, 不侷限於一個文件。
3.6 下面代碼中 mutating 的作用是什麼
struct Person {
var name: String {
mutating get {
return store
}
}
}
讓不可變對象無法訪問 name 屬性
3.7 如何讓自定義對象支持字面量初始化
有幾個協議, 分別是
ExpressibleByArrayLiteral
可以由數組形式初始化
ExpressibleByDictionaryLiteral
可以由字典形式初始化
ExpressibleByNilLiteral
可以由 nil 值初始化
ExpressibleByIntegerLiteral
可以由整數值初始化
ExpressibleByFloatLiteral
可以由浮點數初始化
ExpressibleByBooleanLiteral
可以由布爾值初始化
ExpressibleByUnicodeScalarLiteral
ExpressibleByExtendedGraphemeClusterLiteral
ExpressibleByStringLiteral
這三種都是由字符串初始化, 上面兩種包含有 Unicode 字符和特殊字符
3.8 dynamic framework 和 static framework 的區別是什麼
靜態庫和動態庫, 靜態庫是每一個程序單獨打包一份, 而動態庫則是多個程序之間共享
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/DIn3otUaoGMyEWZf4c99Mw