swift 的基礎(chǔ)知識(shí)

1:?? 的作用

對(duì)可選類(lèi)型的值進(jìn)行解包,如果解包后的數(shù)據(jù)為nil,就用提供的缺省值
如:let url = responModel.url ?? ""

2.Optional(可選型) 是用什么實(shí)現(xiàn)的

enum Optional : _Reflectable, NilLiteralConvertible { 
  case None 
  case Some(T)
}

在這個(gè)定義中,對(duì) T 沒(méi)有任何限制,也就是說(shuō),我們是可以在 Optional 中裝入任意東西的,甚至也包括 Optional 對(duì)象自身。打個(gè)形象的比方,如果我們把 Optional 比作一個(gè)盒子,實(shí)際具體的 String 或者 Int 這樣的值比作糖果的話,當(dāng)我們打開(kāi)一個(gè)盒子 (unwrap) 時(shí),可能的結(jié)果會(huì)有三個(gè) -- 空氣,糖果,或者另一個(gè)盒子

3.接口和類(lèi)方法中的 SELF

我們?cè)诳匆恍┙涌诘亩x時(shí),可能會(huì)注意到出現(xiàn)了首字母大寫(xiě)的 Self 出現(xiàn)在類(lèi)型的位置上

protocol IntervalType {
    //...

    /// Return `rhs` clamped to `self`.  The bounds of the result, even
    /// if it is empty, are always within the bounds of `self`
    func clamp(intervalToClamp: Self) -> Self

    //...
}

比如上面這個(gè) IntervalType 的接口定義了一個(gè)方法,接受實(shí)現(xiàn)該接口的自身的類(lèi)型,并返回一個(gè)同樣的類(lèi)型。

這么定義是因?yàn)榻涌谄鋵?shí)本身是沒(méi)有自己的上下文類(lèi)型信息的,在聲明接口的時(shí)候,我們并不知道最后究竟會(huì)是什么樣的類(lèi)型來(lái)實(shí)現(xiàn)這個(gè)接口,Swift 中也不能在接口中定義泛型進(jìn)行限制。而在聲明接口時(shí),我們希望在接口中使用的類(lèi)型就是實(shí)現(xiàn)這個(gè)接口本身的類(lèi)型的話,就需要使用 Self 進(jìn)行指代。

但是在這種情況下,Self 不僅指代的是實(shí)現(xiàn)該接口的類(lèi)型本身,也包括了這個(gè)類(lèi)型的子類(lèi)。從概念上來(lái)說(shuō),Self 十分簡(jiǎn)單,但是實(shí)際實(shí)現(xiàn)一個(gè)這樣的方法卻稍微要轉(zhuǎn)個(gè)彎。為了說(shuō)明這個(gè)問(wèn)題,我們假設(shè)要實(shí)現(xiàn)一個(gè) Copyable 的接口,滿足這個(gè)接口的類(lèi)型需要返回一個(gè)和接受方法調(diào)用的實(shí)例相同的拷貝。一開(kāi)始我們可能考慮的接口是這樣的:

protocol Copyable {
    func copy() -> Self
}

這是很直接明了的,它應(yīng)該做的是創(chuàng)建一個(gè)和接受這個(gè)方法的對(duì)象同樣的東西,然后將其返回,返回的類(lèi)型不應(yīng)該發(fā)生改變,所以寫(xiě)為 Self。然后開(kāi)始嘗試實(shí)現(xiàn)一個(gè) MyClass 來(lái)滿足這個(gè)接口:

class MyClass: Copyable {

    var num = 1

    func copy() -> Self {
        // TODO: 返回什么?
        // return
    }
}

我們一開(kāi)始的時(shí)候可能會(huì)寫(xiě)類(lèi)似這樣的代碼:

這是錯(cuò)誤代碼

func copy() -> Self {
    let result = MyClass()
    result.num = num
    return result
}

但是顯然類(lèi)型是有問(wèn)題的,因?yàn)樵摲椒ㄒ蠓祷匾粋€(gè)抽象的、表示當(dāng)前類(lèi)型的 Self,但是我們卻返回了它的真實(shí)類(lèi)型 MyClass,這導(dǎo)致了無(wú)法編譯。也許你會(huì)嘗試把方法聲明中的 Self 改為 MyClass,這樣聲明就和實(shí)際返回一致了,但是很快你會(huì)發(fā)現(xiàn)這樣的話,實(shí)現(xiàn)的方法又和接口中的定義不一樣了,依然不能編譯。
為了解決這個(gè)問(wèn)題,我們?cè)谶@里需要的是通過(guò)一個(gè)和當(dāng)前上下文 (也就是和 MyClass) 無(wú)關(guān)的,又能夠指代當(dāng)前類(lèi)型的方式進(jìn)行初始化。希望你還能記得我們?cè)?a target="_blank">對(duì)象類(lèi)型中所提到的 dynamicType,這里我們就可以使用它來(lái)做初始化,以保證方法與當(dāng)前類(lèi)型上下文無(wú)關(guān),這樣不論是 MyClass 還是它的子類(lèi),都可以正確地返回合適的類(lèi)型滿足 Self 的要求:

func copy() -> Self {
    let result = self.dynamicType.init()
    result.num = num
    return result
}

但是很不幸,單單是這樣還是無(wú)法通過(guò)編譯,編譯器提示我們?nèi)绻胍獦?gòu)建一個(gè) Self 類(lèi)型的對(duì)象的話,需要有 required 關(guān)鍵字修飾的初始化方法,這是因?yàn)?Swift 必須保證當(dāng)前類(lèi)和其子類(lèi)都能響應(yīng)這個(gè) init 方法。另一個(gè)解決的方案是在當(dāng)前類(lèi)類(lèi)的聲明前添加 final 關(guān)鍵字,告訴編譯器我們不再會(huì)有子類(lèi)來(lái)繼承這個(gè)類(lèi)型。在這個(gè)例子中,我們選擇添加上 requiredinit 方法。最后,MyClass 類(lèi)型是這樣的:

class MyClass: Copyable {

    var num = 1

    func copy() -> Self {
        let result = self.dynamicType.init()
        result.num = num
        return result
    }

    required init() {

    }
}

我們可以通過(guò)測(cè)試來(lái)驗(yàn)證一下行為的正確性:

