Swift重點知識點總結(jié)

Swift 優(yōu)點 (相對 OC)

  • Swift 更加安全,是類型安全的語言
  • 代碼少,語法簡潔,可以省去大量冗余代碼
  • Swift 速度更快,運算性能更高,(Apple 專門對編譯器進行了優(yōu)化)

Swift 中 類(class) 和 結(jié)構(gòu)體(struct) 的區(qū)別,以及各自優(yōu)缺點?

  • 類:
    • 引用類型
      • 在進行變量賦值時,是通過指針copy,屬于淺拷貝(shallow copy)
      • 數(shù)據(jù)的存儲是在堆空間
    • 可以被繼承(前提是類沒有被 final 關(guān)鍵字修飾),子類可以使用父類的屬性和方法
    • (當(dāng)class繼承自 Object,擁有runtime機制)類型轉(zhuǎn)換可以在運行時檢查和解釋一個實例對象
    • 用 deinit(析構(gòu)函數(shù))來釋放資源 類似OC(dealloc)
    • 類的方法地址 是不確定的,只有在具體運行時,才能確定調(diào)用的具體值
  • 結(jié)構(gòu)體
    • 值類型
      • 在進行變量賦值是,是深拷貝(deep copy),產(chǎn)生了新的副本
      • 數(shù)據(jù)的存儲時在??臻g(大部分情況下,不需要考慮內(nèi)存泄露問題,??臻g的特點是用完即釋放)
    • 結(jié)構(gòu)體調(diào)用方法,在編譯完成就可以確定方法具體的地址值,以便直接調(diào)用

綜上,在滿足程序要求的情況下 優(yōu)先使用 結(jié)構(gòu)體


Swift中strong 、weak和unowned是什么意思?二者有什么不同?何時使用unowned?

Swift 的內(nèi)存管理機制與 Objective-C一樣為 ARC(Automatic Reference Counting)。它的基本原理是,一個對象在沒有任何強引用指向它時,其占用的內(nèi)存會被回收。反之,只要有任何一個強引用指向該對象,它就會一直存在于內(nèi)存中。

  • strong 代表著強引用,是默認屬性。當(dāng)一個對象被聲明為 strong 時,就表示父層級對該對象有一個強引用的指向。此時該對象的引用計數(shù)會增加1。
  • weak 代表著弱引用。當(dāng)對象被聲明為 weak 時,父層級對此對象沒有指向,該對象的引用計數(shù)不會增加1。它在對象釋放后弱引用也隨即消失。繼續(xù)訪問該對象,程序會得到 nil,不虧崩潰
  • unowned 與弱引用本質(zhì)上一樣。不同的是,unowned 無主引用 實例銷毀后仍然存儲著實例的內(nèi)存地址(類似于OC中的unsafe_unretained), 試圖在實例銷毀后訪問無主引用,會產(chǎn)生運行時錯誤(野指針)
  • weak unowned 只能用在 類實例上面
  • weak、unowned 都能解決 循環(huán)引用,unowned 要比 weak 性能 稍高
    • 在生命周期中可能會 變成 nil 的使用 weak
    • 初始化賦值以后再也不會變成 nil 使用 unowned

Swift 中什么是可選類型?

  • Swift中可選類型為了表示 一個變量 允許為 空(nil)的情況
  • 類型名稱后 加 ? 定義 可選項
  • 選項的本質(zhì)是 枚舉類型

Swift 中什么 是 泛型?

  • 跟JS和Dart 類似,泛型 可以將類型 參數(shù)化,提高代碼復(fù)用率,減少代碼量
  • Swift泛型函數(shù) 并不會 在底層 生成 若干個 (匹配類型)函數(shù) ,產(chǎn)生函數(shù)重載,而是: 在函數(shù)調(diào)用時,會將 參數(shù)的類型 傳遞給 目標函數(shù)
  • Swift泛型應(yīng)用在協(xié)議上時,需要使用關(guān)聯(lián)類型(associatedtype)

怎么理解 Swift中的泛型約束

泛型約束 可以 更精確的知道 參數(shù) 需要 遵循什么標準

//someT遵循的是某個class,someU遵循的是某個協(xié)議,這樣在傳參的時候明確參數(shù)類型
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 這里是泛型函數(shù)的函數(shù)體部分
}

Swift 中 static 和 class 關(guān)鍵字的區(qū)別

在 Swift 中 staticclass 都是表示「類型范圍作用域」的關(guān)鍵字。

在所有類型(class[類]、struct、enum )中使用

  • static 修飾都可以表示類方法類與屬性(包括存儲屬性和計算屬性)。
  • class 是專門用在 calss 類型中修飾類方法和類的計算屬性(注意:無法使用 class 修飾存儲屬性)。

