Swift中級(jí)進(jìn)階面試題及答案,涵蓋核心特性、內(nèi)存管理、協(xié)議、泛型等關(guān)鍵知識(shí)點(diǎn),適合中級(jí)進(jìn)階開(kāi)發(fā)者考察:
1.? Swift中值類型和引用類型的核心區(qū)別是什么?各自的代表類型有哪些?
答案:
? 核心區(qū)別:值類型賦值時(shí)會(huì)復(fù)制數(shù)據(jù)(“值傳遞”),引用類型賦值時(shí)僅傳遞指針(“引用傳遞”,共享同一份數(shù)據(jù))。
? 代表類型:
值類型:Struct、Enum、Tuple、基本數(shù)據(jù)類型(Int、String等)。
引用類型:Class、Function、Closure。
2.? 可選類型(Optional)的本質(zhì)是什么?如何安全解包?
答案:
? 本質(zhì):Optional是Swift的泛型枚舉,定義為enum Optional<T> { case none; case some(T) },nil即Optional.none。
? 安全解包方式:
可選綁定:if let/guard let
可選鏈:object?.property?.method()(若鏈中任一環(huán)節(jié)為nil,整體返回nil)
nil合并運(yùn)算符:let value = optionalValue ?? defaultValue
3.? 協(xié)議擴(kuò)展(Protocol Extension)的作用是什么?與類擴(kuò)展有何不同?
答案:
? 作用:為協(xié)議添加默認(rèn)實(shí)現(xiàn)(方法、計(jì)算屬性等),無(wú)需讓遵守協(xié)議的類型手動(dòng)實(shí)現(xiàn),提升代碼復(fù)用性。
? 與類擴(kuò)展的區(qū)別:
協(xié)議擴(kuò)展可給所有遵守協(xié)議的類型添加功能;類擴(kuò)展僅作用于單個(gè)類。
協(xié)議擴(kuò)展中無(wú)法添加存儲(chǔ)屬性;類擴(kuò)展可以添加計(jì)算屬性,但同樣不能添加存儲(chǔ)屬性。
4.? 泛型約束(Generic Constraints)如何使用?舉例說(shuō)明where子句的用法。
答案:
? 泛型約束用于限制泛型參數(shù)的類型范圍,確保其滿足特定條件(如遵守協(xié)議、繼承自某類)。
? where子句示例:
// 約束T必須遵守Equatable協(xié)議
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
? ? for (index, item) in array.enumerated() {
? ? ? ? if item == value { return index }
? ? }
? ? return nil
}
// 多約束場(chǎng)景(T遵守Equatable,U繼承自UIView)
func someFunction<T, U>(t: T, u: U) where T: Equatable, U: UIView { ... }
5.? 閉包的“捕獲值”機(jī)制是什么?捕獲列表(Capture List)的作用?
答案:
? 捕獲值:閉包會(huì)捕獲其定義環(huán)境中的變量/常量,延長(zhǎng)其生命周期(即使原作用域銷毀,閉包仍可訪問(wèn))。
? 捕獲列表:在閉包參數(shù)前用[]定義,用于修改捕獲方式(解決強(qiáng)引用循環(huán)),例如:
class Person {
? ? var name: String
? ? init(name: String) { self.name = name }
? ? lazy var closure: () -> Void = { [weak self] in? // 弱引用捕獲self,避免循環(huán)
? ? ? ? print(self?.name ?? "已釋放")
? ? }
}
6.? ARC如何工作?強(qiáng)引用循環(huán)(Strong Reference Cycles)如何產(chǎn)生和解決?
答案:
? ARC(自動(dòng)引用計(jì)數(shù)):通過(guò)跟蹤對(duì)象的強(qiáng)引用數(shù)量,當(dāng)計(jì)數(shù)為0時(shí)自動(dòng)釋放內(nèi)存。
? 強(qiáng)引用循環(huán)產(chǎn)生場(chǎng)景:兩個(gè)對(duì)象互相強(qiáng)引用(如類實(shí)例與閉包互相引用)。
? 解決方式:
對(duì)類實(shí)例:用weak(可選類型,對(duì)象釋放后變?yōu)閚il)或unowned(非可選,對(duì)象必須始終存在)。
對(duì)閉包:通過(guò)捕獲列表[weak self]或[unowned self]弱化引用。
7.? 枚舉的“關(guān)聯(lián)值”和“原始值”有何區(qū)別?
答案:
? 關(guān)聯(lián)值:為枚舉成員附加動(dòng)態(tài)數(shù)據(jù)(編譯期未知),同一成員可關(guān)聯(lián)不同類型。
enum Result {
? ? case success(Int)? // 關(guān)聯(lián)“成功碼”
? ? case failure(String)? // 關(guān)聯(lián)“錯(cuò)誤信息”
}
? 原始值:枚舉成員的固定值(編譯期已知),所有成員必須是同一類型,通過(guò)rawValue訪問(wèn)。
enum Direction: String {
? ? case north = "N"
? ? case south = "S"
}
print(Direction.north.rawValue)? // 輸出 "N"
8.? 結(jié)構(gòu)體(Struct)和類(Class)的核心區(qū)別?
答案:
? 繼承:類支持繼承,結(jié)構(gòu)體不支持。
? 引用類型vs值類型:類是引用類型(共享數(shù)據(jù)),結(jié)構(gòu)體是值類型(復(fù)制數(shù)據(jù))。
? 析構(gòu)器:類可定義deinit(對(duì)象銷毀前調(diào)用),結(jié)構(gòu)體不能。
? 身份標(biāo)識(shí):類有唯一身份(可用===比較是否為同一實(shí)例),結(jié)構(gòu)體無(wú)(僅比較值是否相等)。
9.? Swift的錯(cuò)誤處理機(jī)制是什么?try、try?、try!的區(qū)別?
答案:
? 機(jī)制:通過(guò)Error協(xié)議定義錯(cuò)誤類型,用throw拋出錯(cuò)誤,do-catch捕獲處理。
? 區(qū)別:
try:必須配合do-catch,明確處理錯(cuò)誤。
try?:將錯(cuò)誤轉(zhuǎn)為Optional(成功返回值,失敗返回nil),無(wú)需catch。
try!:強(qiáng)制解包(認(rèn)為不會(huì)出錯(cuò)),出錯(cuò)時(shí)崩潰。
10. 屬性觀察器(willSet、didSet)的作用?有哪些限制?
答案:
? 作用:監(jiān)聽(tīng)屬性值的變化,willSet在值改變前調(diào)用(參數(shù)為新值),didSet在值改變后調(diào)用(參數(shù)為舊值)。
? 限制:
不能用于lazy屬性(初始化時(shí)機(jī)不確定)。
初始化器中修改屬性不會(huì)觸發(fā)觀察器(僅外部修改時(shí)觸發(fā))。
計(jì)算屬性無(wú)需觀察器(可直接在set中處理)。
11. 延遲存儲(chǔ)屬性(lazy var)的特點(diǎn)?
答案:
? 特點(diǎn):
首次訪問(wèn)時(shí)才初始化(延遲初始化),節(jié)省資源。
必須用var(不可變let要求編譯期確定值)。
初始化閉包中可訪問(wèn)self(因初始化延遲到訪問(wèn)時(shí),self已完全初始化)。
? 示例:
class DataManager {
? ? lazy var database: Database = {? // 首次訪問(wèn)時(shí)才創(chuàng)建數(shù)據(jù)庫(kù)實(shí)例
? ? ? ? Database.connect()
? ? }()
}
12. 協(xié)議中的“關(guān)聯(lián)類型”(associatedtype)如何使用?
答案:
? 作用:在協(xié)議中聲明一個(gè)占位類型,由遵守協(xié)議的類型指定具體類型(類似泛型協(xié)議)。
? 示例:
protocol Container {
? ? associatedtype Item? // 關(guān)聯(lián)類型
? ? mutating func append(_ item: Item)
? ? var count: Int { get }
}
// 遵守協(xié)議時(shí)指定Item為Int
struct IntContainer: Container {
? ? typealias Item = Int? // 可省略(編譯器自動(dòng)推斷)
? ? private var items: [Int] = []
? ? mutating func append(_ item: Int) { items.append(item) }
? ? var count: Int { items.count }
}
13. 逃逸閉包(@escaping)的使用場(chǎng)景?為什么需要標(biāo)記?
答案:
? 場(chǎng)景:閉包生命周期超過(guò)函數(shù)調(diào)用周期(如異步操作回調(diào)、存儲(chǔ)在外部變量中)。
? 為什么標(biāo)記:
非逃逸閉包(默認(rèn)):函數(shù)返回后自動(dòng)銷毀,編譯器可優(yōu)化內(nèi)存(如不捕獲self)。
逃逸閉包:需顯式標(biāo)記@escaping,告知編譯器其生命周期更長(zhǎng),需手動(dòng)處理引用(如避免循環(huán)引用)。
? 示例:
var completionHandlers: [() -> Void] = []
func addCallback(_ callback: @escaping () -> Void) {? // 逃逸閉包(存儲(chǔ)到外部數(shù)組)
? ? completionHandlers.append(callback)
}
14. 自動(dòng)閉包(@autoclosure)的作用?
答案:
? 作用:自動(dòng)將表達(dá)式包裝為閉包,延遲表達(dá)式執(zhí)行(直到閉包被調(diào)用),簡(jiǎn)化語(yǔ)法。
? 示例:
// 不使用自動(dòng)閉包:需顯式傳入閉包
func check(condition: () -> Bool) {
? ? if condition() { print("Valid") }
}
check(condition: { 1 > 0 })? // 調(diào)用時(shí)需寫(xiě)閉包
// 使用自動(dòng)閉包:直接傳表達(dá)式
func checkAuto(@autoclosure condition: () -> Bool) {
? ? if condition() { print("Valid") }
}
checkAuto(condition: 1 > 0)? // 自動(dòng)包裝為閉包,更簡(jiǎn)潔
15. 擴(kuò)展(Extension)的限制?
答案:
? 限制:
不能添加存儲(chǔ)屬性(只能添加計(jì)算屬性)。
不能重寫(xiě)原有方法(可添加新方法,或通過(guò)協(xié)議擴(kuò)展提供默認(rèn)實(shí)現(xiàn))。
不能添加指定初始化器(可添加便利初始化器,但需調(diào)用原有初始化器)。
16. 類型轉(zhuǎn)換操作符is、as?、as!的區(qū)別?
答案:
? is:檢查實(shí)例是否為某類型,返回Bool。
? as?:安全轉(zhuǎn)換為目標(biāo)類型,返回Optional(成功為值,失敗為nil)。
? as!:強(qiáng)制轉(zhuǎn)換(認(rèn)為一定成功),失敗時(shí)崩潰(謹(jǐn)慎使用)。
? 示例:
class Animal {}
class Dog: Animal {}
let pet: Animal = Dog()
print(pet is Dog)? // true(is檢查)
if let dog = pet as? Dog { ... }? // 安全轉(zhuǎn)換(as?)
let dog = pet as! Dog? // 強(qiáng)制轉(zhuǎn)換(as!)
17. 下標(biāo)(subscript)的作用?如何自定義?
答案:
? 作用:讓自定義類型支持類似數(shù)組/字典的下標(biāo)訪問(wèn)(instance[index])。
? 示例(為String擴(kuò)展下標(biāo)訪問(wèn)字符):
extension String {
? ? subscript(index: Int) -> Character? {
? ? ? ? guard index >= 0, let strIndex = self.index(startIndex, offsetBy: index, limitedBy: endIndex) else {
? ? ? ? ? ? return nil
? ? ? ? }
? ? ? ? return self[strIndex]
? ? }
}
let str = "Swift"
print(str[1])? // "w"
18. static和class關(guān)鍵字在定義方法時(shí)的區(qū)別?
答案:
? 共同點(diǎn):都用于定義類型方法(無(wú)需實(shí)例即可調(diào)用)。
? 區(qū)別:
static:不可被子類重寫(xiě)( final 類型方法)。
class:可被子類重寫(xiě)(僅類支持,結(jié)構(gòu)體/枚舉只能用static)。
? 示例:
class Parent {
? ? static func staticMethod() {}? // 不可重寫(xiě)
? ? class func classMethod() {}? ? // 可重寫(xiě)
}
class Child: Parent {
? ? override class func classMethod() {}? // 允許重寫(xiě)
}
19. 自動(dòng)閉包(@autoclosure)與逃逸閉包結(jié)合使用的場(chǎng)景?
答案:
? 場(chǎng)景:需要延遲執(zhí)行表達(dá)式,且閉包需逃逸(如異步驗(yàn)證)。
? 示例(異步檢查條件):
func checkLater(@autoclosure @escaping condition: () -> Bool, completion: @escaping (Bool) -> Void) {
? ? DispatchQueue.global().async {
? ? ? ? let result = condition()? // 延遲執(zhí)行表達(dá)式
? ? ? ? completion(result)
? ? }
}
checkLater(1 > 0) { print($0) }? // 自動(dòng)包裝為閉包,且支持逃逸
20. Result類型的作用?
答案:
? 作用:統(tǒng)一處理異步操作的結(jié)果(成功/失?。?,避免回調(diào)嵌套或多個(gè)可選參數(shù)。
? 定義:enum Result<Success, Failure: Error> { case success(Success); case failure(Failure) }
? 示例:
func fetchData(completion: @escaping (Result<String, Error>) -> Void) {
? ? if let data = "response".data(using: .utf8) {
? ? ? ? completion(.success(String(data: data, encoding: .utf8)!))
? ? } else {
? ? ? ? completion(.failure(NSError(domain: "FetchError", code: -1)))
? ? }
}
21. 委托模式(Delegate Pattern)在Swift中如何實(shí)現(xiàn)?
答案:
? 實(shí)現(xiàn)步驟:
1. 定義協(xié)議(聲明委托方法)。
2. 委托方聲明weak var delegate: 協(xié)議?(用weak避免循環(huán)引用)。
3. 代理方遵守協(xié)議并實(shí)現(xiàn)方法。
? 示例:
// 1. 定義協(xié)議
protocol ButtonDelegate: AnyObject {? // 限制為類類型,支持weak
? ? func buttonTapped()
}
// 2. 委托方(按鈕)
class Button {
? ? weak var delegate: ButtonDelegate?? // weak避免循環(huán)引用
? ? func tap() { delegate?.buttonTapped() }
}
// 3. 代理方(視圖控制器)
class ViewController: ButtonDelegate {
? ? let button = Button()
? ? init() { button.delegate = self }
? ? func buttonTapped() { print("按鈕被點(diǎn)擊") }
}
22. @objc的作用?何時(shí)需要使用?
答案:
? 作用:將Swift聲明暴露給Objective-C(生成Objective-C兼容的接口)。
? 使用場(chǎng)景:
調(diào)用Objective-C代碼時(shí)(如UIKit的@selector)。
實(shí)現(xiàn)NSObject的方法(如KVO、通知)。
標(biāo)記需要被Objective-C反射的成員。
23. 閉包的“尾隨閉包”(Trailing Closure)語(yǔ)法?
答案:
? 作用:當(dāng)函數(shù)最后一個(gè)參數(shù)是閉包時(shí),可將閉包寫(xiě)在函數(shù)括號(hào)外,簡(jiǎn)化語(yǔ)法。
? 示例:
// 普通寫(xiě)法
UIView.animate(withDuration: 0.3, animations: {
? ? self.view.alpha = 0
})
// 尾隨閉包寫(xiě)法(省略參數(shù)名)
UIView.animate(withDuration: 0.3) {
? ? self.view.alpha = 0
}
24. 如何實(shí)現(xiàn)單例模式(Singleton)?需注意什么?
答案:
? 實(shí)現(xiàn)(線程安全版):
class NetworkManager {
? ? static let shared: NetworkManager = {
? ? ? ? let instance = NetworkManager()
? ? ? ? // 初始化配置
? ? ? ? return instance
? ? }()
? ? private init() {}? // 私有初始化器,禁止外部創(chuàng)建實(shí)例
}
? 注意:
私有初始化器(private init)防止外部實(shí)例化。
線程安全:Swift的static let初始化是原子操作,默認(rèn)線程安全。
25. GCD中async和sync的區(qū)別?
答案:
? async:異步執(zhí)行(不阻塞當(dāng)前線程),提交任務(wù)后立即返回,任務(wù)在指定隊(duì)列中后臺(tái)執(zhí)行。
? sync:同步執(zhí)行(阻塞當(dāng)前線程),等待任務(wù)執(zhí)行完成后才返回。
? 注意:避免在主線程中用sync調(diào)用主線程隊(duì)列(會(huì)導(dǎo)致死鎖)。
26. 可失敗初始化器(init?)的作用?
答案:
? 作用:初始化可能失敗時(shí)(如參數(shù)無(wú)效),返回nil表示失?。ㄆ胀ǔ跏蓟鞅仨毘晒Γ?/p>
? 示例:
struct PositiveNumber {
? ? let value: Int
? ? init?(value: Int) {? // 可失敗初始化器
? ? ? ? guard value > 0 else { return nil }? // 失敗返回nil
? ? ? ? self.value = value
? ? }
}
let num = PositiveNumber(value: -5)? // nil(初始化失?。?/p>
27. defer語(yǔ)句的作用?
答案:
? 作用:確保代碼塊在當(dāng)前作用域退出前執(zhí)行(無(wú)論正常退出還是錯(cuò)誤拋出),常用于資源釋放(如關(guān)閉文件、解鎖鎖)。
? 示例:
func readFile() {
? ? let file = openFile()
? ? defer { closeFile(file) }? // 函數(shù)退出前一定會(huì)關(guān)閉文件
? ? // 讀取文件(即使中途return或throw,defer仍執(zhí)行)
? ? if errorOccurred { return }
}
28. 函數(shù)重載(Function Overloading)的條件?
答案:
? 條件:同一作用域內(nèi),函數(shù)名相同但參數(shù)列表不同(參數(shù)個(gè)數(shù)、類型、標(biāo)簽不同),與返回值無(wú)關(guān)。
? 示例:
func sum(a: Int, b: Int) -> Int { a + b }
func sum(a: Double, b: Double) -> Double { a + b }? // 重載(類型不同)
func sum(_ a: Int, _ b: Int) -> Int { a + b }? // 重載(標(biāo)簽不同)
29. 屬性包裝器(@propertyWrapper)的基本原理?
答案:
? 原理:將屬性的讀寫(xiě)邏輯封裝到一個(gè)結(jié)構(gòu)體/枚舉/類中,簡(jiǎn)化重復(fù)邏輯(如驗(yàn)證、存儲(chǔ))。
? 示例(限制數(shù)值范圍的包裝器):
@propertyWrapper
struct Clamped<Value: Comparable> {
? ? private var value: Value
? ? let min: Value, max: Value
? ?
? ? init(wrappedValue: Value, min: Value, max: Value) {
? ? ? ? self.min = min
? ? ? ? self.max = max
? ? ? ? self.value = wrappedValue.clamped(to: min...max)? // 初始化時(shí)驗(yàn)證
? ? }
? ?
? ? var wrappedValue: Value {
? ? ? ? get { value }
? ? ? ? set { value = newValue.clamped(to: min...max) }? // 設(shè)置時(shí)驗(yàn)證
? ? }
}
// 使用
struct Product {
? ? @Clamped(min: 0, max: 100) var stock: Int = 50? // 庫(kù)存限制在0-100
}
30. Swift中如何實(shí)現(xiàn)KVO?
答案:
? 步驟:
1. 類必須繼承NSObject(KVO基于Objective-C運(yùn)行時(shí))。
2. 被觀察屬性用@objc dynamic標(biāo)記(確保動(dòng)態(tài)派發(fā))。
3. 調(diào)用addObserver(_:forKeyPath:options:context:)注冊(cè)觀察者。
4. 實(shí)現(xiàn)observeValue(forKeyPath:of:change:context:)處理變化。
5. 移除觀察者(避免崩潰)。
? 示例:
class User: NSObject {
? ? @objc dynamic var name: String? // 必須@objc dynamic
? ? init(name: String) { self.name = name }
}
class Observer: NSObject {
? ? override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
? ? ? ? print("name changed to: \(change?[.newKey] as! String)")
? ? }
}
let user = User(name: "Tom")
let observer = Observer()
user.addObserver(observer, forKeyPath: "name", options: .new, context: nil)
user.name = "Jerry"? // 觸發(fā)KVO,輸出變化