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

前言:

最近把 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. 無需擔心內存泄露問題

精選大廠 · iOS面試題答案PDF文集

  • 獲取加小編的iOS技術交流圈:937 194 184,直接獲取

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可以用於值類型,結構體,枚舉;

精選大廠 · iOS面試題答案PDF文集

  • 獲取加小編的iOS技術交流圈:937 194 184,直接獲取

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


精選大廠 · iOS面試題答案PDF文集

  • 獲取加小編的iOS技術交流圈:937 194 184,直接獲取

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” 一下,謝謝您的支持


推薦文集

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章