結(jié)構(gòu)體只能用 static 修飾 類方法或?qū)傩?/p>

class類型 static class 的區(qū)別

class 類型staticclass 都可以表示類型范圍作用域,那區(qū)別是

  1. class 無法修飾存儲屬性,而 static 可以。
  2. 使用 static 修飾的類方法和類屬性無法在子類中重載。也就是說 static 修飾的類方法和類屬性包含了 final 關(guān)鍵字的特性。相當(dāng)于 final class 。

一般在 protocol定義一個類方法或者類計算屬性推薦使用 static 關(guān)鍵字來修飾。使用 protocol 時,在 struct 和 enum 中仍然使用 static,在 class 類型中 class 和 static 關(guān)鍵字都可以使用。


Swift 中的模式匹配?

模式 是用于 匹配的規(guī)則,比如 switch 的 case 、捕捉錯誤的 catch 、 if guard while for 語句條件等


Swift 中的訪問控制?

Swift 提供了 5個 不同的訪問級別,從高到低排列如下:

  • open
    • 允許在任意模塊中訪問、繼承、重寫
    • open 只能用在 類 、 類成員上
  • public
    • 允許在任意模塊中訪問
    • 修飾類時不允許其他模塊進行繼承、重寫
  • internal - 默認
    • 只允許在定義實體的模塊中訪問,不允許在其他模塊中訪問
  • fileprivate
    • 只允許在定義實體的源文件中訪問
  • private:
    • 只允許在定義實體的封閉聲明中(作用域)訪問

怎么理解 copy - on - write? 或者 理解Swift中的寫時復(fù)制

值類型在復(fù)制時,復(fù)制對象 與 原對象 實際上在內(nèi)存中指向同一個對象,**當(dāng)且僅當(dāng) ** 修改復(fù)制的對象時,才會在內(nèi)存中創(chuàng)建一個新的對象,

  • 為了提升性能,Struct, String、Array、Dictionary、Set采取了Copy On Write的技術(shù)
  • 比如僅當(dāng)有“寫”操作時,才會真正執(zhí)行拷貝操作
  • 對于標準庫值類型的賦值操作,Swift 能確保最佳性能,所有沒必要為了保證最佳性能來避免賦值

原理

在結(jié)構(gòu)體內(nèi)部用一個引用類型來存儲實際的數(shù)據(jù),

  • 在不進行寫入操作的普通傳遞過程中,都是將內(nèi)部的reference的應(yīng)用計數(shù)+1,
  • 當(dāng)進行寫入操作時,對內(nèi)部的 reference 做一次 copy 操作用來存儲新的數(shù)據(jù);防止和之前的reference產(chǎn)生意外的數(shù)據(jù)共享。

swift中提供[isKnownUniquelyReferenced]函數(shù),他能檢查一個類的實例是不是唯一的引用,如果是,我們就不需要對結(jié)構(gòu)體實例進行復(fù)制,如果不是,說明對象被不同的結(jié)構(gòu)體共享,這時對它進行更改就需要進行復(fù)制。

Swift 為什么將 Array,String,Dictionary,Set,設(shè)計為值類型?

值類型 相比 引用類型的優(yōu)點

  • 值類型和引用類型相比,最大優(yōu)勢可以高效的使用內(nèi)存;
  • 值類型在棧上操作,引用類型在堆上操作;
  • 棧上操作僅僅是單個指針的移動,
  • 堆上操作牽涉到合并,位移,重鏈接

Swift 這樣設(shè)計減少了堆上內(nèi)存分配和回收次數(shù),使用 copy-on-write將值傳遞與復(fù)制開銷降到最低

String,Array,Dictionary設(shè)計成值類型,也是為了線程安全考慮。通過Swift的let設(shè)置,使得這些數(shù)據(jù)達到了真正意義上的“不變”,它也從根本上解決了多線程中內(nèi)存訪問和操作順序的問題


什么是屬性觀察器?

屬性觀察是指在當(dāng)前類型內(nèi)對特性屬性進行監(jiān)測,并作出響應(yīng),屬性觀察是 swift 中的特性,具有2種, willsetdidset

  • willSet 傳遞新值 newValue

  • didSet 傳遞舊值 oldValue

  • 在初始化器中對屬性初始化時,不會觸發(fā)觀察器

  • 屬性觀察器 只能用在 存儲屬性 ,不可用在 計算屬性

  • 可以 為 lazy (即延遲存儲屬性)的 var 存儲屬性 設(shè)置 屬性觀察器

  • willSet 會傳遞新值,默認叫 newValue
  • didSet 會傳遞舊值,默認叫 oldValue

