- 總結(jié)關(guān)于swift的面試題------持續(xù)更新
- 來源于網(wǎng)上、書籍等
- 侵權(quán)即刪-聯(lián)系我
1.Class 和 Struct 的區(qū)別
類是引用類型, 結(jié)構(gòu)體為值類型
結(jié)構(gòu)體不可以繼承
值類型被賦予給一個變量、常量或者被傳遞給一個函數(shù)的時候,其值會被拷貝
引用類型在被賦予到一個變量、常量或者被傳遞到一個函數(shù)時,其值不會被拷貝。因此,引用的是已存在的實(shí)例本身而不是其拷貝
2.理解Swift值類型的寫時復(fù)制
只有當(dāng)一個結(jié)構(gòu)體發(fā)生了寫入行為時才會有復(fù)制行為。
在結(jié)構(gòu)體內(nèi)部用一個引用類型來存儲實(shí)際的數(shù)據(jù),在不進(jìn)行寫入操作的普通傳遞過程中,都是將內(nèi)部的reference的應(yīng)用計數(shù)+1,在進(jìn)行寫入操作時,對內(nèi)部的reference做一次copy操作用來存儲新的數(shù)據(jù),防止和之前的reference產(chǎn)生意外的數(shù)據(jù)共享。
swift中提供該[isKnownUniquelyReferenced]函數(shù),他能檢查一個類的實(shí)例是不是唯一的引用,如果是,我們就不需要對結(jié)構(gòu)體實(shí)例進(jìn)行復(fù)制,如果不是,說明對象被不同的結(jié)構(gòu)體共享,這時對它進(jìn)行更改就需要進(jìn)行復(fù)制。
3.defer的用法
使用defer代碼塊來表示在函數(shù)返回前,函數(shù)中最后執(zhí)行的代碼。無論函數(shù)是否會拋出錯誤,這段代碼都將執(zhí)行。
-
defer 語句塊中的代碼, 會在當(dāng)前作用域結(jié)束前調(diào)用。每當(dāng)一個作用域結(jié)束就進(jìn)行該作用域defer執(zhí)行。
func doSomethingFile{ openDirectory() defer{ closeDirectory() } openFile() defer{ closeFile() } // do other things }
4.inout 輸入輸出參數(shù)
函數(shù)參數(shù)默認(rèn)為常量。試圖從函數(shù)主體內(nèi)部更改函數(shù)參數(shù)的值會導(dǎo)致編譯時錯誤。這意味著您不能錯誤地更改參數(shù)的值。如果您希望函數(shù)修改參數(shù)的值,并且希望這些更改在函數(shù)調(diào)用結(jié)束后仍然存在,請將該參數(shù)定義為輸入輸出參數(shù)。
您可以通過將inout關(guān)鍵字放在參數(shù)類型的前面來編寫輸入/輸出參數(shù)。一個在出參數(shù)具有傳遞的值中,由函數(shù)修改的功能,并將該部分送回出的功能來代替原來的值。有關(guān)輸入輸出參數(shù)的行為以及相關(guān)的編譯器優(yōu)化的詳細(xì)討論,請參見輸入輸出參數(shù)。
您只能將變量作為輸入輸出參數(shù)的參數(shù)傳遞。您不能將常量或文字值作為參數(shù)傳遞,因為無法修改常量和文字。當(dāng)您將一個與號(&)作為變量傳入in-out參數(shù)時,將它放在變量名的前面,以表明該變量可以被函數(shù)修改。
-
注意:輸入輸出參數(shù)不能具有默認(rèn)值,并且可變參數(shù)不能標(biāo)記為inout。
let temporaryA = a a = b b = temporaryA } // 參數(shù)a本身定義是常量,inout修飾,可以修改a的值 var someInt = 3 var anotherInt = 107 swapTwoInts(&someInt, &anotherInt) print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") // Prints "someInt is now 107, and anotherInt is now 3"
5.什么是高階函數(shù)
- 一個函數(shù)如果可以以某一個函數(shù)作為參數(shù), 或者是返回值, 那么這個函數(shù)就稱之為高階函數(shù)
6. static和class的區(qū)別
- 在Swift中static和class都表示“類型范圍作用域”的關(guān)鍵字。在所有類型中(class、static、enum)中,我們可以使用static來描述類型作用域。class是專門用于修飾class類型的。
- 1.static可以修飾屬性和方法
- 所修飾的屬性和方法不能夠被重寫。
- static修飾的類方法和屬性包含了final關(guān)鍵字的特性,重寫會報錯
- 2.class修飾方法和計算屬性
- 我們同樣可以使用class修飾方法和計算屬性,但是不能夠修飾存儲屬性。
- 類方法和計算屬性是可以被重寫的,可以使用class關(guān)鍵字也可以是static
7.自定義模式匹配模式
可參考 swift模式和模式匹配
-
模式:
代表單個或者復(fù)合值得結(jié)構(gòu),也就是說模式不是一個特定的值,它是一種抽象的結(jié)構(gòu),【一句話,不是特指,是泛指】。這樣就可以用模式來匹配各種各樣的值。- 例如:(x,y)可以匹配元祖(1.2),以及任何包含兩個元素的元組。
除了利用模式匹配一個值以外,你可以從復(fù)合值中提取出部分或全部值,然后把各個部分的值和一個常量或變量綁定起來。
- 例如:(x,y)可以匹配元祖(1.2),以及任何包含兩個元素的元組。
-
swift中的模式分為兩類:
- 一種能匹配任何類型的值,另一種在運(yùn)行時匹配某個特定的值,可能會失敗。
- 第一種模式用于結(jié)構(gòu)簡單變量,常量和可選綁定中的值。此類模式包括通配符模式,標(biāo)識符模式,以及包含前兩種模式的值綁定模式和元組模式。你可以為這類模式指定一個類型標(biāo)注,從而限制它們只能匹配某種特定類型的值。
- 第二種模式用于全局模式匹配。這種情況下,你試圖匹配的值在運(yùn)行時可能不存在。此類模式包括枚舉用例模式,可選模式,表達(dá)式模式和類型轉(zhuǎn)換模式。你在switch語句的case標(biāo)簽中,do語句的catch 子句中,或者再if,while,guard,for-in語句的case條件語句中使用這類模式。
-
重載 ~= 該運(yùn)算符
switch 80 { case "eighty": //編譯通過并且匹配 case "not eighty": default: break } //以上代碼編譯直接失敗失敗 //重載 ~= 函數(shù) func ~= (pattern: String, value: Int) -> Bool { if pattern == "eighty" { return value == 80 } else if pattern == "not eighty" { return value != 80 } else { return false } } switch 80 { case "eighty": //編譯通過并且匹配 case "not eighty": default: break } 該switch編譯通過
8.dynamic framework 和 static framework 的區(qū)別是什么
可參考該文章
靜態(tài)庫和動態(tài)庫, 靜態(tài)庫是每一個程序單獨(dú)打包一份, 而動態(tài)庫則是多個程序之間共享
靜態(tài)庫和動態(tài)庫是相對編譯期和運(yùn)行期的:靜態(tài)庫在程序編譯時會被鏈接到目標(biāo)代碼中,程序運(yùn)行時將不再需要改靜態(tài)庫;而動態(tài)庫在程序編譯時并不會被鏈接到目標(biāo)代碼中,只是在程序運(yùn)行時才被載入,因為在程序運(yùn)行期間還需要動態(tài)庫的存在。
-
靜態(tài)庫 好處:
- 模塊化,分工合作,提高了代碼的復(fù)用及核心技術(shù)的保密程度
- 避免少量改動經(jīng)常導(dǎo)致大量的重復(fù)編譯連接
- 也可以重用,注意不是共享使用
-
動態(tài)庫 好處:
- 使用動態(tài)庫,可以將最終可執(zhí)行文件體積縮小,將整個應(yīng)用程序分模塊,團(tuán)隊合作,進(jìn)行分工,影響比較小
- 使用動態(tài)庫,多個應(yīng)用程序共享內(nèi)存中得同一份庫文件,節(jié)省資源
- 使用動態(tài)庫,可以不重新編譯連接可執(zhí)行程序的前提下,更新動態(tài)庫文件達(dá)到更新應(yīng)用程序的目的。
-
不同點(diǎn):
- 靜態(tài)庫在鏈接時,會被完整的復(fù)制到可執(zhí)行文件中,如果多個App都使用了同一個靜態(tài)庫,那么每個App都會拷貝一份,缺點(diǎn)是浪費(fèi)內(nèi)存。類似于定義一個基本變量,使用該基本變量是是新復(fù)制了一份數(shù)據(jù),而不是原來定義的;
- 動態(tài)庫不會復(fù)制,只有一份,程序運(yùn)行時動態(tài)加載到內(nèi)存中,系統(tǒng)只會加載一次,多個程序共用一份,節(jié)約了內(nèi)存。類似于使用變量的內(nèi)存地址一樣,使用的是同一個變量;
-
共同點(diǎn):
- 靜態(tài)庫和動態(tài)庫都是閉源庫,只能拿來滿足某個功能的使用,不會暴露內(nèi)部具體的代碼信息
9. Swift 與 Objective-C 的聯(lián)系與區(qū)別?
- Swift和Objective-C 共用一套運(yùn)行時環(huán)境,Swift 的類型可以橋接到Objective-C(下面我簡稱OC),反之亦然。兩者可以互相引用混合編程。
其次就是,OC 之前積累的很多類庫,在 Swift 中大部分依然可以直接使用,當(dāng)然,Swift3之后,一些語法改變了很多,不過還是有跡可循的。OC出現(xiàn)過的絕大多數(shù)概念,比如引用計數(shù)、ARC、屬性、協(xié)議、接口、初始化、擴(kuò)展類、命名參數(shù)、匿名函數(shù)等,在Swift中繼續(xù)有效(可能最多換個術(shù)語)。Swift大多數(shù)概念與OC一樣。當(dāng)然Swift也多出了一些新興概念,這些在OC中是沒有的,比如范型、元組等。
10. Swift 比 Objective-C 有什么優(yōu)勢?
- Swift 容易閱讀,語法和文件結(jié)構(gòu)簡易化。
- Swift 更易于維護(hù),文件分離后結(jié)構(gòu)更清晰。
- Swift 更加安全,它是類型安全的語言。
- Swift 代碼更少,簡潔的語法,可以省去大量冗余代碼。
- Swift 速度更快,運(yùn)算性能更高。
11.Swift 是面向?qū)ο筮€是函數(shù)式的編程語言?
Swift 既是面向?qū)ο蟮?,又是函?shù)式的編程語言。
說 Swift 是面向?qū)ο蟮恼Z言,是因為 Swift 支持類的封裝、繼承、和多態(tài),從這點(diǎn)上來看與 Java 這類純面向?qū)ο蟮恼Z言幾乎毫無差別。說 Swift 是函數(shù)式編程語言,是因為 Swift 支持 map, reduce, filter, flatmap 這類去除中間狀態(tài)、數(shù)學(xué)函數(shù)式的方法,更加強(qiáng)調(diào)運(yùn)算結(jié)果而不是中間過程。
12.請說明并比較以下關(guān)鍵詞:Open, Public, Internal, File-private, Private
Swift 有五個級別的訪問控制權(quán)限,從高到底依次為比如 Open, Public, Internal, File-private, Private。
他們遵循的基本原則是:高級別的變量不允許被定義為低級別變量的成員變量。比如一個 private 的 class 中不能含有 public 的 String。反之,低級別的變量卻可以定義在高級別的變量中。比如 public 的 class 中可以含有 private 的 Int。
Open 具備最高的訪問權(quán)限。其修飾的類和方法可以在任意 Module 中被訪問和重寫;它是 Swift 3 中新添加的訪問權(quán)限。
Public 的權(quán)限僅次于 Open。與 Open 唯一的區(qū)別在于它修飾的對象可以在任意 Module 中被訪問,但不能重寫。
Internal 是默認(rèn)的權(quán)限。它表示只能在當(dāng)前定義的 Module 中訪問和重寫,它可以被一個 Module 中的多個文件訪問,但不可以被其他的 Module 中被訪問。
File-private 也是 Swift 3 新添加的權(quán)限。其被修飾的對象只能在當(dāng)前文件中被使用。例如它可以被一個文件中的 class,extension,struct 共同使用。
Private 是最低的訪問權(quán)限。它的對象只能在定義的作用域內(nèi)使用。離開了這個作用域,即使是同一個文件中的其他作用域,也無法訪問。
13.請說明并比較以下關(guān)鍵詞:strong, weak, unowned
Swift 的內(nèi)存管理機(jī)制與 Objective-C一樣為 ARC(Automatic Reference Counting)。它的基本原理是,一個對象在沒有任何強(qiáng)引用指向它時,其占用的內(nèi)存會被回收。反之,只要有任何一個強(qiáng)引用指向該對象,它就會一直存在于內(nèi)存中。
strong 代表著強(qiáng)引用,是默認(rèn)屬性。當(dāng)一個對象被聲明為 strong 時,就表示父層級對該對象有一個強(qiáng)引用的指向。此時該對象的引用計數(shù)會增加1。
weak 代表著弱引用。當(dāng)對象被聲明為 weak 時,父層級對此對象沒有指向,該對象的引用計數(shù)不會增加1。它在對象釋放后弱引用也隨即消失。繼續(xù)訪問該對象,程序會得到 nil,不虧崩潰
unowned 與弱引用本質(zhì)上一樣。唯一不同的是,對象在釋放后,依然有一個無效的引用指向?qū)ο螅皇?Optional 也不指向 nil。如果繼續(xù)訪問該對象,程序就會崩潰。
-
加分回答:
weak 和 unowned 的引入是為了解決由 strong 帶來的循環(huán)引用問題。簡單來說,就是當(dāng)兩個對象互相有一個強(qiáng)指向去指向?qū)Ψ?,這樣導(dǎo)致兩個對象在內(nèi)存中無法釋放。
-
weak 和 unowned 的使用場景有如下差別:
- 當(dāng)訪問對象時該對象可能已經(jīng)被釋放了,則用 weak。比如 delegate 的修飾。
- 當(dāng)訪問對象確定不可能被釋放,則用 unowned。比如 self 的引用。
- 實(shí)際上為了安全起見,很多公司規(guī)定任何時候都使用 weak 去修飾。
14. 說說Swift為什么將String,Array,Dictionary設(shè)計成值類型?
要解答這個問題,就要和Objective-C中相同的數(shù)據(jù)結(jié)構(gòu)設(shè)計進(jìn)行比較。Objective-C中,字符串,數(shù)組,字典,皆被設(shè)計為引用類型。
值類型相比引用類型,最大的優(yōu)勢在于內(nèi)存使用的高效。值類型在棧上操作,引用類型在堆上操作。棧上的操作僅僅是單個指針的上下移動,而堆上的操作則牽涉到合并、移位、重新鏈接等。也就是說Swift這樣設(shè)計,大幅減少了堆上的內(nèi)存分配和回收的次數(shù)。同時copy-on-write又將值傳遞和復(fù)制的開銷降到了最低。
String,Array,Dictionary設(shè)計成值類型,也是為了線程安全考慮。通過Swift的let設(shè)置,使得這些數(shù)據(jù)達(dá)到了真正意義上的“不變”,它也從根本上解決了多線程中內(nèi)存訪問和操作順序的問題。
設(shè)計成值類型還可以提升API的靈活度。例如通過實(shí)現(xiàn)Collection這樣的協(xié)議,我們可以遍歷String,使得整個開發(fā)更加靈活高效。
15. 閉包是引用類型嗎?
- 閉包是引用類型。如果一個閉包被分配給一個變量,這個變量復(fù)制給另一個變量,那么他們引用的是同一個閉包,他們的捕捉列表也會被復(fù)制。
16.Swift mutating關(guān)鍵字的使用?
- 類是引用類型,而結(jié)構(gòu)和枚舉是值類型。默認(rèn)情況下,不能在其實(shí)例方法中修改值類型的屬性。為了修改值類型的屬性,必須在實(shí)例方法中使用mutating關(guān)鍵字。使用此關(guān)鍵字,您的方法將能夠更改屬性的值,并在方法實(shí)現(xiàn)結(jié)束時將其寫回到原始結(jié)構(gòu)
17.Swift定義常量 和 OC定義常量的區(qū)別?
OC:
const int price = 0;
Swift:
let price = 0
- OC中用 const 來表示常量,而 Swift 中用 let 來判斷是不是常量
- OC中 const 常量類型和數(shù)值是在編譯時確定的
- Swift 中 let 常量(只能賦值一次),其類型和值既可以是靜態(tài)的,也可以是一個動態(tài)的計算方法,它們在運(yùn)行時確定的。
18. 閉包
-
閉包和函數(shù)是引用類型,將函數(shù)或閉包賦值給一個常量還是變量,實(shí)際上都是將常量或變量的值設(shè)置為對應(yīng)函數(shù)或閉包的引用。func makeInCount(count: Int) -> () -> Int { var total = 0 func incrementer() -> Int { total += count return count } return incrementer } let incrementBySeven = makeInCount(count: 7) incrementBySeven() let alsoIncrementBySeven = incrementBySeven alsoIncrementBySeven() -
逃逸閉包,當(dāng)一個閉包作為參數(shù)傳到一個函數(shù)中,但是這個閉包在函數(shù)返回之后才被執(zhí)行,我們稱該閉包從函數(shù)中逃逸。當(dāng)你定義接受閉包作為參數(shù)的函數(shù)時,你可以在參數(shù)名之前標(biāo)注 @escaping,用來指明這個閉包是允許“逃逸”出這個函數(shù)的。 例如網(wǎng)絡(luò)請求??func request(result:@escaping((String)->())){ DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 10) { result("數(shù)據(jù)結(jié)果") } } -
非逃逸閉包, 永遠(yuǎn)不會離開一個函數(shù)的局部作用域的閉包就是非逃逸閉包。func player(complete:(Bool)->()){ complete(true) } -
自動閉包,自動閉包是一種自動創(chuàng)建的閉包,用于包裝傳遞給函數(shù)作為參數(shù)的表達(dá)式。這種閉包不接受任何參數(shù),當(dāng)它被調(diào)用的時候,會返回被包裝在其中的表達(dá)式的值。當(dāng)閉包作為參數(shù)傳入 可用@autoclosure標(biāo)記閉包參數(shù) ,可將參數(shù)當(dāng)函數(shù)調(diào)用而并非以閉包的形式。這種便利語法讓你能夠省略閉包的花括號,用一個普通的表達(dá)式來代替顯式的閉包var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] print(customersInLine.count) // 打印出“5” let customerProvider = { customersInLine.remove(at: 0) } print(customersInLine.count) // 打印出“5” print("Now serving \(customerProvider())!") // 打印出“Now serving Chris!” print(customersInLine.count) // 打印出“4” // customersInLine is ["Ewa", "Barry", "Daniella"] func serve(customer customerProvider: @autoclosure () -> String) { print("Now serving \(customerProvider())!") } serve(customer: customersInLine.remove(at: 0)) // 打印“Now serving Ewa!” // 不用 @autoclosure 修飾 serve(customer: { customersInLine.remove(at: 0) } ) // 打印“Now serving Ewa!”