Swift面試題總結(一)

u010186280發表於2020-11-23

1、Swift比Objective-C有什麼優勢?

Swift速度更快,運算效能更高。
Swift語法簡單易讀,程式碼更少,更加清晰,易於維護
Swift更加安全,它是型別安全的語言
Swift泛型,結構體,列舉都很強大
Swift便捷的函數語言程式設計
Swift型別推斷

2、Swift 是物件導向還是函數語言程式設計語言?

Swift既是物件導向的,又是函式式的程式語言
說Swift是物件導向的語言,是因為Swift支援類的封裝,繼承和多型
說Swift是函數語言程式設計語言,是因為Swift支援map、reduce、filter、flatmap這類去除中間狀態,數學函式式的方法,更加強調運算結果而不是中間過程

3、 dynamic framework 和 static framework 的區別是什麼?

靜態庫和動態庫,靜態庫是每一個程式單獨打包一份,而動態庫則是多個程式之間共享
靜態庫和動態庫是相對編譯器和執行期的:靜態庫在程式編譯時會被連結到目的碼中,程式執行時將不再更改靜態庫;而動態庫在程式編譯時並不會被連結到目的碼中,只是在程式執行時才被載入。

不同點:
靜態庫在連結時,會被完整的複製到可執行檔案中,如果多個APP都使用了同一個靜態庫,那麼每個APP都會拷貝一份,缺點是浪費記憶體。
動態庫不會複製,只有一份,程式執行時動態載入到記憶體中,系統只會載入一次,多個程式公用一份,節約了記憶體。

共同點:靜態庫和動態庫都是閉源庫,只能拿來滿足某個功能的使用,不會暴露內部具體的程式碼資訊。

4、Swift的靜態派發

OC中的方法都是動態派發(方法呼叫),Swift中的方法分為靜態派發和動態派發。
動態派發:指的是方法在執行時才找到具體實現。Swift中的動態派發和OC中的動態派發是一樣的。

靜態派發:靜態派發是指在執行時呼叫方法不需要查表,直接跳轉到方法的程式碼中執行。
靜態派發的特點:
靜態派發更高效,因為靜態派發免去了查表操作。
靜態派發的條件是方法內部的程式碼必須對編譯器透明,且在執行時不能被更改,這樣編譯器才能幫助我們。
Swift中的值型別不能被繼承,也就是說值型別的方法實現不能被修改或者被複寫,因此值型別的方法滿足靜態派發。

5、說說Swift為什麼將String,Array,Dictionary設計成值型別?

值型別相比引用型別,最大的優勢在於記憶體使用的高效。值型別在棧上操作,引用型別在堆上操作。棧上的操作僅僅是單個指標的上下移動,而堆上的操作則牽涉到合併、移位、重新連結等。也就是說Swift這樣設計,大幅減少了堆上的記憶體分配和回收次數。同時寫時複製又將值傳遞和複製的開銷降到了最低。
String,Array,Dictionary設計成值型別,也是為了執行緒安全考慮。通過Swift的let設定,使得這些資料達到了真正意義上的“不變”,它從根本上解決了多執行緒中記憶體訪問和操作順序的問題。
設計成值型別還可以提升API的靈活度。

6、什麼是函數語言程式設計?

物件導向的程式設計思想,我們將要解決的一個個問題,抽象成一個個類,通過給類定義屬性和方法,讓類幫助我們解決需要處理的問題(即指令式程式設計,給物件下一個個命令)。函數語言程式設計指的是數學意義上的函式,即對映關係(如:y = f(x),就是 y 和 x 的對應關係,可以理解為"像函式一樣的程式設計").它的主要思想是把運算過程儘量寫成一系列巢狀的函式呼叫。
例:
數學表示式
(1 + 2) * 3 - 4
傳統程式設計
var a = 1 + 2
var b = a * 3
var c = b - 4
函數語言程式設計
var result = subtract(multiply(add(1,2), 3), 4)

  • 函數語言程式設計的特點

1、 函式是"第一等公民"

函式和其他資料型別一樣,可以作為引數,可以賦值給其他變數,可以作為返回值。
例:

var print = function(i){ 
  console.log(i)
}
[1,2,3].forEach(print)

2、高階函式
高階函式:接受至少一個函式作為引數,返回的結果是一個函式。

3、柯里化
所謂“柯里化”,就是把一個多引數的函式,轉換為單引數函式,並且這個函式的返回值也是一個函式。

例:

// 柯里化之前
function add(x, y) {
  return x + y;
}
add(1, 2) // 3