注意:

  • 在初始化器中設(shè)置屬性值不會觸發(fā) 屬性觀察器
    • 屬性定義時設(shè)置初始值也不會出發(fā) 屬性觀察,原因是
      • 屬性定義時設(shè)置初始值,本質(zhì)跟在 初始化器中設(shè)置值是一樣的
  • 屬性觀察器 只能用在 存儲屬性 ,不可用在 計算屬性
struct Cicle {
    /// 存儲屬性
    var radius :Double {
        willSet {
            print("willSet -- ",newValue,"radius == ",radius)
        }
        didSet {
            print("didSet ++ ",oldValue,"radius == ",radius)
        }
    }
    /*
     上述代碼 跟下面等價,不推薦
     var radius :Double {
         willSet(jk_newValue) {
             print("willSet -- ",jk_newValue,"radius == ",radius)
         }
         didSet(jk_oldValue) {
             print("didSet ++ ",jk_oldValue,"radius == ",radius)
         }
     }
     */
}

var circle = Cicle.init(radius: 10.0)

circle.radius = 20.0
/*
 willSet --  20.0 radius ==  10.0
 didSet ++  10.0 radius ==  20.0
 */

print("result == ",circle.radius)
//result ==  20.0

拓展:
●屬性觀察器,計算屬性這兩個功能,同樣可以應(yīng)用在全局變量/局部變量
●屬性觀察器,計算屬性 不可以同時 應(yīng)用在同一個類(不包括繼承)的屬性中

Swift 異常捕獲

do - try - catch 機制

  • Swift中可以通過 Error 協(xié)議自定義運行時的錯誤信息
  • 函數(shù)內(nèi)部通過throw 拋出 自定義 Error , 拋出 Error 的函數(shù)必須加上 throws 聲明(邏輯通過不會拋出,反之可能拋出)
  • 需要使用 try 調(diào)用 可能會 拋出 的Error 函數(shù)
  • 通過 try 嘗試調(diào)用 函數(shù) 拋出的異常 必須要 處理異常;否在會編譯報錯; 反之 運行時 在 top level (main) 報錯,閃退

defer 的用法

  • 使用defer代碼塊來表示在函數(shù)返回前,函數(shù)中最后執(zhí)行的代碼。無論函數(shù)是否會拋出錯誤,這段代碼都將執(zhí)行。

如何將Swift中協(xié)議 部分方法 設(shè)計成可選?

  • 方案一(不推薦,除非需要暴露給OC用)
    • 在協(xié)議和方法前面添加 @objc,然后在方法前面添加 optional關(guān)鍵字,改方式實際上是將協(xié)議轉(zhuǎn)為了OC的方式
@objc protocol someProtocol {
  @objc  optional func test()
}

協(xié)議 可以 用來定義 屬性 方法 下標聲明,協(xié)議 可以被 類 結(jié)構(gòu)體 枚舉 遵守(多個協(xié)議之間用, 隔開)

protocol Drawable {
    func draw()
    var x:Int { get set }
    var y:Int { get }
    subscript(index:Int) -> Int {get set}
}

protocol Test1 {}
protocol Test2 {}
protocol Test3 {}

class TestClass: Test1,Test2,Test3 {
    
}

注意:

  • 協(xié)議中定義的方法,不能有默認參數(shù)
  • 默認情況下,協(xié)議中定義的內(nèi)容需要全部實現(xiàn)

Swift和OC中的 protocol 有什么不同?

  • 相同點,兩者都是用作代理
  • 不同點
    • Swift
      • Swift中的 protocol 還可以對接口進行抽象,可以實現(xiàn)面向協(xié)議,從而大大提高編程效率
      • Swift中的 protocol 可以用于值類型,結(jié)構(gòu)體,枚舉;

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

swift 的初始化方法,因為引入了兩段式初始化和安全檢查因此更加嚴格和準確,

swift初始化方法需要保證所有的非optional的成員變量都完成初始化,

同時 swfit 新增了convenience和 required兩個修飾初始化器的關(guān)鍵字

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

Swift 和OC 中的自省 有什么區(qū)別?

  • OC
    • 自省在OC中就是判斷某一對象是否屬于某一個類的操作,有以下2中方式
      • [obj iskinOfClass:[SomeClass class]] : obj 必須是 SomeClass 的 對象或 其子類對象;return YES;
      • [obj isMemberOfClass:[SomeClass class]] : obj 必須是 SomeClass 的 對象;return YES;
  • Swift
    • Swift 中由于很多 class 并非繼承自 NSObject, 故而 Swift 使用 is 來判斷是否屬于某一類型, is 不僅可以作用于class, 還可以作用于enum和struct