let object = MyClass()
object.num = 100

let newObject = object.copy()
object.num = 1

print(object.num)     // 1
print(newObject.num)  // 100

而對(duì)于 MyClass 的子類(lèi),copy() 方法也能正確地返回子類(lèi)的經(jīng)過(guò)拷貝的對(duì)象了。

另一個(gè)可以使用 Self 的地方是在類(lèi)方法中,使用起來(lái)也十分相似,核心就在于保證子類(lèi)也能返回恰當(dāng)?shù)念?lèi)型。

4. Swift中的Protocol

什么是Protocol?

Protocol是Swift中的一種自定義類(lèi)型,可以使用protocol定義某種約定,而不是某一種類(lèi)型,一般用于表示某種類(lèi)型的共性。

Protocol 用法

定義一個(gè)protocol

protocol PersonProtocol {
    func getName()
    func getSex()
}

某個(gè)class、struct或者enum要遵守這種約定的話,需要實(shí)現(xiàn)約定的方法

struct Person: PersonProtocol {
    func getName() {
         print("MelodyZhy")
    }
    func getSex() {
         print("boy")
    }
}
protocol中的約定方法,當(dāng)方法中有參數(shù)時(shí)是不能有默認(rèn)值的
protocol中也可以定義屬性,但必須明確指定該屬性支持的操作:只讀(get)或者是可讀寫(xiě)(get set)
protocol PersonProtocol {
    // 我們也可以在protocol中定義屬性
    // ?必須明確指定該屬性支持的操作:只讀(get)或者是可讀寫(xiě)(get set)
    var height: Int { get set }
    func getName()
    func getSex()
    // protocol中的約定方法,當(dāng)方法中有參數(shù)時(shí)是不能有默認(rèn)值的
    // ? Default argument not permitted in a protocol method
    // func getAge(age: Int = 18)
    func getAge(age: Int)
}
雖然height在protocol中是一個(gè)computed property,但在遵守該約定的類(lèi)型中可以簡(jiǎn)單的定義成一個(gè)stored property
當(dāng)protocol中定義了一個(gè)只讀屬性,其實(shí)我們也可以在遵守該約定的類(lèi)型中完成該屬性的可讀可寫(xiě)
protocol PersonProtocol {
    var height: Int { get set }
    var weight: Int { get }
    func getName()
    func getSex()
    func getAge(age: Int)
}

struct Person: PersonProtocol {
    var height = 178
    var weight = 120
    func getName() {
         print("MelodyZhy")
    }
    func getSex() {
         print("boy")
    }
    func getAge(age: Int) {
        print("age = \(age)")
    }
}

var person = Person()
person.height   // 178
person.height = 180
person.height  // 180

person.weight // 120
// 可以更改(但在以前版本的Swift中是不能直接這樣更改的
// 需要借助一個(gè)內(nèi)部的stored property
// 然后把這個(gè)屬性設(shè)計(jì)成一個(gè)computed property實(shí)現(xiàn) get 和 set 方法)
person.weight = 130 // 130
// 當(dāng)我們把person從Person轉(zhuǎn)換成PersonProtocol時(shí) 他就是只讀的了
// ?Cannot assign to property: 'weight' is a get-only property
(person as PersonProtocol).weight = 120
如何定義可選的protocol屬性或者方法?
@objc protocol PersonProtocol {
    optional var height: Int { get set }
    optional var weight: Int { get }
    optional func getName()
    optional func getSex()
    optional func getAge(age: Int)
}

class Person: PersonProtocol {
    // 如果想提供可選的約定方法或者屬性那么只能定義@objc的protocol
    // 并且這種約定只能class能遵守
}
protocol可以繼承,當(dāng)然struct、class、enum都可以同時(shí)遵守多個(gè)約定
// 例如:
protocol PersonProtocol {
    var height: Int { get set }
    var weight: Int { get }
    func getName()
    func getSex()
    func getAge(age: Int)
}
protocol Engineer: PersonProtocol {
    var good: Bool { get }
}
protocol Animal {
}
struct Person: Engineer, Animal {
    // 省略了該實(shí)現(xiàn)約定的方法和屬性
}

protocol extension

protocol extension 最關(guān)鍵的一點(diǎn)就是能在 protocol extension 方法中獲取 protocol 的屬性,因?yàn)镾wift編譯器知道任何一個(gè)遵守 protocol 的自定義類(lèi)型,一定會(huì)定義這個(gè) protocol 約定的各種屬性,既然這樣我們就可以在 protocol extension 中添加默認(rèn)的實(shí)現(xiàn)了。這也是為什么會(huì)有 protocol oriented programming 這個(gè)概念,但這時(shí)候肯定會(huì)有人說(shuō)我通過(guò)面對(duì)對(duì)象的編程方式也可以實(shí)現(xiàn),但為什么要用遵守 protocol 的方法呢,這個(gè)要等到了解 extension 中的 type constraints 后解釋...

先看一個(gè)通過(guò) protocol extension 添加默認(rèn)實(shí)現(xiàn)的代碼例子

// 定義一個(gè)人屬性的 protocol
protocol PersonProperty {
    var height: Int { get } // cm
    var weight: Double { get } // kg
    // 判斷體重是否合格的函數(shù)
    func isStandard() -> Bool
}
extension PersonProperty {
    // 給 protocol 添加默認(rèn)的實(shí)現(xiàn)
    func isStandard() -> Bool {
        return self.weight == Double((height - 100)) * 0.9
    }
    // 給 protocol 添加默認(rèn)屬性
    var isPerfectHeight: Bool {
        return self.height == 178
    }
}
struct Person: PersonProperty {
    var height: Int
    var weight: Double
    // 如果自定義類(lèi)型里面創(chuàng)建了遵守的 protocol 中的方法
    // 那么他將覆蓋 protocol 中的方法
//    func isStandard() -> Bool {
//        return true
//    }
}
// 創(chuàng)建遵守 PersonProperty 的自定義類(lèi)型
let p = Person(height: 178, weight: 61.5)
// 那么 p 這個(gè)自定義類(lèi)型 天生就有判斷這個(gè)人身高體重是否合格的方法
p.isStandard() // false
// 同樣天生具有判斷是否是 Perfect Height 的屬性
p.isPerfectHeight // true