// 柯里化之後
function addX(y) {
  return function (x) {
    return x + y;
  };
}
addX(2)(1) // 3

4、沒有“副作用”
所謂“副作用”,指的是函式內部與外部互動,產生運算以外的其他結果。

5、純函式
純函式程式設計和函式程式設計的區別在於:是否允許在函式內部執行一些非函式式的操作,同時這些操作是否會暴露給系統中的其他地方?也就是是否存在副作用,如果不存在副作用或者說可以不用在意這些副作用,那麼就將其稱為純粹的函數語言程式設計。

6、引用透明性
函式無論在何處何時用,如果使用相同的輸入總能持續地得到相同的結果,就具備了函式式的特徵。這種不依賴外部變數或“狀態”,只依賴輸入的引數的特性就被稱為引用透明性。

  • 函數語言程式設計的好處
    程式碼簡潔,開發迅速
    接近自然語言,易於理解
    更方便的程式碼管理
    易於“併發程式設計”
    程式碼的熱升級

7、Swift mutating關鍵字的使用?

預設情況下,不能在例項方法中修改值型別的屬性,若在例項方法中使用mutating關鍵字,不僅可以在例項方法中修改值型別的屬性,而且會在方法實現結束時將其寫回到原始結構。

8、autoclosure的作用

自動閉包,將引數自動封裝為閉包引數

9、Swift中,如何阻止方法、屬性、下標被子類改寫?

在類的定義中使用final關鍵字宣告類、屬性、方法和下標,final宣告的類不能被繼承,final宣告的屬性、方法和下標不能被重寫。
如果只是限制一個方法或者屬性被重寫,只需要在該方法或者屬性前加一個final。
如果需要限制整個類無法被繼承,那麼可以在類名之前加一個final。

10、Optional(可選型)是什麼?Optional(可選型)解決方式?

Optional是一個泛型列舉

enum Optional<Wrapped> {
   case none
   case some(Wrapped)
}

Optional型別表示:有值/沒有值
在OC中並沒有Optional型別,只有nil,並且nil只能用於表示物件型別無值,並不能用於基礎型別(int,float),列舉和結構體。
基礎型別需要返回類似NSNotFound的特殊值來表示無值,所以在swift中定義了Optional型別來表示各種型別的無值狀態,並規定了nil不能用於非可選的常量和變數,只能用於Optional型別。
解決方式:
強行開啟-不安全

let a: String = x!

隱式解包變數宣告-在許多情況下安全

var a = x!

可選繫結 - 安全

if let a = x {
   print("\(a)")
}

可選連結 - 安全

let a = x?.count

無合併操作 - 安全

let a = x ?? ""

警衛宣告 - 安全

guard let a = x else {
  return 
}

可選模式 - 安全

if case let a? = x {
  print(a)
}

11、inout的作用

inout輸入輸出引數,讓輸入引數可變,型別__block的作用。
1、 函式引數預設為常量。試圖從函式主體內部更改函式引數的值會導致編譯時錯誤。這意味著您不能錯誤地更改引數的值。如果您希望函式修改引數的值,並且希望這些更改在函式呼叫結束後仍然存在,請將該引數定義為輸入輸出引數。

2、您可以通過將inout關鍵字放在引數型別的前面來編寫輸入/輸出引數。一個在出引數具有傳遞的值中,由函式修改的功能,並將該部分送回出的功能來代替原來的值。

3、您只能將變數作為輸入輸出引數的引數傳遞。您不能將常量或文字值作為引數傳遞,因為無法修改常量和文字。當您將一個與號(&)作為變數傳入in-out引數時,將它放在變數名的前面,以表明該變數可以被函式修改。

4、注意:輸入輸出引數不能具有預設值,並且可變引數不能標記為inout。

12、下面的功能特性都包含在Swift中嗎?

1、泛型類
2、泛型結構體
3、泛型協議
答案: Swift 包含1和2特性。泛型可以在類、結構體、列舉、全域性函式或者方法中使用。
3是通過 typealias 部分實現的。typealias 不是一個泛型型別,它只是一個佔位符的名字。它通常是作為關聯型別被引用,只有協議被一個型別引用的時候它才被定義。

13、Error 如果要相容 NSError 需要做什麼操作

Error是一個協議, swift中的Error 都是enum, 可以轉 NSError.如果需要Error有NSError的功能,實現 LocalizedError CustomNSError 協議.

14、許可權修飾符