Swift 與 OC 如何相互調(diào)用

  • Swift -> OC
    • 需要創(chuàng)建一個 Target-BriBridging-Header.h (默認在OC項目中,會提示自動創(chuàng)建)的橋文件,在該文件中,導(dǎo)入需要調(diào)用的OC代碼的頭文件即可
  • OC -> Swift
    • 直接導(dǎo)入Target-Swift.h(該文件是Xcode自動創(chuàng)建) Swift如何需要被OC調(diào)用,需要使用 @objc 對方法或?qū)傩赃M行修飾

Swift 中特殊的標記

swift

Swift調(diào)用OC
●新建一個橋接文件,文件格式默認為:{targetName}-Bridging-Header.h;(一般在OC項目中,創(chuàng)建Swift文件,Xcode會自動提示生成該文件,僅需點擊確認即可)

swift

●在{targetName}-Bridging-Header.h 文件中 #import OC 需要 暴露 給 Swift的內(nèi)容

OC 調(diào)用 Swift
●Xcode 已經(jīng)默認 生成 一個 用于 OC 調(diào)用 Swift的頭文件,文件名格式是: {targetName}-Swift.h

swift

●Swift 暴露給 OC的 類 一定要繼承 NSObject

●使用 @objc 修飾 需要暴露 給 OC的成員

●使用@objcMembers 修飾類
○代表 默認所有的 成員 都會 暴露給 OC(包括擴展中定義的成員)
○最終 是否成功 暴露,還需要考慮 成員自身的 訪問級別

拓展
●為什么Swift 暴露給 OC 的類 要最終 繼承 NSObject?
因為 OC 中的方法調(diào)用 是通過 Runtime 機制,需要通過 isa 指針 去完成 一些列消息的發(fā)送等, 而 只有繼承自 NSObject 的類 才具有 isa 指針,才具備 Runtime 消息 發(fā)送的能力

●p.run() 底層是如何調(diào)用的? 反過來,OC調(diào)用Swift 又是如何調(diào)用?
○JKPerson 是 OC 的類,以及OC 中定義的初始化 和 run 方法
○在Swift中 調(diào)用 JKPerson 對象的 run 方法 ,底層是如何調(diào)用的?

var p = JKPerson(age: 10,name:"Jack")
p.run()

答:走 Runtime 運行時機制, 反過來 OC 調(diào)用 Swift中的類 跟 問題一 一樣,也是通過 Runtime 機制

●car.run() 底層是如何調(diào)用的?

swift

答 : (雖然 Car 類 被暴露給 OC使用)在Swift中 car.run(),底層依然是 通過 類似 C++ 的虛表 機制 來調(diào)用的;

拓展:
如果想要 Swift中的方法 調(diào)用 也使用 Runtime 機制,需要在方法名稱前面 加上 dynamic關(guān)鍵字


Swift定義常量 和 OC定義常量的區(qū)別?

//OC:
const int price = 0;
//Swift:
let price = 0
  • OC中 const 常量類型和數(shù)值是在編譯時確定的
  • Swift 中 let 常量(只能賦值一次),其類型和值既可以是靜態(tài)的,也可以是一個動態(tài)的計算方法,它們在運行時確定的。

Swift 中的函數(shù)重載

構(gòu)成函數(shù)重載的規(guī)則

  • 函數(shù)名相同
  • 參數(shù)個數(shù)不同 || 參數(shù)類型不同 || 參數(shù)標簽不同

注意: 返回值類型 與函數(shù)重載無關(guān);返回值類型不同時,函數(shù)重載會報錯:

func overloadsum(v1 : Int,v2:Int) -> Int {
    v2 + v1
}

// 參數(shù)個數(shù)不同
func overloadsum(v1 : Int,v2:Int,v3:Int) -> Int {
    v2 + v1 + v3
}

// 參數(shù)類型不同
func overloadsum(v1 : Int,v2:Double) -> Double {
    v2 + Double(v1)
}

// 參數(shù)標簽不同
func overloadsum(_ v1 : Int,_ v2:Int) -> Int {
    v2 + v1
}

func overloadsum(a : Int,_ b:Int) -> Int {
    a + b
}

/**
 返回值類型不同時,在函數(shù)重載時,會報錯:
 Ambiguous use of 'overloadsum(v1:v2:)'
 
 func overloadsum(v1 : Int,v2:Int) {
 }
 */