protocol extension 中的 type constraints

這相當(dāng)于給 protocol extension 中的默認(rèn)實(shí)現(xiàn)添加限定條件,寫(xiě)法如下

// 運(yùn)動(dòng)因素的 protocol
protocol SportsFactors {
    // 運(yùn)動(dòng)量
    var sportQuantity: Double { get }
}

// 下面這種寫(xiě)法就用到了 extension 中的 type constraints
// 意思是 只有同時(shí)遵守了 SportsFactors 和 PersonProperty 時(shí)
// 才使 PersonProperty 獲得擴(kuò)展 并提供帶有 sportQuantity 屬性的 isStandard 方法
extension PersonProperty where Self: SportsFactors {
    func isStandard() -> Bool {
        // 隨意寫(xiě)的算法 不要在意
        return self.weight == Double((height - 100)) * 0.9 - self.sportQuantity
    }
}

protocol oriented programming 的優(yōu)點(diǎn)

1、首先繼承是 class 專(zhuān)有的,所以它不能用來(lái)擴(kuò)展其他類(lèi)型,但 protocol 是沒(méi)有這種局限性的
2、試想一下,上面的代碼你用面對(duì)對(duì)象的編程方式的話可能你就需要多一個(gè)運(yùn)動(dòng)量的屬性,同時(shí)也要修改 isStandard 函數(shù),一切看起來(lái)特別自然,隨著后續(xù)需求的更改可能會(huì)有更多因素影響是否是合格的體重,那么這時(shí)候你就會(huì)在不知不覺(jué)中將你代碼的耦合度成倍提高,其實(shí)對(duì)于這個(gè)類(lèi)來(lái)說(shuō),他完全不需要知道是否是合格體重的計(jì)算細(xì)節(jié),所以我們完全可以把這些類(lèi)型無(wú)關(guān)的細(xì)節(jié)從類(lèi)型定義上移出去,用一個(gè) protocol 封裝好這些細(xì)節(jié),然后讓其成為這個(gè)類(lèi)型的一種修飾,這就是POP的核心思想。
3、當(dāng)有多種因素制約是否是合格體重時(shí),我們可以用多個(gè) protocol 來(lái)對(duì)該類(lèi)型進(jìn)行修飾,每一種修飾的相關(guān)細(xì)節(jié),我們都在對(duì)應(yīng)的 protocol extension 中單獨(dú)的封裝起來(lái),這樣就大大降低了代碼的耦合度,同時(shí)代碼的可維護(hù)性也得到了相應(yīng)的提高。swift標(biāo)準(zhǔn)庫(kù)中大部分都是用這種思想構(gòu)建的。**

5.Protocol Oriented Programming 面向協(xié)議編程

面向協(xié)議編程中,Protocol 實(shí)際上就是 DIP 中的抽象接口。通過(guò)之前的講解,采用面向協(xié)議的方式進(jìn)行編程,即是對(duì)依賴(lài)反轉(zhuǎn)原則 DIP 的踐行,在一定程度上降低代碼的耦合性,避免耦合性過(guò)高帶來(lái)的問(wèn)題。下面通過(guò)一個(gè)具體實(shí)例簡(jiǎn)單講解一下:
首先是高層次結(jié)構(gòu)的實(shí)現(xiàn),創(chuàng)建EmmettBrown的類(lèi),然后聲明了一個(gè)需求(travelInTime方法)。

// 高層次實(shí)現(xiàn) - EmmettBrown
final class EmmettBrown {
    private let timeMachine: TimeTraveling
    init(timeMachine: TimeTraveling) {
        self.timeMachine = timeMachine
    }
    func travelInTime(time: TimeInterval) -> String {
        return timeMachine.travelInTime(time: time)
    }
}

采用 Protocol 定義抽象接口 travelInTime,低層次的實(shí)現(xiàn)將需要依賴(lài)這個(gè)接口。

// 抽象接口 - 時(shí)光旅行
protocol TimeTraveling {
    func travelInTime(time: TimeInterval) -> String
}

最后是低層次實(shí)現(xiàn),創(chuàng)建DeLorean類(lèi),通過(guò)遵循TimeTraveling協(xié)議,完成TravelInTime抽象接口的具體實(shí)現(xiàn)。

// 低層次實(shí)現(xiàn) - DeLorean
final class DeLorean: TimeTraveling {
    func travelInTime(time: TimeInterval) -> String {
        return "Used Flux Capacitor and travelled in time by: \(time)s"
    }
}

使用的時(shí)候只需要?jiǎng)?chuàng)建相關(guān)類(lèi)即可調(diào)用其方法。

// 使用方式
let timeMachine = DeLorean()
let mastermind = EmmettBrown(timeMachine: timeMachine)
mastermind.travelInTime(time: -3600 * 8760)

Delegate - 利用 Protocol 解耦

委托(Delegate)是一種設(shè)計(jì)模式,表示將一個(gè)對(duì)象的部分功能轉(zhuǎn)交給另一個(gè)對(duì)象。委托模式可以用來(lái)響應(yīng)特定的動(dòng)作,或者接收外部數(shù)據(jù)源提供的數(shù)據(jù),而無(wú)需關(guān)心外部數(shù)據(jù)源的類(lèi)型。部分情況下,Delegate 比起自上而下的繼承具有更松的耦合程度,有效的減少代碼的復(fù)雜程度。

那么 Deleagte 和 Protocol 之間是什么關(guān)系呢?在 Swift 中,Delegate 就是基于 Protocol 實(shí)現(xiàn)的,定義 Protocol 來(lái)封裝那些需要被委托的功能,這樣就能確保遵循協(xié)議的類(lèi)型能提供這些功能。

Protocol 是 Swift 的語(yǔ)言特性之一,而 Delegate 是利用了 Protocol 來(lái)達(dá)到解耦的目的。

Delegate 使用實(shí)例:
//定義一個(gè)委托
protocol CustomButtonDelegate: AnyObject{
    func CustomButtonDidClick()
}
 
class ACustomButton: UIView {
    ...
    weak var delegate: ButtonDelegate?
    func didClick() {
        delegate?.CustomButtonDidClick()
    }
}