open :修飾的屬性或者方法在其他作用域既可以被訪問也可以被繼承或過載 override。
public :修飾的屬性或者方法可以在其他作用域被訪問,但不能在過載 override 中被訪問,也不能在繼承方法中的 Extension 中被訪問。
internal:被修飾的屬性和方法只能在模組內部可以訪問,超出模組內部就不可被訪問了。(預設)
fileprivate :其修飾的屬性或者方法只能在當前的 Swift 原始檔裡可以訪問。
private :只允許在當前類中呼叫,不包括 Extension ,用 private 修飾的方法不可以被程式碼域之外的地方訪問。

從高到低排序如下:
open > public > interal > fileprivate > private

15、struct 與 class 的區別

1、 struct是值型別,class是引用型別:
值型別的變數直接包含它們的資料,對於值型別都有它們自己的資料副本,因此對一個變數操作不可能影響另一個變數.值型別包括結構體 (陣列和字典),列舉,基本資料型別 (boolean, integer, float等).
引用型別的變數儲存對他們的資料引用,對一個變數操作可能影響另一個變數.
二者的本質區別:struct是深拷貝;class是淺拷貝。

2、 property的初始化不同:
class 在初始化時不能直接把 property 放在預設的 constructor 的引數裡,而是需要自己建立一個帶引數的 constructor;而struct可以,把屬性放在預設的 constructor 的引數裡。

3、 變數賦值方式不同:
struct是值拷貝;class是引用拷貝。

4、 immutable變數:
swift的可變內容和不可變內容用var和let來甄別,如果初始為let的變數再去修改會發生編譯錯誤。struct遵循這一特性;class不存在這樣的問題。

5、 mutating function:
struct 和 class 的差別是 struct 的 function 要去改變 property 的值的時候要加上 mutating,而 class 不用。

6、 繼承:
struct不可以繼承,class可以繼承。

7、 struct比class更輕量:
struct分配在棧中,class分配在堆中。

16、舉例swift中模式匹配的作用?

模式匹配: 在switch中體現最明顯
萬用字元模式: _
識別符號模式:let i = 1
值繫結模式:case .Student(let name) 或者 case let .Student(name)
元祖模式:case (let code, _)
可選模式:if case let x? = someOptional { }
型別轉換模式:case is Int: 或者 case let n as String:
表示式模式:範圍匹配 case (0…<2) case(0…2, 2…4)
條件句中使用where: case (let age) where age > 30
if case let:if case let .Student(name) = xiaoming { }
for case let: for case let x in array where x > 10 {} 或者 for x in array where x > 10

17、swift中 closure 與OC中block的區別?

1、closure是匿名函式、block是一個結構體物件
2、closure通過逃逸閉包來在內部修改變數,block 通過 __block 修飾符

//非逃逸閉包,函式執行完之前,閉包就執行完了,閉包沒有逃出函式的作用域
    func handleData(closure:(Any) -> Void) {
        print("函式開始執行---\(Thread.current)")
        print("執行了閉包----\(Thread.current)")
        closure("4456")
        print("函式執行結束----\(Thread.current)")
    }
    
    //逃逸閉包--函式執行完後,閉包裡面的內容還可以執行,閉包的作用域逃出了函式
    func getData(closure: @escaping (Any) -> Void) {
        print("函式開始執行--\(Thread.current)")
        DispatchQueue.global().async {
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2, execute: {
                print("執行了閉包---\(Thread.current)")
                closure("345")
            })
        }
        print("函式執行結束---\(Thread.current)")
    }

18、Swift 中的 KVC和KVO

KVC
要繼承NSObject

class KVCClass: NSObject {
   @objc var someValue: String = "123"
    
    func show() {
        print("someValue的值:\(someValue)")
    }
}

let kvc = KVCClass()
 kvc.show()
 kvc.setValue("456", forKey: "someValue")
 kvc.show()

KVO
由於 Swift 為了效率, 預設禁用了動態派發, 因此 Swift 要實現 KVO, 除了要繼承自 NSObject 外還要將觀測的物件標記為 dynamic(讓 swift 程式碼也能有 Objective-C 中的動態機制).

class KVOClass: NSObject {
    @objc dynamic var someValue = "abc"
    func updateValue() {
        someValue = "bbc"
    }
}

class MyObserver: NSObject {
    @objc var kvoClass: KVOClass
    var observation: NSKeyValueObservation?
    
    init(object: KVOClass) {
        kvoClass = object
        super.init()
        observation = observe(\.kvoClass.someValue, options: [.old, .new], changeHandler: { (object, change) in
            print("someValue changed from: \(change.oldValue!), updated to: \(change.newValue!)")
        })
    }
}

 let kvoclass = KVOClass()//被觀察者
 let observer = MyObserver(object: kvoclass)//觀察者
 kvoclass.updateValue()

