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è)例子中,我們選擇添加上 required 的 init 方法。最后,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ì)將 ACustomButton 的 delegate 設(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)用于struct 和 enum 時(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)
不同之處
- Structure無(wú)法繼承
- Structure擁有成員建構(gòu)子(Memberwise Initializer)
- Structure是Value Type , Class是Reference Type
- 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, Double,Float,String,Array,Dictionary,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ò)程:
- 當(dāng)函數(shù)被調(diào)用時(shí),參數(shù)值被拷貝
- 在函數(shù)體內(nèi),被拷貝的參數(shù)修改
- 函數(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)型。