// 遵循委托的類(lèi)
class ViewController: UIViewController, CustomButtonDelegate {
    let view = ACustomButton()
    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        view.delegate = self
    }
    func CustomButtonDidClick() {
        print("Delegation works!")
    }
}

代碼說(shuō)明

如前所述,Delegate 的原理其實(shí)很簡(jiǎn)單。ViewController會(huì)將 ACustomButtondelegate 設(shè)置為自己,同時(shí)自己遵循、實(shí)現(xiàn)了 CustomButtonDelegate 協(xié)議中的方法。這樣在后者調(diào)用 didClick 方法的時(shí)候會(huì)調(diào)用 CustomButtonDidClick 方法,從而觸發(fā)前者中對(duì)應(yīng)的方法,從而打印出 Delegation works!

循環(huán)引用

我們注意到,在聲明委托時(shí),我們使用了 weak 關(guān)鍵字。目的是在于避免循環(huán)引用。ViewController擁有 view,而 view.delegate 又強(qiáng)引用了ViewController,如果不將其中一個(gè)強(qiáng)引用設(shè)置為弱引用,就會(huì)造成循環(huán)引用的問(wèn)題。

AnyObject

定義委托時(shí),我們讓 protocol 繼承自 AnyObject。這是由于,在 Swift 中,這表示這一個(gè)協(xié)議只能被應(yīng)用于 class(而不是 struct 和 enum)。

實(shí)際上,如果讓 protocol 不繼承自任何東西,那也是可以的,這樣定義的 Delegate 就可以被應(yīng)用于 class 以及 struct、enum。由于 Delegate 代表的是遵循了該協(xié)議的實(shí)例,所以當(dāng) Delegate 被應(yīng)用于 class 時(shí),它就是 Reference type,需要考慮循環(huán)引用的問(wèn)題,因此就必須要用 weak 關(guān)鍵字。

但是這樣的問(wèn)題在于,當(dāng) Delegate 被應(yīng)用于structenum 時(shí),它是 Value type,不需要考慮循環(huán)引用的問(wèn)題,也不能被使用 weak關(guān)鍵字。所以當(dāng)Delegate未限定只能用于 class,Xcode 就會(huì)對(duì) weak 關(guān)鍵字報(bào)錯(cuò):'weak' may only be applied to class and class-bound protocol types

delegate只能用于類(lèi)中

定義協(xié)議的格式
編寫(xiě)協(xié)議的格式:
protocol 協(xié)議名字 : 基協(xié)議 {  //當(dāng)然也可以不遵守基協(xié)議
    //方法的聲明
}
例:定義一個(gè)買(mǎi)票的協(xié)議
protocol buyTicketProtocol {
    func buyTicket() -> Void
}
Tips:
如果一個(gè)協(xié)議繼承了基協(xié)議NSObjectProtocol,那么遵守這個(gè)協(xié)議的類(lèi)也必須要繼承NSObject這個(gè)類(lèi)
遵守協(xié)議的格式

一個(gè)類(lèi)若要遵守一個(gè)協(xié)議,只需要在自己所繼承的父類(lèi)后面寫(xiě)上要遵守的協(xié)議名并以逗號(hào)","隔開(kāi),如果這個(gè)類(lèi)無(wú)需繼承,那么直接在冒號(hào)后面寫(xiě)上協(xié)議的名字就好

遵守協(xié)議的格式:
class Person : NSObject,SportProtocol{}
例:定義一個(gè)會(huì)買(mǎi)票的黃牛類(lèi)
class Tout : buyTicketProtocol {  //無(wú)繼承類(lèi)遵守協(xié)議
    func buyTicket() {
        print("here's your ticket")
    }
}
Tips:
Swift中的基協(xié)為NSObjectProtocol,這與OC中的基協(xié)議(NSObject)有些不同

協(xié)議的繼承

  • 上面有提到,當(dāng)我們自定義一個(gè)協(xié)議的時(shí)候可以選擇讓這個(gè)協(xié)議繼承自NSObjectProtocol,不單單如此,自定義的協(xié)議也可以遵守另外一個(gè)協(xié)議哦,基本格式如下:
protocol showTicketNumberProtocol {  //展示票號(hào)
    func showTicketNumber() -> Void
}
protocol buyTicketProtocol : showTicketNumberProtocol  {  //買(mǎi)票
    func buyTicket() -> Void
}
  • 如果一個(gè)類(lèi)遵循了一個(gè)含有繼承的協(xié)議,那么這個(gè)類(lèi)就必須實(shí)現(xiàn)這個(gè)協(xié)議鏈中所有的必須實(shí)現(xiàn)的函數(shù),否則編譯報(bào)錯(cuò)
class Tout : buyTicketProtocol  { 
    func buyTicket() {
        print("here's your ticket")
    }
    func showTicketNumber() {  //必須實(shí)現(xiàn)buyTicketProtocol所繼承的"父"協(xié)議中的函數(shù)
        print("123456")
    }
}
Tips:
上面提到的"如果一個(gè)協(xié)議繼承了基協(xié)議NSObjectProtocol,那么遵守這個(gè)協(xié)議的類(lèi)也必須要繼承NSObject這個(gè)類(lèi)"
這是因?yàn)槲覀冃枰狽SObject這個(gè)父類(lèi)來(lái)幫我們實(shí)現(xiàn)NSObjectProtocol中定義的函數(shù),否則編譯器會(huì)以"沒(méi)有實(shí)現(xiàn)NSObjectProtocol中的函數(shù)為由而報(bào)錯(cuò)
協(xié)議中可選實(shí)現(xiàn)的函數(shù)

為了保證Swift語(yǔ)言的嚴(yán)謹(jǐn)性,不建議在協(xié)議中定義可選實(shí)現(xiàn)的函數(shù),不過(guò)不建議不代表不能嘛,我們可以利用OC特性來(lái)實(shí)現(xiàn)在Swift協(xié)議中定義可選實(shí)現(xiàn)函數(shù)

  • 創(chuàng)建帶有OC特性的協(xié)議
@objc  //表示一下代碼含有OC特性
protocol showTicketNumberProtocol {
    optional func showTicketNumber() -> Void  //optional修飾的函數(shù)為可選擇實(shí)現(xiàn)(或不實(shí)現(xiàn))的函數(shù)
}
  • 遵守帶有OC特性的協(xié)議