19、associatedtype 的作用

關聯型別:為協議中的某個型別提供了一個別名,其代表的真實型別在實現者中定義.

struct Model {
    var a: String
    var b: String
}

protocol TableViewCell {
    associatedtype T
    func updateCell(_ data: T)
}

class MyTableViewCell: UITableViewCell, TableViewCell {
    typealias T = Model
    func updateCell(_ data: Model) {
        //do something...
    }
}

20、 什麼是泛型,swift在哪些地方使用了泛型?

泛型(generic)可以使我們在程式程式碼中定義一些可變的部分,在執行的時候指定。使用泛型可以最大限度地重用程式碼、保護型別的安全以及提高效能。

 // 定義一個交換兩個變數的函式
    func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
        let temp = a
        a = b
        b = temp
    }
        var num1 = 100
        var num2 = 200
        print("交換前資料:  \(num1) 和 \(num2)")
        swapTwoValues(&num1, &num2)
        print("交換後資料: \(num1) 和 \(num2)")
        
        var str1 = "A"
        var str2 = "B"
        print("交換前資料:  \(str1) 和 \(str2)")
        swapTwoValues(&str1, &str2)
        print("交換後資料: \(str1) 和 \(str2)")

21、map、filter、reduce 的作用

map 用於對映, 可以將一個列表轉換為另一個列表

let arr =  [1,2,3].map{"\($0)"}
 print(arr)
 ["1", "2", "3"]

filter 用於過濾, 可以篩選出想要的元素

// 篩選偶數
let arr = [1,2,3,4,5,6,7,8].filter{$0 % 2 == 0}
print(arr)
 [2, 4, 6, 8]

reduce 合併

[1,2,3].reduce(""){$0 + "\($1)"}
// "123"

22、map 與 flatmap 的區別

  • 在新版swift中,flatmap被compactMap替代

1、 map 可以對一個集合型別的所有元素做一個對映操作.
2、 flatMap
第一個作用和map一樣,對一個集合型別的所有元素做一個對映操作,且可以過濾為nil的情況.

  let array = [1,2,5,6,7,nil]
 let array_map = array.map{ $0 }
 print(array_map)
 let array_flatmap = array_map.compactMap{$0}
print(array_flatmap)

[Optional(1), Optional(2), Optional(5), Optional(6), Optional(7), nil]
[1, 2, 5, 6, 7]

第二種情況可以進行“降維”操作

 let array = [["1", "2"], ["3", "4"]]
 let array_map = array.map{$0}
 print(array_map)
 let array_flatmap = array_map.flatMap{$0}
 print(array_flatmap)

[["1", "2"], ["3", "4"]]
["1", "2", "3", "4"]

23、defer、guard的作用?

defer 語句塊中的程式碼, 會在當前作用域結束前呼叫,無論函式是否會丟擲錯誤。每當一個作用域結束就進行該作用域defer執行。 如果有多個 defer, 那麼後加入的先執行.
guard :過濾器,攔截器
guard 和 if 類似, 不同的是, guard 總是有一個 else 語句, 如果表示式是假或者值繫結失敗的時候, 會執行 else 語句, 且在 else 語句中一定要停止函式呼叫.

24、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)
}

25、如何自定義下標獲取

實現 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]
    }
}

索引除了數字之外, 其他型別也是可以的

26、為什麼陣列索引越界會崩潰,而字典用下標取值時 key 沒有對應值的話返回的是 nil 不會崩潰。

struct Array<Element> {
    subscript(index: Int) -> Element
}

struct Dictionary<Key: Hashable, Value> {
    subscript(key: Key) -> Value?
}

1、 陣列索引訪問的是一段連續地址,越界訪問也能訪問到記憶體,但這段記憶體不一定可用,所以會引起Crash.
2、 字典的key並沒有對應確定的記憶體地址,所以是安全的.

27、給集合中元素是字串的型別增加一個擴充套件方法,應該怎麼宣告?

使用 where 子句, 限制 Element 為 String
extension Array where Element == String {
    var isStringElement:Bool {
        return true
    }
}
["1", "2"].isStringElement
//[1, 2].isStringElement// error

28、一個函式的引數型別只要是數字(Int、Float)都可以,要怎麼表示。

Int、Float 都有一個協議
func myMethod<T>(_ value: T) where T: Numeric {
    print(value + 1)
} 
或者 ExpressibleByIntegerLiteral 協議也行

29、一個型別表示選項,可以同時表示有幾個選項選中(類似 UIViewAnimationOptions ),用什麼型別表示?

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]

相關文章