最新整理:iOS面試題-常問Swift問題(八)

語言: CN / TW / HK

前言:

最近把 iOS 面試中可能會遇到的問題整理了一番, 題目大部分是網上收錄的, 方便自己鞏固複習, 也分享給大家; 希望對大家有所幫助!

  • 對於答案,不一定都合適,歡迎大家積極討論;整理不易,如果您覺得還不錯,麻煩在文末 “點個贊” ,或者留下您的評論“Mark” 一下,謝謝您的支援。文章末尾有驚喜

iOS面試題-常問Swift問題(八)

1. 介紹一下 Swift?

Swift是蘋果在2014年6月WWDC釋出的全新程式語言,借鑑了JS,Python,C#,Ruby等語言特性,看上去偏指令碼化,Swift 仍支援 cocoa touch 框架

他的優點:

  1. Swift更加安全,它是型別安全的語言。
  2. Swift容易閱讀,語法和檔案結構簡易化。
  3. Swift更易於維護,檔案分離後結構更清晰。
  4. Swift程式碼更少,簡潔的語法,可以省去大量冗餘程式碼
  5. Swift速度更快,運算效能更高。

2. Swift 和OC 如何相互呼叫?

  • Swift 呼叫 OC程式碼\ 需要建立一個 Target-BriBridging-Header.h 的橋檔案,在喬檔案匯入需要呼叫的OC程式碼標頭檔案即可
  • OC 呼叫 Swift程式碼\ 直接匯入 Target-Swift.h檔案即可, Swift如果需要被OC呼叫,需要使用@objc 對方法或者屬性進行修飾

3. 類(class) 和 結構體(struct) 有什麼區別?

在 Swift 中,class 是引用型別(指標型別), struct 是值型別

值型別

  • 值型別在傳遞和賦值時將進行復制; 賦值給var、let或者給函式傳參,是直接將所有內容拷貝一份, 類似於對檔案進行copy、paste操作,產生了全新的檔案副本。屬於深拷貝(deep copy)
  • 值型別: 比如結構體,列舉,是在棧空間上儲存和操作的

引用型別

  • 引用型別只會使用引用物件的一個"指向"; 賦值給var、let或者給函式傳參,是將記憶體地址拷貝一份,類似於製作一個檔案的替身(快捷方式、連結),指向的是同一個檔案。屬於淺拷貝(shallow copy)
  • 引用型別: 比如 Class,是在堆空間上儲存和操作的

4. class 和 struct 比較,優缺點?

class 有以下功能,struct 是沒有的:

  1. class可以繼承,子類可以使用父類的特性和方法
  2. 型別轉換可以在執行時檢查和解釋一個例項物件
  3. class可以用 deinit來釋放資源
  4. 一個類可以被多次引用

struct 優勢:

  1. 結構較小,適用於複製操作,相比較一個class 例項被多次引用,struct 更安全
  2. 無需擔心記憶體洩露問題

5. Swift 中,什麼可選型(Optional)

  • 在 Swift 中,可選型是為了表達一個變數為空的情況,當一個變數為空,他的值就是 nil
  • 在型別名稱後面加個問號? 來定義一個可選型
  • 值型別或者引用型別都可以是可選型變數

**

var name: String? // 預設為 nil var age: Int? // 預設為nil print(name, age) // 列印 nil, nil

6.Swift,什麼是泛型?

  • 泛型主要是為增加程式碼的靈活性而生的,它可以是對應的程式碼滿足任意型別的的變數或方法;
  • 泛型可以將型別引數化,提高程式碼複用率,減少程式碼量

**

// 實現一個方法,可以交換實現任意型別 func swap<T>(a: inout T, b: inout T) { (a, b) = (b, a) }

7. 訪問控制關鍵字 open, public, internal, fileprivate, private 的區別?

Swift 中有個5個級別的訪問控制權限,從高到低依次是 open, public, internal, fileprivate, private

它們遵循的基本規則: 高級別的變數不允許被定義為低級別變數的成員變數,比如一個 private 的 class 內部允許包含 public的 String值,反之低階變數可以定義在高級別變數中;

  • open: 具備最高訪問許可權,其修飾的類可以和方法,可以在任意 模組中被訪問和重寫.
  • public: 許可權僅次於 open,和 open 唯一的區別是: 不允許其他模組進行繼承、重寫
  • internal: 預設許可權, 只允許在當前的模組中訪問,可以繼承和重寫,不允許在其他模組中訪問
  • fileprivate: 修飾的物件只允許在當前的檔案中訪問;
  • private: 最低級別訪問許可權,只允許在定義的作用域內訪問