public func overloadtest() {
    let result1 = overloadsum(v1: 10, v2: 20)
    let result2 = overloadsum(v1: 10, v2: 20, v3: 30)
    let result3 = overloadsum(v1: 10, v2: 20)
    let result4 = overloadsum(10, 20)
    let resutt4_1 = overloadsum(a: 10, 20)
    
    print(result1,result2,result3,result4,resutt4_1)
    //30 60 30 30 30
}


Swift 中的枚舉,關(guān)聯(lián)值 和 原始值的區(qū)分?

    • 將 枚舉的成員值 跟 其他類型的值 關(guān)聯(lián) 存儲在一起
    • 存儲在枚舉變量中,占用枚舉變量內(nèi)存
enum Score {
    case points(Int)
    case grade(Character)
}
    • 枚舉成員可以使用相同類型的默認值預(yù)先關(guān)聯(lián),這個默認值叫做:原始值
    • 不會存儲在 枚舉變量中,不占用枚舉變量內(nèi)存
enum PokerSuit : Character {
    case spade = "?"
    case heart = "?"
    case diamond = "?"
    case club = "?"
}

閉包是引用類型嗎?

閉包和函數(shù)都是是引用類型。如果一個閉包被分配給一個變量,這個變量復(fù)制給另一個變量,那么他們引用的是同一個閉包,他們的捕捉列表也會被復(fù)制。

swift 中的閉包結(jié)構(gòu)是什么樣子的?

{
    (參數(shù)列表) -> 返回值類型 in 函數(shù)體代碼
}

什么是尾隨閉包

  • 尾隨閉包 是一個被 書寫在 函數(shù)調(diào)用 括號 后面的 閉包表達式

基本定義
●Swift中可通過 func 定義一個函數(shù),也可以通過 閉包表達式 定義一個函數(shù)

閉包表達式的格式:

{
    (參數(shù)列表) -> 返回值類型  in
    函數(shù)體代碼
}

閉包表達式與定義函數(shù)的語法相對比,有區(qū)別如下:
1沒有func
2沒有函數(shù)名
3返回值類型添加了關(guān)鍵字in

let fn1 = {
    (v1 : Int,v2 : Int) -> Int in
    return v1 + v2
}

let result1 = fn1(10,5)

let result2 = {
    (v1:Int,v2:Int) -> Int in
    return v2 + v1
}(10,6)

print(result1,result2) // 15 16

閉包表達式的簡寫

func exec(v1:Int,v2:Int,fn:(Int,Int)->Int) {
    print(fn(v1,v2))
}

閉包表達式的簡寫
private func test2() {
    // 1: 沒有簡寫
    exec(v1:10, v2:20, fn: {
        (v1:Int,v2:Int) -> Int in
        return v1 + v2
    })
    
    // 2: 簡寫1
    exec(v1: 2, v2: 3, fn: {
        v1,v2 in return v1 + v2
    })
    
    // 3:簡寫 2
    exec(v1: 3, v2: 4, fn: {
        v1,v2 in v1 + v2
    })
    
    // 4: 簡寫3
    exec(v1: 5, v2: 6, fn: {$0 + $1})
    
    // 5: 簡寫4
    exec(v1: 7, v2: 8, fn: +)
}

尾隨閉包

  • 將一個很長的閉包表達式作為函數(shù)的最后一個實參,使用尾隨閉包可以增強代碼的可讀性
    • 尾隨閉包 是一個被 書寫在 函數(shù)調(diào)用 括號 后面的 閉包表達式
func test3() {
    
    exec(v1: 8, v2: 7) { a, b in
        a + b
    }
    
    // or     { 書寫在 函數(shù)調(diào)用 括號 后面的 閉包表達式}
    exec(v1: 9, v2: 10) {
        $0 + $1
    }
}
  • 如果 閉包表達式 是函數(shù)的唯一 實參,且使用了尾隨閉包的 語法,則在函數(shù)名后面的 () 可省略
// fn:就是尾隨閉包
func exec1(fn:(Int,Int)->Int) {
    print(fn(1,2))
}

exec1(fn: {$0 + $1})
exec1() {$0 + $1}
exec1{$0 + $1}

什么是逃逸閉包

  • 閉包有可能在函數(shù)結(jié)束后調(diào)用,閉包調(diào)用 逃離了函數(shù)的作用域,需要通過@escaping 聲明

注意:逃逸閉包 不可以 捕獲 inout 參數(shù)

原因是: 逃逸閉包不確定 何時開始執(zhí)行,有可能 在執(zhí)行逃逸閉包時,可變參數(shù)已經(jīng)被程序回收,造成野指針訪問

什么是自動閉包

自動閉包是一種自動創(chuàng)建的用來把作為實際參數(shù)傳遞給函數(shù)的表達式打包的閉包。