class Tout : showTicketNumberProtocol  {
    //終于,下面這個(gè)函數(shù)可以不實(shí)現(xiàn),并且不會(huì)報(bào)錯(cuò)了
    @objc func showTicketNumber() {  //由于showTicketNumberProtocol含有OC特性,于是這個(gè)協(xié)議中所有的函數(shù)在實(shí)現(xiàn)之前都要有@objc來(lái)修飾
        print("123456")
    }
}

6.Structure 和Class 的差別

相同之處

1.擁有自己的屬性(property)及方法(function)
2.可以自訂建構(gòu)子(initializer)

不同之處
  1. Structure無(wú)法繼承
  2. Structure擁有成員建構(gòu)子(Memberwise Initializer)
  3. Structure是Value Type , Class是Reference Type
  4. Structure不能直接修改內(nèi)部屬性,如果要修改必須加上mutating

大致意思就是說(shuō),雖然結(jié)構(gòu)體和枚舉可以定義自己的方法,但是默認(rèn)情況下,實(shí)例方法中是不可以修改值類(lèi)型的屬性。
舉個(gè)簡(jiǎn)單的例子,假如定義一個(gè)點(diǎn)結(jié)構(gòu)體,該結(jié)構(gòu)體有一個(gè)修改點(diǎn)位置的實(shí)例方法:

struct Point {
  var x = 0, y = 0
  
  func moveXBy(x:Int,yBy y:Int) {
    self.x += x
    // Cannot invoke '+=' with an argument list of type '(Int, Int)'
    self.y += y
    // Cannot invoke '+=' with an argument list of type '(Int, Int)'
  }
}

編譯器拋出錯(cuò)誤,說(shuō)明確實(shí)不能在實(shí)例方法中修改屬性值。

為了能夠在實(shí)例方法中修改屬性值,可以在方法定義前添加關(guān)鍵字 mutating

struct Point {
  var x = 0, y = 0
  mutating func moveXBy(x:Int,yBy y:Int) {
    self.x += x
    self.y += y
  }
}
var p = Point(x: 5, y: 5)
p.moveXBy(3, yBy: 3)

另外,在值類(lèi)型的實(shí)例方法中,也可以直接修改self屬性值。

enum TriStateSwitch {
  case Off, Low, High
  mutating func next() {
    switch self {
    case Off:
      self = Low
    case Low:
      self = High
    case High:
      self = Off
    }
  }
}
var ovenLight = TriStateSwitch.Low
ovenLight.next()
// ovenLight is now equal to .High
ovenLight.next()
// ovenLight is now equal to .Off”

TriStateSwitch枚舉定義了一個(gè)三個(gè)狀態(tài)的開(kāi)關(guān),在next實(shí)例方法中動(dòng)態(tài)改變self屬性的值。

當(dāng)然,在引用類(lèi)型中(即class)中的方法默認(rèn)情況下就可以修改屬性值,不存在以上問(wèn)題。

7. 值類(lèi)型和引用類(lèi)型的區(qū)別

由于 Swift 中的 struct 為值類(lèi)型,class 為引用類(lèi)型,因此文中以這兩種類(lèi)型為代表來(lái)具體闡述

stack & heap

內(nèi)存(RAM)中有兩個(gè)區(qū)域,棧區(qū)(stack)和堆區(qū)(heap)。在 Swift 中,值類(lèi)型,存放在棧區(qū);引用類(lèi)型,存放在堆區(qū)。

class RectClass {
    var height = 0.0
    var width = 0.0
}

struct RectStruct {
    var height = 0.0
    var width = 0.0
}

var rectCls = RectClass()
var rectStrct = RectStruct()

值類(lèi)型(Value Type)

值類(lèi)型,即每個(gè)實(shí)例保持一份數(shù)據(jù)拷貝。

在 Swift 中,典型的有 struct,enum,以及 tuple 都是值類(lèi)型。而平時(shí)使用的 Int, DoubleFloat,String,ArrayDictionary,Set 其實(shí)都是用結(jié)構(gòu)體實(shí)現(xiàn)的,也是值類(lèi)型。
Swift 中,值類(lèi)型的賦值為深拷貝(Deep Copy),值語(yǔ)義(Value Semantics)即新對(duì)象和源對(duì)象是獨(dú)立的,當(dāng)改變新對(duì)象的屬性,源對(duì)象不會(huì)受到影響,反之同理。
在 Swift 中,雙等號(hào)(== &!=)可以用來(lái)比較變量存儲(chǔ)的內(nèi)容是否一致,如果要讓我們的struct 類(lèi)型支持該符號(hào),則必須遵守 Equatable協(xié)議。

extension CoordinateStruct: Equatable {
    static func ==(left: CoordinateStruct, right: CoordinateStruct) -> Bool {
        return (left.x == right.x && left.y == right.y)
    }
}

if coordA != coordB {
    print("coordA != coordB")
}

引用類(lèi)型(Reference Type)

引用類(lèi)型,即所有實(shí)例共享一份數(shù)據(jù)拷貝。

在 Swift 中,class 和閉包是引用類(lèi)型。引用類(lèi)型的賦值是淺拷貝(Shallow Copy),引用語(yǔ)義(Reference Semantics)即新對(duì)象和源對(duì)象的變量名不同,但其引用(指向的內(nèi)存空間)是一樣的,因此當(dāng)使用新對(duì)象操作其內(nèi)部數(shù)據(jù)時(shí),源對(duì)象的內(nèi)部數(shù)據(jù)也會(huì)受到影響。

class Dog {
    var height = 0.0
    var weight = 0.0
}

var dogA = Dog()
var dogB = dogA

dogA.height = 50.0
print("dogA.height -> \(dogA.height)")
print("dogB.height -> \(dogB.height)")

// dogA.height -> 50.0
// dogB.height -> 50.0

如果聲明一個(gè)引用類(lèi)型的常量,那么就意味著該常量的引用不能改變(即不能被同類(lèi)型變量賦值),但指向的內(nèi)存中所存儲(chǔ)的變量是可以改變的。
在 Swift 中,三等號(hào)(=== &!==)可以用來(lái)比較引用類(lèi)型的引用(即指向的內(nèi)存地址)是否一致。也可以在遵守 Equatable 協(xié)議后,使用雙等號(hào)(==&!=)用來(lái)比較變量的內(nèi)容是否一致。