8.關鍵字:Strong,Weak,Unowned 區別?

  • Swift 的記憶體管理機制同OC一致,都是ARC管理機制; Strong,和 Weak用法同OC一樣
  • Unowned(無主引用), 不會產生強引用,例項銷燬後仍然儲存著例項的記憶體地址(類似於OC中的unsafe_unretained), 試圖在例項銷燬後訪問無主引用,會產生執行時錯誤(野指標)

9. 如何理解copy-on-write?

值型別(比如:struct),在複製時,複製物件與原物件實際上在記憶體中指向同一個物件,當且僅當修改複製的物件時,才會在記憶體中建立一個新的物件,

  • 為了提升效能,Struct, String、Array、Dictionary、Set採取了Copy On Write的技術
  • 比如僅當有“寫”操作時,才會真正執行拷貝操作
  • 對於標準庫值型別的賦值操作,Swift 能確保最佳效能,所有沒必要為了保證最佳效能來避免賦值

10.什麼是屬性觀察?

屬性觀察是指在當前型別內對特性屬性進行監測,並作出響應,屬性觀察是 swift 中的特性,具有2種, willsetdidset

**

``` var title: String { willSet { print("willSet", newValue)

}
didSet {
    print("didSet", oldValue, title)
}

} ```

  • willSet會傳遞新值,預設叫newValue
  • didSet會傳遞舊值,預設叫oldValue
  • 在初始化器中設定屬性值不會觸發willSet和didSet

11. swift 為什麼將 String,Array,Dictionary設計為值型別?

  • 值型別和引用型別相比,最大優勢可以高效的使用記憶體,值型別在棧上操作,引用型別在堆上操作,棧上操作僅僅是單個指標的移動,而堆上操作牽涉到合併,位移,重連結,Swift 這樣設計減少了堆上記憶體分配和回收次數,使用 copy-on-write將值傳遞與複製開銷降到最低

12.如何將Swift 中的協議(protocol)中的部分方法設計為可選(optional)?

  • 在協議和方法前面新增 @objc,然後在方法前面新增 optional關鍵字,改方式實際上是將協議轉為了OC的方式

**

@objc protocol someProtocol { @objc optional func test() }

  • 使用擴充套件(extension),來規定可選方法,在 swift 中,協議擴充套件可以定義部分方法的預設實現

**

``` protocol someProtocol { func test() }

extension someProtocol{ func test() { print("test") } } ```

13.比較Swift 和OC中的初始化方法 (init) 有什麼不同?

swift 的初始化方法,更加嚴格和準確, swift初始化方法需要保證所有的非optional的成員變數都完成初始化, 同時 swfit 新增了convenience和 required兩個修飾初始化器的關鍵字

  • convenience只提供一種方便的初始化器,必須通過一個指定初始化器來完成初始化
  • required是強制子類重寫父類中所修飾的初始化方法

14.比較 Swift和OC中的 protocol 有什麼不同?

  • Swift 和OC中的 protocol相同點在於: 兩者都可以被用作代理;
  • 不同點: Swift中的 protocol還可以對介面進行抽象,可以實現面向協議,從而大大提高程式設計效率,Swift中的protocol可以用於值型別,結構體,列舉;

15.swift 和OC 中的自省 有什麼區別?

自省在OC中就是判斷某一物件是否屬於某一個類的操作,有以下2中方式

**

[obj iskinOfClass:[SomeClass class]] [obj isMemberOfClass:[SomeClass class]]

在 Swift 中由於很多 class 並非繼承自 NSObject, 故而 Swift 使用 is 來判斷是否屬於某一型別, is 不僅可以作用於class, 還是作用於enumstruct

16.什麼是函式過載? swift 支不支援函式過載?

  • 函式過載是指: 函式名稱相同,函式的引數個數不同, 或者引數型別不同,或引數標籤不同, 返回值型別與函式過載無關
  • swift 支援函式過載