它不接受任何實際參數(shù),并且當(dāng)它被調(diào)用時,它會返回內(nèi)部打包的表達式的值。

這個語法的好處在于通過寫普通表達式代替顯式閉包而使你省略包圍函數(shù)形式參數(shù)的括號。

func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? {
    return v1 > 0 ? v1 : v2()
}
getFirstPositive(10, 20)
  • 為了避免與期望沖突,使用了@autoclosure的地方最好明確注釋清楚:這個值會被推遲執(zhí)行
  • @autoclosure 會自動將 20 封裝成閉包 { 20 }
  • @autoclosure 只支持 () -> T 格式的參數(shù)
  • @autoclosure 并非只支持最后1個參數(shù)
  • 有@autoclosure、無@autoclosure,構(gòu)成了函數(shù)重載

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


Swift 中的存儲屬性與計算屬性

存儲屬性(Stored Property)

  • 類似于成員變量這個概念
  • 存儲在實例對象的內(nèi)存中
  • 結(jié)構(gòu)體、類可以定義存儲屬性
  • 枚舉不可以定義存儲屬性

關(guān)于 存儲屬性, Swift 中有個明確的規(guī)定

  • 在創(chuàng)建 類 或者 結(jié)構(gòu)體 的實例時,必須為所有的存儲屬性設(shè)置一個合適的初始值
    • 可以在初始化器里為存儲屬性設(shè)置一個初始值
    • 可以分配一個默認的屬性值作為屬性定義的一部分

計算屬性(Computed Property)

  • 本質(zhì)就是方法(函數(shù))
  • 不占用實例對象的內(nèi)存
  • 枚舉、結(jié)構(gòu)體、類都可以定義計算屬性

計算屬性(Computed Property)
○本質(zhì)就是方法(函數(shù))
○不占用實例的內(nèi)存
○枚舉、結(jié)構(gòu)體、類 都可以定義計算屬性

理解計算屬性與存儲屬性:

如果兩個屬性之間存在一定的邏輯關(guān)系,使用計算屬性,原因如下:
●如果都用存儲屬性的話,其邏輯對應(yīng)關(guān)系可能有誤
●而使用計算屬性,則可以準確的描述 這種邏輯關(guān)系
具體案例參考 下面的 Cicle 中的 radius(半徑) 和 diameter(直徑) 的邏輯關(guān)系

同時因為計算屬性不占用實例的內(nèi)存,可以有效的節(jié)省實例的內(nèi)存空間

●set 傳入的新值 默認叫做 newValue,也可以自定義

struct Cicle {
    /// 存儲屬性
    var radius :Double
    /// 計算屬性
    var diameter: Double {
        get {
            radius * 2
        }
        set (jkNewValue){
            radius = jkNewValue / 2.0
        }
    }
}
  • 只讀計算屬性:只有 get , 沒有 set
struct Cicle {
    /// 存儲屬性
    var radius :Double
    /// 計算屬性
    /*
     var diameter: Double {
         get {
             radius * 2
         }
     }
     */

    // 上述代碼與下面的代碼等價
    var diameter: Double {radius * 2}
}

var cicle = Cicle.init(radius: 12)

print(cicle.radius)//12.0
print(cicle.diameter)//24.0

// cicle.diameter = 10.0 //Cannot assign to property: 'diameter' is a get-only property
  • 定義計算屬性只能用 var,不可以是 let
    • let 表示常量
    • 計算屬性的值是可能會發(fā)生變化的(包括只讀計算屬性)

什么是[延遲存儲屬性](Lazy Stored Property)

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

注意點:

  • lazy 屬性 必須是 var
    • 因為,let 屬性 必須在實例的初始化方法 完成之前 就擁有值
  • 如果多條線程同時第一次訪問 lazy 屬性,無法保證 屬性 只被 初始化 一次 (即線程不是安全的)

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

注意點:

  • lazy 屬性 必須是 var
    • 因為Swift 規(guī)定:let 屬性 必須在實例的初始化方法 完成之前 就擁有值
  • 如果多條線程同時第一次訪問 lazy 屬性,無法保證 屬性 只被 初始化 一次 (即線程不是安全的)
class Car {
    init() {
        print("car has init")
    }

    func run() {
        print("car running")
    }
}

class Person {
    lazy var car = Car.init()

    init() {
        print("person has init")
    }

    func go_out() {
        car.run()
    }
}

let p = Person.init() //person has init
print("*******")
p.go_out()//  car has init ---->   car running
  • 當(dāng)結(jié)構(gòu)體 包含 一個延遲存儲屬性時,只有 var 才能訪問延遲 存儲屬性
    • 因為延遲屬性 初始化時 要改變 結(jié)構(gòu)體的內(nèi)存