函數(shù)傳參

在 Swift 中,函數(shù)的參數(shù)默認(rèn)為常量,即在函數(shù)體內(nèi)只能訪問(wèn)參數(shù),而不能修改參數(shù)值。具體來(lái)說(shuō):
1.值類(lèi)型作為參數(shù)傳入時(shí),函數(shù)體內(nèi)部不能修改其值
2.引用類(lèi)型作為參數(shù)傳入時(shí),函數(shù)體內(nèi)部不能修改其指向的內(nèi)存地址,但是可以修改其內(nèi)部的變量值

值類(lèi)型

當(dāng)值類(lèi)型的變量作為參數(shù)被傳入函數(shù)時(shí),相當(dāng)于創(chuàng)建了新的常量并初始化為傳入的變量值,該參數(shù)的作用域及生命周期僅存在于函數(shù)體內(nèi)。
當(dāng)引用類(lèi)型的變量作為參數(shù)被傳入函數(shù)時(shí),相當(dāng)于創(chuàng)建了新的常量并初始化為傳入的變量引用,當(dāng)函數(shù)體內(nèi)操作參數(shù)指向的數(shù)據(jù),函數(shù)體外也受到了影響

inout

inout是 Swift 中的關(guān)鍵字,可以放置于參數(shù)類(lèi)型前,冒號(hào)之后。使用 inout之后,函數(shù)體內(nèi)部可以直接更改參數(shù)值,而且改變會(huì)保留。

func swap(resSct: inout ResolutionStruct) {
    withUnsafePointer(to: &resSct) { print("During calling: \($0)") }
    let temp = resSct.height
    resSct.height = resSct.width
    resSct.width = temp
}

var iPhone6ResoStruct = ResolutionStruct(height: 1334, width: 750)
print(iPhone6ResoStruct)
withUnsafePointer(to: &iPhone6ResoStruct) { print("Before calling: \($0)") }
swap(resSct: &iPhone6ResoStruct)
print(iPhone6ResoStruct)
withUnsafePointer(to: &iPhone6ResoStruct) { print("After calling: \($0)") }

小結(jié):值類(lèi)型變量作為參數(shù)傳入函數(shù),外界和函數(shù)參數(shù)的內(nèi)存地址一致,函數(shù)內(nèi)對(duì)參數(shù)的更改得到了保留。
需要注意的是:
1.使用 inout 關(guān)鍵字的函數(shù),在調(diào)用時(shí)需要在該參數(shù)前加上 & 符號(hào)
2.inout 參數(shù)在傳入時(shí)必須為變量,不能為常量或字面量(literal)
3.inout 參數(shù)不能有默認(rèn)值,不能為可變參數(shù)
4.inout 參數(shù)不等同于函數(shù)返回值,是一種使參數(shù)的作用域超出函數(shù)體的方式
5.多個(gè)inout 參數(shù)不能同時(shí)傳入同一個(gè)變量,因?yàn)榭饺肟匠龅捻樞虿欢?,那么最終值也不能確定

struct Point {
    var x = 0.0
    var y = 0.0
}

struct Rectangle {
    var width = 0.0
    var height = 0.0
    var origin = Point()
    
    var center: Point {
        get {
            print("center GETTER call")
            return Point(x: origin.x + width / 2,
                         y: origin.y + height / 2)
        }
        
        set {
            print("center SETTER call")
            origin.x = newValue.x - width / 2
            origin.y = newValue.y - height / 2
        }
    }
    
    func reset(center: inout Point) {
        center.x = 0.0
        center.y = 0.0
    }
    
}

var rect = Rectangle(width: 100, height: 100, origin: Point(x: -100, y: -100))
print(rect.center)
rect.reset(center: &rect.center)
print(rect.center)

// center GETTER call
// Point(x: -50.0, y: -50.0)

// center GETTER call
// center SETTER call

// center GETTER call
// Point(x: 0.0, y: 0.0)

inout參數(shù)的傳遞過(guò)程:

  1. 當(dāng)函數(shù)被調(diào)用時(shí),參數(shù)值被拷貝
  2. 在函數(shù)體內(nèi),被拷貝的參數(shù)修改
  3. 函數(shù)返回時(shí),被拷貝的參數(shù)值被賦值給原有的變量

8.Swift 構(gòu)造過(guò)程

構(gòu)造器
語(yǔ)法
init()
{
    // 實(shí)例化后執(zhí)行的代碼
}
默認(rèn)屬性值

我們可以在構(gòu)造器中為存儲(chǔ)型屬性設(shè)置初始值;同樣,也可以在屬性聲明時(shí)為其設(shè)置默認(rèn)值。

struct rectangle {
    // 設(shè)置默認(rèn)值
    var length = 6
    var breadth = 12
}
var area = rectangle()
print("矩形的面積為 \(area.length*area.breadth)")
構(gòu)造參數(shù)

你可以在定義構(gòu)造器 init() 時(shí)提供構(gòu)造參數(shù),如下所示:

struct Rectangle {
    var length: Double
    var breadth: Double
    var area: Double
    
    init(fromLength length: Double, fromBreadth breadth: Double) {
        self.length = length
        self.breadth = breadth
        area = length * breadth
    }
    
    init(fromLeng leng: Double, fromBread bread: Double) {
        self.length = leng
        self.breadth = bread
        area = leng * bread
    }
}

let ar = Rectangle(fromLength: 6, fromBreadth: 12)
print("面積為: \(ar.area)")

let are = Rectangle(fromLeng: 36, fromBread: 12)
print("面積為: \(are.area)")
內(nèi)部和外部參數(shù)名

跟函數(shù)和方法參數(shù)相同,構(gòu)造參數(shù)也存在一個(gè)在構(gòu)造器內(nèi)部使用的參數(shù)名字和一個(gè)在調(diào)用構(gòu)造器時(shí)使用的外部參數(shù)名字。

然而,構(gòu)造器并不像函數(shù)和方法那樣在括號(hào)前有一個(gè)可辨別的名字。所以在調(diào)用構(gòu)造器時(shí),主要通過(guò)構(gòu)造器中的參數(shù)名和類(lèi)型來(lái)確定需要調(diào)用的構(gòu)造器。