17.swift 中的列舉,關聯值 和 原始值的區分?

  • 關聯值--有時會將列舉的成員值跟其他型別的變數關聯儲存在一起,會非常有用

    **

    // 關聯值 enum Date { case digit(year: Int, month: Int, day: Int) case string(String) }

  • 原始值--列舉成員可以使用相同型別的預設值預先關聯,這個預設值叫做:原始值

    **

    // 原始值 enum Grade: String { case perfect = "A" case great = "B" case good = "C" case bad = "D" }

18. swift 中的閉包結構是什麼樣子的?

**

{ (引數列表) -> 返回值型別 in 函式體程式碼 }

19. 什麼是尾隨閉包?

  • 將一個很長的閉包表示式作為函式的最後一個實參
  • 使用尾隨閉包可以增強函式的可讀性
  • 尾隨閉包是一個被書寫在函式呼叫括號外面(後面)的閉包表示式

**

``` // fn 就是一個尾隨閉包引數 func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) { print(fn(v1, v2)) }

// 呼叫 exec(v1: 10, v2: 20) { $0 + $1 } ```

20. 什麼是逃逸閉包?

當閉包作為一個實際引數傳遞給一個函式或者變數的時候,我們就說這個閉包逃逸了,可以在形式引數前寫 @escaping 來明確閉包是允許逃逸的。

  • 非逃逸閉包、逃逸閉包,一般都是當做引數傳遞給函式
  • 非逃逸閉包:閉包呼叫發生在函式結束前,閉包呼叫在函式作用域內
  • 逃逸閉包:閉包有可能在函式結束後呼叫,閉包呼叫逃離了函式的作用域,需要通過@escaping宣告

**

``` // 定義一個數組用於儲存閉包型別 var completionHandlers: [() -> Void] = []

// 在方法中將閉包當做實際引數,儲存到外部變數中 func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) } ```

如果你不標記函式的形式引數為 @escaping ,你就會遇到編譯時錯誤。

21. 什麼是自動閉包?

自動閉包是一種自動建立的用來把作為實際引數傳遞給函式的表示式打包的閉包。它不接受任何實際引數,並且當它被呼叫時,它會返回內部打包的表示式的值。這個語法的好處在於通過寫普通表示式代替顯式閉包而使你省略包圍函式形式引數的括號。

**

func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? { return v1 > 0 ? v1 : v2() } getFirstPositive(10, 20)

  • 為了避免與期望衝突,使用了@autoclosure的地方最好明確註釋清楚:這個值會被推遲執行
  • @autoclosure 會自動將 20 封裝成閉包 { 20 }
  • @autoclosure 只支援 () -> T 格式的引數
  • @autoclosure 並非只支援最後1個引數
  • 有@autoclosure、無@autoclosure,構成了函式過載

如果你想要自動閉包允許逃逸,就同時使用 @autoclosure 和 @escaping 標誌。

22. swift中, 儲存屬性和計算屬性的區別?

Swift中跟例項物件相關的屬性可以分為2大類

儲存屬性(Stored Property)

  • 類似於成員變數這個概念
  • 儲存在例項物件的記憶體中
  • 結構體、類可以定義儲存屬性
  • 列舉不可以定義儲存屬性

計算屬性(Computed Property)

  • 本質就是方法(函式)
  • 不佔用例項物件的記憶體
  • 列舉、結構體、類都可以定義計算屬性

**

struct Circle { // 儲存屬性 var radius: Double // 計算屬性 var diameter: Double { set { radius = newValue / 2 } get { return radius * 2 } } }

23. 什麼是延遲儲存屬性(Lazy Stored Property)?

使用lazy可以定義一個延遲儲存屬性,在第一次用到屬性的時候才會進行初始化(類似OC中的懶載入)

  • lazy屬性必須是var,不能是let

    • let必須在例項物件的初始化方法完成之前就擁有值
  • 如果多條執行緒同時第一次訪問lazy屬性

    • 無法保證屬性只被初始化1次

**

class PhotoView { // 延遲儲存屬性 lazy var image: Image = { let url = "https://...x.png" let data = Data(url: url) return Image(data: data) }() }

24. 什麼是屬性觀察器?

可以為非lazy的var儲存屬性設定屬性觀察器,通過關鍵字willSetdidSet來監聽屬性變化

**

struct Circle { var radius: Double { willSet { print("willSet", newValue) } didSet { print("didSet", oldValue, radius) } } init() { self.radius = 1.0 print("Circle init!") } }



25. swift中什麼型別屬性(Type Property)?

嚴格來說,屬性可以分為

例項屬性(Instance Property): 只能通過例項物件去訪問