struct Point {
    var x = 0
    var y = 0
    lazy var z = 0
}

let p = Point.init()
print(p.z)//Cannot use mutating getter on immutable value: 'p' is a 'let' constant

什么是 類型 屬性?

  • 類型屬性(Type Property) :通過類型去訪問
    • 存儲類型屬性(Stored Type Property):整個程序運行過程中,就只有1份內(nèi)存(類似于全局變量,底層采用了 gcd_once 操作)
    • 計算類型數(shù)據(jù)(Computed Type Property):

屬性可分為
●實例屬性(Instance Property):通過實例去訪問
○存儲實例屬性(Stored Instance Property):存儲在實例的內(nèi)存中,每個實例都有1分
○計算實例屬性(Computed Instance Property):不占用實例的內(nèi)存,本質(zhì)是方法

●類型屬性(Type Property) :通過類型去訪問
○存儲類型屬性(Stored Type Property):整個程序運行過程中,就只有1份內(nèi)存(類似于全局變量,底層采用了 gcd_once 操作,保證只初始化一次)
○計算類型數(shù)據(jù)(Computed Type Property):

注意:
存儲類型屬性不會 占用 實例對象 的內(nèi)存,整個程序運行過程中,就只有1份內(nèi)存

存儲類型屬性 本質(zhì)就是全局變量(該全局變量加了一些類型控制,只能通過類型去訪問),

可以通過 static 定義類型屬性
如果 是類, 也可以使用 關(guān)鍵字 class
結(jié)構(gòu)體 就只能使用 關(guān)鍵字 static

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

let c1 = Car.init()
let c2 = Car.init()
let c3 = Car.init()
print(Car.count)//3

類型屬性的細節(jié):
●不同于 存儲實例屬性 ,必須給 存儲類型屬性 設(shè)定初始值
○因為類型沒有想實例那樣的 init 初始化器來初始化 存儲屬性
●存儲類型屬性 默認就是 lazy。會在第一次使用的時候 初始化
○就算 被 多個線程 同時訪問,保證只會被 初始化 一次,線程是安全的
○存儲類型 屬性 也可以是 let
●枚舉類型 也可以 定義 類型屬性(存儲類型屬性,計算類型屬性)


Swift 中如何定義單例模式

可以通過類型屬性+let+private 來寫單例; 代碼如下如下:

// 方式一
public class SingleManager{
    public static let shared = SingleManager()
    private init(){}
}

// 方式二
public class SingleManager{
    public static let shared = {
        //...
        //...
        return SingleManager()
    }()
    private init(){}
}

// 上述兩個方法等價,一般推薦 方式二

Swift 中 下標是什么?

  • 使用subscript 可以給任意類型(枚舉,結(jié)構(gòu)體,類) 增加下標的功能

subscript 的語法 類似于 實例方法、計算屬性,本質(zhì)就是方法(函數(shù))

func xiabiaoTest() {
    class Point {
        var x = 0.0
        var 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
            }
        }
    }
    
    let p = Point()
    p[0] = 11.1 // 調(diào)用subscript
    p[1] = 22.2 // 調(diào)用subscript
    print(p.x)//11.1 不會調(diào)用 subscript
    print(p.y)//22/2 不會調(diào)用 subscript
    print(p[0])//11.1 // 調(diào)用subscript
    print(p[1])//22.2 // 調(diào)用subscript
}

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

一圖勝千言 針對類

  • 結(jié)構(gòu)體會默認生成 含有參數(shù)的初始化器,一旦自定義初始化器,默認的初始化器則不可用
  • 類默認只會生成無參的指定初始化器
swift
  • 類、結(jié)構(gòu)體、枚舉都可以定義初始化器
  • 類有2種初始化器: 指定初始化器(designated initializer)、便捷初始化器(convenience initializer)

什么是可選鏈?

可選鏈是一個調(diào)用和查詢可選屬性、方法和下標的過程,它可能為 nil 。

  • 如果可選項包含值,屬性、方法或者下標的調(diào)用成功;
  • 如果可選項是 nil ,屬性、方法或者下標的調(diào)用會返回 nil 。
  • 多個查詢可以鏈接在一起,如果鏈中任何一個節(jié)點是 nil ,那么整個鏈就會得體地失敗。

可選鏈(Optional Chaining)

如果 可選項 不會nil ,調(diào)用 方法 ,下標,屬性成功,結(jié)果會被包裝成 可選項,反之調(diào)用失敗,返回nil