如果你在定義構(gòu)造器時(shí)沒(méi)有提供參數(shù)的外部名字,Swift 會(huì)為每個(gè)構(gòu)造器的參數(shù)自動(dòng)生成一個(gè)跟內(nèi)部名字相同的外部名。

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
}

// 創(chuàng)建一個(gè)新的Color實(shí)例,通過(guò)三種顏色的外部參數(shù)名來(lái)傳值,并調(diào)用構(gòu)造器
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)

print("red 值為: \(magenta.red)")
print("green 值為: \(magenta.green)")
print("blue 值為: \(magenta.blue)")

// 創(chuàng)建一個(gè)新的Color實(shí)例,通過(guò)三種顏色的外部參數(shù)名來(lái)傳值,并調(diào)用構(gòu)造器
let halfGray = Color(white: 0.5)
print("red 值為: \(halfGray.red)")
print("green 值為: \(halfGray.green)")
print("blue 值為: \(halfGray.blue)")

以上程序執(zhí)行輸出結(jié)果為:

red 值為: 1.0
green 值為: 0.0
blue 值為: 1.0
red 值為: 0.5
green 值為: 0.5
blue 值為: 0.5
沒(méi)有外部名稱(chēng)參數(shù)

如果你不希望為構(gòu)造器的某個(gè)參數(shù)提供外部名字,你可以使用下劃線_來(lái)顯示描述它的外部名。

struct Rectangle {
    var length: Double
    
    init(frombreadth breadth: Double) {
        length = breadth * 10
    }
    
    init(frombre bre: Double) {
        length = bre * 30
    }
    //不提供外部名字
    init(_ area: Double) {
        length = area
    }
}

// 調(diào)用不提供外部名字
let rectarea = Rectangle(180.0)
print("面積為: \(rectarea.length)")

// 調(diào)用不提供外部名字
let rearea = Rectangle(370.0)
print("面積為: \(rearea.length)")

// 調(diào)用不提供外部名字
let recarea = Rectangle(110.0)
print("面積為: \(recarea.length)")
可選屬性類(lèi)型

如果你定制的類(lèi)型包含一個(gè)邏輯上允許取值為空的存儲(chǔ)型屬性,你都需要將它定義為可選類(lèi)型optional type(可選屬性類(lèi)型)。

當(dāng)存儲(chǔ)屬性聲明為可選時(shí),將自動(dòng)初始化為空 nil。

struct Rectangle {
    var length: Double?
    
    init(frombreadth breadth: Double) {
        length = breadth * 10
    }
    
    init(frombre bre: Double) {
        length = bre * 30
    }
    
    init(_ area: Double) {
        length = area
    }
}

let rectarea = Rectangle(180.0)
print("面積為:\(rectarea.length)")

let rearea = Rectangle(370.0)
print("面積為:\(rearea.length)")

let recarea = Rectangle(110.0)
print("面積為:\(recarea.length)")

以上程序執(zhí)行輸出結(jié)果為:

面積為:Optional(180.0)
面積為:Optional(370.0)
面積為:Optional(110.0)
構(gòu)造過(guò)程中修改常量屬性

只要在構(gòu)造過(guò)程結(jié)束前常量的值能確定,你可以在構(gòu)造過(guò)程中的任意時(shí)間點(diǎn)修改常量屬性的值。

對(duì)某個(gè)類(lèi)實(shí)例來(lái)說(shuō),它的常量屬性只能在定義它的類(lèi)的構(gòu)造過(guò)程中修改;不能在子類(lèi)中修改。

盡管 length 屬性現(xiàn)在是常量,我們?nèi)匀豢梢栽谄漕?lèi)的構(gòu)造器中設(shè)置它的值:

struct Rectangle {
    let length: Double?
    
    init(frombreadth breadth: Double) {
        length = breadth * 10
    }
    
    init(frombre bre: Double) {
        length = bre * 30
    }
    
    init(_ area: Double) {
        length = area
    }
}

let rectarea = Rectangle(180.0)
print("面積為:\(rectarea.length)")

let rearea = Rectangle(370.0)
print("面積為:\(rearea.length)")

let recarea = Rectangle(110.0)
print("面積為:\(recarea.length)")
類(lèi)的繼承和構(gòu)造過(guò)程

Swift 提供了兩種類(lèi)型的類(lèi)構(gòu)造器來(lái)確保所有類(lèi)實(shí)例中存儲(chǔ)型屬性都能獲得初始值,它們分別是指定構(gòu)造器和便利構(gòu)造器。

指定構(gòu)造器實(shí)例
class mainClass {
    var no1 : Int // 局部存儲(chǔ)變量
    init(no1 : Int) {
        self.no1 = no1 // 初始化
    }
}
class subClass : mainClass {
    var no2 : Int // 新的子類(lèi)存儲(chǔ)變量
    init(no1 : Int, no2 : Int) {
        self.no2 = no2 // 初始化
        super.init(no1:no1) // 初始化超類(lèi)
    }
}

let res = mainClass(no1: 10)
let res2 = subClass(no1: 10, no2: 20)

print("res 為: \(res.no1)")
print("res2 為: \(res2.no1)")
print("res2 為: \(res2.no2)")
便利構(gòu)造器實(shí)例
class mainClass {
    var no1 : Int // 局部存儲(chǔ)變量
    init(no1 : Int) {
        self.no1 = no1 // 初始化
    }
}

class subClass : mainClass {
    var no2 : Int
    init(no1 : Int, no2 : Int) {
        self.no2 = no2
        super.init(no1:no1)
    }
    // 便利方法只需要一個(gè)參數(shù)
    override convenience init(no1: Int)  {
        self.init(no1:no1, no2:0)
    }
}
let res = mainClass(no1: 20)
let res2 = subClass(no1: 30, no2: 50)

print("res 為: \(res.no1)")
print("res2 為: \(res2.no1)")
print("res2 為: \(res2.no2)")
構(gòu)造器的繼承和重載

Swift 中的子類(lèi)不會(huì)默認(rèn)繼承父類(lèi)的構(gòu)造器。

父類(lèi)的構(gòu)造器僅在確定和安全的情況下被繼承。