  • 儲存例項屬性(Stored Instance Property):儲存在例項物件的記憶體中,每個例項物件都有1份
  • 計算例項屬性(Computed Instance Property)

型別屬性(Type Property):只能通過型別去訪問

  • 儲存型別屬性(Stored Type Property):整個程式執行過程中,就只有1份記憶體(類似於全域性變數)
  • 計算型別屬性(Computed Type Property)

可以通過static定義型別屬性 p如果是類,也可以用關鍵字class

**

struct Car { static var count: Int = 0 init() { Car.count += 1 } }

不同於儲存例項屬性,你必須給儲存型別屬性設定初始值

  • 因為型別沒有像例項物件那樣的init初始化器來初始化儲存屬性

儲存型別屬性預設就是lazy,會在第一次使用的時候才初始化

  • 就算被多個執行緒同時訪問,保證只會初始化一次
  • 儲存型別屬性可以是let

列舉型別也可以定義型別屬性(儲存型別屬性、計算型別屬性)

26. swift 中如何使用單例模式?

可以通過型別屬性+let+private 來寫單例; 程式碼如下如下:

**

public class FileManager { public static let shared = { // .... // .... return FileManager() }() private init() { } }

27.swift 中的下標是什麼?

  • 使用subscript可以給任意型別(列舉、結構體、類)增加下標功能,有些地方也翻譯為:下標指令碼
  • subscript的語法類似於例項方法、計算屬性,本質就是方法(函式)

使用如下:

**

``` class Point { var x = 0.0, y = 0.0 subscript(index: Int) -> Double { set { if index == 0 { x = newValue } else if index == 1 { y = newValue } } get { if index == 0 { return x } else if index == 1 { return y } return 0 } } }

var p = Point() // 下標賦值 p[0] = 11.1 p[1] = 22.2 // 下標訪問 print(p.x) // 11.1 print(p.y) // 22.2 ```

27.簡要說明Swift中的初始化器?

  • 類、結構體、列舉都可以定義初始化器
  • 類有2種初始化器: 指定初始化器(designated initializer)便捷初始化器(convenience initializer)

**

// 指定初始化器 init(parameters) { statements } // 便捷初始化器 convenience init(parameters) { statements }

規則:

  • 每個類至少有一個指定初始化器,指定初始化器是類的主要初始化器
  • 預設初始化器總是類的指定初始化器
  • 類偏向於少量指定初始化器,一個類通常只有一個指定初始化器

初始化器的相互呼叫規則

  • 指定初始化器必須從它的直系父類呼叫指定初始化器
  • 便捷初始化器必須從相同的類裡呼叫另一個初始化器
  • 便捷初始化器最終必須呼叫一個指定初始化器

28.什麼可選鏈?

可選鏈是一個呼叫和查詢可選屬性、方法和下標的過程,它可能為 nil 。如果可選項包含值,屬性、方法或者下標的呼叫成功;如果可選項是 nil ,屬性、方法或者下標的呼叫會返回 nil 。多個查詢可以連結在一起,如果鏈中任何一個節點是 nil ,那麼整個鏈就會得體地失敗。

  • 多個?可以連結在一起
  • 如果鏈中任何一個節點是nil,那麼整個鏈就會呼叫失敗

29. 什麼是運算子過載(Operator Overload)?

類、結構體、列舉可以為現有的運算子提供自定義的實現,這個操作叫做:運算子過載

**

``` struct Point { var x: Int var y: Int

// 過載運算子
static func + (p1: Point, p2: Point) -> Point   {
    return Point(x: p1.x + p2.x, y: p1.y + p2.y)
}

}

var p1 = Point(x: 10, y: 10) var p2 = Point(x: 20, y: 20) var p3 = p1 + p2 ```

收錄 | 原文地址


結語

再次說一聲,對於答案,不一定都合適,歡迎大家積極討論;整理不易,如果您覺得還不錯,麻煩在文末 “點個贊” ,或者留下您的評論“Mark” 一下,謝謝您的支援

推薦教程:Flutter手把手教你從入門到精通