class Car { var price = 0}
class Dog { var weight = 0}
class Person {
    var name:String = ""
    var dog :Dog = Dog()
    var car :Car? = Car()
    func age() -> Int { 18 }
    func eat() {print("Person Eat")}
    subscript (index:Int) ->Int {index}
}


var person :Person? = Person()
var age1 = person!.age() //Int
var age2 = person?.age() // Int?
var name = person?.name // String?
var index = person?[6] // Int?


func getName() -> String {"jackie"}

/*
 如果 person 對象 是 nil  ,將不會調(diào)用 getName() 方法
 */
person?.name = getName()

  • 如果結(jié)果本來就是可選項,不會進行再次包裝
if let _ = person?.eat() {
    /*
    Person Eat
    eat success
    */
    print("eat success")
} else {
    print("eat failure")
}
  • 多個 ? 可以連接在一起,其中任何一個節(jié)點 如果為 nil,那么整條鏈就會 調(diào)用失敗
var dog = person?.dog // Dog?
var weight = person?.dog.weight // Int?
var price = person?.car?.price // Int?

什么是運算符重載?

類、結(jié)構(gòu)體、枚舉可以為現(xiàn)有的運算符提供自定義的實現(xiàn),這個操作叫做:運算符重載

struct Point {
    var x: Int,y: Int
    static func + (p1: Point,p2: Point) -> Point {
        Point(x: p1.x + p2.x ,y: p1.y + p2.y)
    }
}
let p = Point(x:10,y: 20) + Point(x: 30,y: 40)
print(p) //Point(x: 40, y: 60)

Swift中函數(shù)的柯里化

將一個 接受 多個參數(shù)的 函數(shù),變成 只接受 一個參數(shù)的一系列 操作

示例

func add(_ v1: Int,_ v2: Int) -> Int {
    v1 + v2
}

func difference(_ v1: Int,_ v2: Int) -> Int {
    v1 - v2
}

func add2(_ v1: Int,_ v2: Int,_ v3 :Int ,_ v4 :Int) -> Int {
    v1 + v2 - v3 + v4
}
  • 偽柯里化
func currying_add(_ v1:Int) -> (Int) -> Int {
    return {$0 + v1}
}

/*
 將任意一個 接受兩個 參數(shù)的函數(shù) 柯里化
 */
func curring_fun_tow_params1(_ f1 :@escaping (Int,Int) -> Int, _ v1 :Int) -> (Int) -> Int {
//    return {
//        f1($0,v1)
//    }
    return { (v2) in
        return f1(v1 , v2)
    }
}

print(add(10, 20)) // 30
print(currying_add(10)(20)) // 30 // 30
print(curring_fun_tow_params1(add, 10)(20))
  • 正宗柯里化
func curring_fun_two_params2<A,B,C>(_ f1: @escaping (A,B) -> C) -> (A) -> ((B) -> C) {
    /*
     return {
         (a) in  // a = 3
         return {
             (b) in  // b = 8
             return  f1(a,b)
         }
     }
     */
    
    { a in { b in f1(a, b)} }
    
}

let result = curring_fun_two_params2(add)(3)(5)
print("result == ",result) //8
  • 柯里化拓展

/*
 -> (A) -> (B) -> (C) -> (D) -> E
 
 實際是 一連串 閉包的 組合  如下所示:
 
 -> (A) -> (  (B) ->    ((C)  ->   ((D) -> E))  )
 
 
 傳入 A   >  一個 閉包    (B)   ->   (  (C) -> ((D) -> E)  )

 
 傳入 B   >>  一個 閉包   (C)   ->   (  (D) -> E  )
 
 
 傳入 C   >>  一個閉包    (D) -> E
 
 
 */


//func curring_fun_more_params<A,B,C,D,E>(_ fn: @escaping (A,B,C,D) -> (E)) -> (A) -> ((B) -> ((C) -> ((D) -> E))) {
  func curring_fun_more_params<A,B,C,D,E>(_ fn: @escaping (A,B,C,D) -> (E)) -> (A) -> (B) -> (C) -> (D) -> E {
   /*
    return {
        (a) in
        return {
            (b) in
            return {
                (c) in
                return {
                    d in
                    return fn(a,b,c,d)
                }
            }
        }
    }
    */
    
    {a in { b in { c in { d in fn(a,b,c,d)}}}}
}

let resutl2 = curring_fun_more_params(add2)(10)(20)(30)(40)
print(resutl2) // 40

let resutl2_func = curring_fun_more_params(add2)
let resutl2_func_value = resutl2_func(10)(20)(30)(40)
print(resutl2_func_value) // 40
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容