當(dāng)你重寫(xiě)一個(gè)父類(lèi)指定構(gòu)造器時(shí),你需要寫(xiě)override修飾符。

class SuperClass {
    var corners = 4
    var description: String {
        return "\(corners) 邊"
    }
}
let rectangle = SuperClass()
print("矩形: \(rectangle.description)")

class SubClass: SuperClass {
    override init() {  //重載構(gòu)造器
        super.init()
        corners = 5
    }
}

let subClass = SubClass()
print("五角型: \(subClass.description)")

9.Swift中的元組(Tuples)

創(chuàng)建元組

在Swift中創(chuàng)建元組的方式很簡(jiǎn)單,它的語(yǔ)法有點(diǎn)類(lèi)似數(shù)組,但是需要把方括號(hào)替換為圓括號(hào):

let firstHighScore = ("Mary", 9001)

與數(shù)組不同的是,元組中的元素可以是任意類(lèi)型。上面代碼中firstHighScore元組就包含一個(gè)String類(lèi)型的元素和一個(gè)Int類(lèi)型的元素。
另外,在創(chuàng)建元組時(shí)你還可以給元組中的元素命名:

let secondHighScore = (name: "James", score: 4096)

這樣可以讓我們?cè)谑褂迷M的時(shí)候明確的指定某個(gè)元素,非常有用。在后面的文章中大家可以看到給元素命名的好處。

以上就是創(chuàng)建元組的兩種方式,非常簡(jiǎn)單和簡(jiǎn)潔。你不需要像創(chuàng)建struct一樣寫(xiě)出它的結(jié)構(gòu)和內(nèi)部屬性,也不需要像創(chuàng)建class一樣要寫(xiě)初始化方法。你只需要把你想用的、任何類(lèi)型的值放在圓括號(hào)內(nèi),用逗號(hào)隔開(kāi)即可。如果你愿意你還可以給每個(gè)元素命名,提高元組使用效率。

從元組中讀元素

從元組中讀取元素有幾種方式,但一般我們會(huì)選擇最適合當(dāng)前應(yīng)用場(chǎng)景的方式,并且確保選擇的方式是在當(dāng)前情況下最簡(jiǎn)單的一種。

元組元素沒(méi)有命名

如果我們沒(méi)有給元組的元素命名,我們可以用點(diǎn)語(yǔ)法,通過(guò)定義好的元組變量或常量獲取它的第1個(gè)到第n個(gè)元素:

let firstHighScore = ("Mary", 9001)
firstHighScore.0            // Mary
firstHighScore.1        // 9001

如果你覺(jué)得上述這種方法會(huì)造成語(yǔ)義的不明確,那么我們還可以將元組賦值給一個(gè)帶有元素名稱(chēng)的元組(元素名稱(chēng)個(gè)數(shù)要對(duì)應(yīng)):


let (firstName, firstScore) = firstHighScore
firstName       // Mary
firstScore      // 9001

如果你只想讀取firstHighScore元組中的分?jǐn)?shù),那么你可以這樣寫(xiě):

let (_, firstScore) = firstHighScore
firstScore      // 9001
元組元素有命名

如果我們已經(jīng)給元組中的元素命名了名稱(chēng),那么我們可以這樣寫(xiě):

let secondName = secondHighScore.name
let secondScore = secondHighScore.score
secondName      // James
secondScore     // 4096
將元組作為函數(shù)返回值

我們可以將元組作為函數(shù)的返回值,下面這個(gè)函數(shù)的返回值就是我們之前定義過(guò)的secondHighScore元組:

func getAHighScore() -> (name: String, score: Int)
{
    let theName = "Patricia"
    let theScore = 3894
    
    return (theName, theScore)
}

為什么說(shuō)上述函數(shù)的返回值是secondHighScore元組呢?因?yàn)間etAHighScore函數(shù)返回的元組元素個(gè)數(shù)、元素名稱(chēng)、元素類(lèi)型均和secondHighScore相同。

其實(shí)將元組作為函數(shù)的返回值時(shí)也可以不必對(duì)元素進(jìn)行命名,只要你明白每個(gè)元素代表的含義即可:

func getAHighScore() -> (String, Int)
{
    let theName = "Patricia"
    let theScore = 3894
    
    return (theName, theScore)
}

如果你不確定返回的元組一定不為nil,那么你可以返回一個(gè)可選的元組類(lèi)型:

func maybeGetHighScore() -> (String, Int)?
{
    return nil
}

因?yàn)槭强蛇x的元組類(lèi)型,所以當(dāng)返回的元組不為nil時(shí),你需要對(duì)元組進(jìn)行解包:

if let possibleScore = maybeGetHighScore()
{
    possibleScore.0
    possibleScore.1
}
else
{
    println("Nothing Here")
}

注意:當(dāng)你定義了一個(gè)沒(méi)有返回值的函數(shù)時(shí),其實(shí)該函數(shù)是返回一個(gè)空的元組()。

元組的訪問(wèn)級(jí)別

元組的訪問(wèn)級(jí)別取決于它包含的元素。比如元組里的元素都是private級(jí)別的,那么該元組也是private級(jí)別的。但這里有一個(gè)遵循最小的原則,也就是說(shuō)如果一個(gè)元組中有兩個(gè)元素,一個(gè)為private級(jí)別,另一個(gè)為public級(jí)別,那么該元組遵循最小原則,它的訪問(wèn)級(jí)別為private。

元組是值類(lèi)型

關(guān)于值類(lèi)型和引用類(lèi)型的知識(shí)這里不再累贅,我們通過(guò)一個(gè)代碼示例來(lái)看看元組是哪種類(lèi)型:

var someScore = ("John", 55)
 
var anotherScore = someScore
anotherScore.0 = "Robert"
 
 
println(anotherScore.0)  //Outputs:  "Robert"
println(someScore.0)     //Outputs:  "John"

通過(guò)上述的代碼示例可以看出,我把someScore元組賦值給了anotherScore,然后修改了anotherScore的第1個(gè)元素的值,最后分別打印了someScore和anotherScore第1個(gè)元素的值。someScore元組第一個(gè)元素的值為Robert,而anotherScore元組第一個(gè)元素的值仍然為John。由此可見(jiàn)元組是值類(lèi)型。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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