Swift Tour Learn (六) -- Swift 語(yǔ)法(類(lèi)和結(jié)構(gòu)體、屬性、方法)

Swift語(yǔ)法基礎(chǔ)(五)-- (類(lèi)和結(jié)構(gòu)體、屬性、方法)

本章將會(huì)介紹

類(lèi)和結(jié)構(gòu)體對(duì)比
結(jié)構(gòu)體和枚舉是值類(lèi)型
類(lèi)是引用類(lèi)型
類(lèi)和結(jié)構(gòu)體的選擇
字符串、數(shù)組和字典類(lèi)型的賦值和復(fù)制行為
存儲(chǔ)屬性
計(jì)算屬性
屬性觀察器
全局變量和局部變量
類(lèi)型屬性
實(shí)例方法
類(lèi)型方法

類(lèi)和結(jié)構(gòu)體

類(lèi)和結(jié)構(gòu)體是人們構(gòu)建代碼所用的一種通用且靈活的構(gòu)造體。我們可以使用完全相同的語(yǔ)法規(guī)則來(lái)為類(lèi)和結(jié)構(gòu)體定義屬性(常量、變量)和添加方法,從而擴(kuò)展類(lèi)和結(jié)構(gòu)體的功能。

與其他編程語(yǔ)言所不同的是,Swift 并不要求你為自定義類(lèi)和結(jié)構(gòu)體去創(chuàng)建獨(dú)立的接口和實(shí)現(xiàn)文件。你所要做的是在一個(gè)單一文件中定義一個(gè)類(lèi)或者結(jié)構(gòu)體,系統(tǒng)將會(huì)自動(dòng)生成面向其它代碼的外部接口。

注意
通常一個(gè)類(lèi)的實(shí)例被稱(chēng)為對(duì)象。然而在 Swift 中,類(lèi)和結(jié)構(gòu)體的關(guān)系要比在其他語(yǔ)言中更加的密切,大部分功能都可以用在類(lèi)和結(jié)構(gòu)體上。因此,我們會(huì)主要使用實(shí)例。

1.類(lèi)和結(jié)構(gòu)體對(duì)比

Swift 中類(lèi)和結(jié)構(gòu)體有很多共同點(diǎn)。共同處在于:

  • 定義屬性用于存儲(chǔ)值
  • 定義方法用于提供功能
  • 定義下標(biāo)操作使得可以通過(guò)下標(biāo)語(yǔ)法來(lái)訪問(wèn)實(shí)例所包含的值
  • 定義構(gòu)造器用于生成初始化值
  • 通過(guò)擴(kuò)展以增加默認(rèn)實(shí)現(xiàn)的功能
  • 實(shí)現(xiàn)協(xié)議以提供某種標(biāo)準(zhǔn)功能

與結(jié)構(gòu)體相比,類(lèi)還有如下的附加功能:

  • 繼承允許一個(gè)類(lèi)繼承另一個(gè)類(lèi)的特征
  • 類(lèi)型轉(zhuǎn)換允許在運(yùn)行時(shí)檢查和解釋一個(gè)類(lèi)實(shí)例的類(lèi)型
  • 析構(gòu)器允許一個(gè)類(lèi)實(shí)例釋放任何其所被分配的資源
  • 引用計(jì)數(shù)允許對(duì)一個(gè)類(lèi)的多次引用

定義語(yǔ)法
類(lèi)和結(jié)構(gòu)體有著類(lèi)似的定義方式。我們通過(guò)關(guān)鍵字class和struct來(lái)分別表示類(lèi)和結(jié)構(gòu)體,并在一對(duì)大括號(hào)中定義它們的具體內(nèi)容:

class SomeClass {
    // 在這里定義類(lèi)
}
struct SomeStructure {
    // 在這里定義結(jié)構(gòu)體
}

注意
在你每次定義一個(gè)新類(lèi)或者結(jié)構(gòu)體的時(shí)候,實(shí)際上你是定義了一個(gè)新的 Swift 類(lèi)型。因此請(qǐng)使用UpperCamelCase這種方式來(lái)命名(如SomeClass和SomeStructure等),以便符合標(biāo)準(zhǔn) Swift 類(lèi)型的大寫(xiě)命名風(fēng)格(如String,Int和Bool)。相反的,請(qǐng)使用lowerCamelCase這種方式為屬性和方法命名(如frameRate和incrementCount),以便和類(lèi)型名區(qū)分。

以下是定義結(jié)構(gòu)體和定義類(lèi)的示例:

struct Resolution {
    var width = 0
    var height = 0
}
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

在上面的示例中我們定義了一個(gè)名為Resolution的結(jié)構(gòu)體,用來(lái)描述一個(gè)顯示器的像素分辨率。這個(gè)結(jié)構(gòu)體包含了兩個(gè)名為width和height的存儲(chǔ)屬性。存儲(chǔ)屬性是被捆綁和存儲(chǔ)在類(lèi)或結(jié)構(gòu)體中的常量或變量。當(dāng)這兩個(gè)屬性被初始化為整數(shù)0的時(shí)候,它們會(huì)被推斷為Int類(lèi)型。

在上面的示例中我們還定義了一個(gè)名為VideoMode的類(lèi),用來(lái)描述一個(gè)視頻顯示器的特定模式。這個(gè)類(lèi)包含了四個(gè)變量存儲(chǔ)屬性。第一個(gè)是分辨率,它被初始化為一個(gè)新的Resolution結(jié)構(gòu)體的實(shí)例,屬性類(lèi)型被推斷為Resolution。新VideoMode實(shí)例同時(shí)還會(huì)初始化其它三個(gè)屬性,它們分別是,初始值為false的interlaced,初始值為0.0的frameRate,以及值為可選String的name。name屬性會(huì)被自動(dòng)賦予一個(gè)默認(rèn)值nil,意為“沒(méi)有name值”,因?yàn)樗且粋€(gè)可選類(lèi)型。

類(lèi)和結(jié)構(gòu)體實(shí)例
Resolution結(jié)構(gòu)體和VideoMode類(lèi)的定義僅描述了什么是Resolution和VideoMode。它們并沒(méi)有描述一個(gè)特定的分辨率(resolution)或者視頻模式(video mode)。為了描述一個(gè)特定的分辨率或者視頻模式,我們需要生成一個(gè)它們的實(shí)例。

生成結(jié)構(gòu)體和類(lèi)實(shí)例的語(yǔ)法非常相似:

let someResolution = Resolution()
let someVideoMode = VideoMode()

結(jié)構(gòu)體和類(lèi)都使用構(gòu)造器語(yǔ)法來(lái)生成新的實(shí)例。構(gòu)造器語(yǔ)法的最簡(jiǎn)單形式是在結(jié)構(gòu)體或者類(lèi)的類(lèi)型名稱(chēng)后跟隨一對(duì)空括號(hào),如Resolution()或VideoMode()。通過(guò)這種方式所創(chuàng)建的類(lèi)或者結(jié)構(gòu)體實(shí)例,其屬性均會(huì)被初始化為默認(rèn)值。

屬性訪問(wèn)
通過(guò)使用點(diǎn)語(yǔ)法,你可以訪問(wèn)實(shí)例的屬性。其語(yǔ)法規(guī)則是,實(shí)例名后面緊跟屬性名,兩者通過(guò)點(diǎn)號(hào)(.)連接:

print("The width of someResolution is \(someResolution.width)")
// 打印 "The width of someResolution is 0"

在上面的例子中,someResolution.width引用someResolution的width屬性,返回width的初始值0。

你也可以訪問(wèn)子屬性,如VideoMode中Resolution屬性的width屬性:

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is 0"

你也可以使用點(diǎn)語(yǔ)法為變量屬性賦值:

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is now 1280"

注意
與 Objective-C 語(yǔ)言不同的是,Swift 允許直接設(shè)置結(jié)構(gòu)體屬性的子屬性。上面的最后一個(gè)例子,就是直接設(shè)置了someVideoMode中resolution屬性的width這個(gè)子屬性,以上操作并不需要重新為整個(gè)resolution屬性設(shè)置新值。

結(jié)構(gòu)體類(lèi)型的成員逐一構(gòu)造器
所有結(jié)構(gòu)體都有一個(gè)自動(dòng)生成的成員逐一構(gòu)造器,用于初始化新結(jié)構(gòu)體實(shí)例中成員的屬性。新實(shí)例中各個(gè)屬性的初始值可以通過(guò)屬性的名稱(chēng)傳遞到成員逐一構(gòu)造器之中:

let vga = Resolution(width:640, height: 480)

與結(jié)構(gòu)體不同,類(lèi)實(shí)例沒(méi)有默認(rèn)的成員逐一構(gòu)造器。

2.結(jié)構(gòu)體和枚舉是值類(lèi)型

值類(lèi)型被賦予給一個(gè)變量、常量或者被傳遞給一個(gè)函數(shù)的時(shí)候,其值會(huì)被拷貝。

在之前的章節(jié)中,我們已經(jīng)大量使用了值類(lèi)型。實(shí)際上,在 Swift 中,所有的基本類(lèi)型:整數(shù)(Integer)、浮點(diǎn)數(shù)(floating-point)、布爾值(Boolean)、字符串(string)、數(shù)組(array)和字典(dictionary),都是值類(lèi)型,并且在底層都是以結(jié)構(gòu)體的形式所實(shí)現(xiàn)。

在 Swift 中,所有的結(jié)構(gòu)體和枚舉類(lèi)型都是值類(lèi)型。這意味著它們的實(shí)例,以及實(shí)例中所包含的任何值類(lèi)型屬性,在代碼中傳遞的時(shí)候都會(huì)被復(fù)制。

請(qǐng)看下面這個(gè)示例,其使用了前一個(gè)示例中的Resolution結(jié)構(gòu)體:

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

在以上示例中,聲明了一個(gè)名為hd的常量,其值為一個(gè)初始化為全高清視頻分辨率(1920 像素寬,1080 像素高)的Resolution實(shí)例。

然后示例中又聲明了一個(gè)名為cinema的變量,并將hd賦值給它。因?yàn)镽esolution是一個(gè)結(jié)構(gòu)體,所以cinema的值其實(shí)是hd的一個(gè)拷貝副本,而不是hd本身。盡管hd和cinema有著相同的寬(width)和高(height),但是在幕后它們是兩個(gè)完全不同的實(shí)例。

下面,為了符合數(shù)碼影院放映的需求(2048 像素寬,1080 像素高),cinema的width屬性需要作如下修改:

cinema.width = 2048

這里,將會(huì)顯示cinema的width屬性確已改為了2048:

print("cinema is now  \(cinema.width) pixels wide")
// 打印 "cinema is now 2048 pixels wide"

然而,初始的hd實(shí)例中width屬性還是1920:

print("hd is still \(hd.width) pixels wide")
// 打印 "hd is still 1920 pixels wide"

在將hd賦予給cinema的時(shí)候,實(shí)際上是將hd中所存儲(chǔ)的值進(jìn)行拷貝,然后將拷貝的數(shù)據(jù)存儲(chǔ)到新的cinema實(shí)例中。結(jié)果就是兩個(gè)完全獨(dú)立的實(shí)例碰巧包含有相同的數(shù)值。由于兩者相互獨(dú)立,因此將cinema的width修改為2048并不會(huì)影響hd中的width的值。

枚舉也遵循相同的行為準(zhǔn)則:

enum CompassPoint {
    case North, South, East, West
}
var currentDirection = CompassPoint.West
let rememberedDirection = currentDirection
currentDirection = .East
if rememberedDirection == .West {
    print("The remembered direction is still .West")
}
// 打印 "The remembered direction is still .West"

上例中rememberedDirection被賦予了currentDirection的值,實(shí)際上它被賦予的是值的一個(gè)拷貝。賦值過(guò)程結(jié)束后再修改currentDirection的值并不影響rememberedDirection所儲(chǔ)存的原始值的拷貝。

3.類(lèi)是引用類(lèi)型

與值類(lèi)型不同,引用類(lèi)型在被賦予到一個(gè)變量、常量或者被傳遞到一個(gè)函數(shù)時(shí),其值不會(huì)被拷貝。因此,引用的是已存在的實(shí)例本身而不是其拷貝。

請(qǐng)看下面這個(gè)示例,其使用了之前定義的VideoMode類(lèi):

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

以上示例中,聲明了一個(gè)名為tenEighty的常量,其引用了一個(gè)VideoMode類(lèi)的新實(shí)例。在之前的示例中,這個(gè)視頻模式(video mode)被賦予了HD分辨率(1920*1080)的一個(gè)拷貝(即hd實(shí)例)。同時(shí)設(shè)置為interlaced,命名為“1080i”。最后,其幀率是25.0幀每秒。

然后,tenEighty被賦予名為alsoTenEighty的新常量,同時(shí)對(duì)alsoTenEighty的幀率進(jìn)行修改:

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

因?yàn)轭?lèi)是引用類(lèi)型,所以tenEight和alsoTenEight實(shí)際上引用的是相同的VideoMode實(shí)例。換句話(huà)說(shuō),它們是同一個(gè)實(shí)例的兩種叫法。

下面,通過(guò)查看tenEighty的frameRate屬性,我們會(huì)發(fā)現(xiàn)它正確的顯示了所引用的VideoMode實(shí)例的新幀率,其值為30.0:

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 打印 "The frameRate property of theEighty is now 30.0"

需要注意的是tenEighty和alsoTenEighty被聲明為常量而不是變量。然而你依然可以改變tenEighty.frameRate和alsoTenEighty.frameRate,因?yàn)閠enEighty和alsoTenEighty這兩個(gè)常量的值并未改變。它們并不“存儲(chǔ)”這個(gè)VideoMode實(shí)例,而僅僅是對(duì)VideoMode實(shí)例的引用。所以,改變的是被引用的VideoMode的frameRate屬性,而不是引用VideoMode的常量的值。

  • 恒等運(yùn)算符

因?yàn)轭?lèi)是引用類(lèi)型,有可能有多個(gè)常量和變量在幕后同時(shí)引用同一個(gè)類(lèi)實(shí)例。(對(duì)于結(jié)構(gòu)體和枚舉來(lái)說(shuō),這并不成立。因?yàn)樗鼈冏鳛橹殿?lèi)型,在被賦予到常量、變量或者傳遞到函數(shù)時(shí),其值總是會(huì)被拷貝。)

如果能夠判定兩個(gè)常量或者變量是否引用同一個(gè)類(lèi)實(shí)例將會(huì)很有幫助。為了達(dá)到這個(gè)目的,Swift 內(nèi)建了兩個(gè)恒等運(yùn)算符:

 等價(jià)于(===)
 不等價(jià)于(!==)

運(yùn)用這兩個(gè)運(yùn)算符檢測(cè)兩個(gè)常量或者變量是否引用同一個(gè)實(shí)例:

if tenEighty === alsoTenEighty {
    print("tenEighty and alsoTenEighty refer to the same Resolution instance.")
}
//打印 "tenEighty and alsoTenEighty refer to the same Resolution instance."

請(qǐng)注意,“等價(jià)于”(用三個(gè)等號(hào)表示,===)與“等于”(用兩個(gè)等號(hào)表示,==)的不同:

“等價(jià)于”表示兩個(gè)類(lèi)類(lèi)型(class type)的常量或者變量引用同一個(gè)類(lèi)實(shí)例。
“等于”表示兩個(gè)實(shí)例的值“相等”或“相同”,判定時(shí)要遵照設(shè)計(jì)者定義的評(píng)判標(biāo)準(zhǔn),因此相對(duì)于“相等”來(lái)說(shuō),這是一種更加合適的叫法。

當(dāng)你在定義你的自定義類(lèi)和結(jié)構(gòu)體的時(shí)候,你有義務(wù)來(lái)決定判定兩個(gè)實(shí)例“相等”的標(biāo)準(zhǔn)。

  • 指針

如果你有 C,C++ 或者 Objective-C 語(yǔ)言的經(jīng)驗(yàn),那么你也許會(huì)知道這些語(yǔ)言使用指針來(lái)引用內(nèi)存中的地址。一個(gè)引用某個(gè)引用類(lèi)型實(shí)例的 Swift 常量或者變量,與 C 語(yǔ)言中的指針類(lèi)似,但是并不直接指向某個(gè)內(nèi)存地址,也不要求你使用星號(hào)(*)來(lái)表明你在創(chuàng)建一個(gè)引用。Swift 中的這些引用與其它的常量或變量的定義方式相同。

4.類(lèi)和結(jié)構(gòu)體的選擇

在你的代碼中,你可以使用類(lèi)和結(jié)構(gòu)體來(lái)定義你的自定義數(shù)據(jù)類(lèi)型。

然而,結(jié)構(gòu)體實(shí)例總是通過(guò)值傳遞,類(lèi)實(shí)例總是通過(guò)引用傳遞。這意味兩者適用不同的任務(wù)。當(dāng)你在考慮一個(gè)工程項(xiàng)目的數(shù)據(jù)結(jié)構(gòu)和功能的時(shí)候,你需要決定每個(gè)數(shù)據(jù)結(jié)構(gòu)是定義成類(lèi)還是結(jié)構(gòu)體。

按照通用的準(zhǔn)則,當(dāng)符合一條或多條以下條件時(shí),請(qǐng)考慮構(gòu)建結(jié)構(gòu)體:

  • 該數(shù)據(jù)結(jié)構(gòu)的主要目的是用來(lái)封裝少量相關(guān)簡(jiǎn)單數(shù)據(jù)值。
  • 有理由預(yù)計(jì)該數(shù)據(jù)結(jié)構(gòu)的實(shí)例在被賦值或傳遞時(shí),封裝的數(shù)據(jù)將會(huì)被拷貝而不是被引用。
  • 該數(shù)據(jù)結(jié)構(gòu)中儲(chǔ)存的值類(lèi)型屬性,也應(yīng)該被拷貝,而不是被引用。
  • 該數(shù)據(jù)結(jié)構(gòu)不需要去繼承另一個(gè)既有類(lèi)型的屬性或者行為。

舉例來(lái)說(shuō),以下情境中適合使用結(jié)構(gòu)體:

  • 幾何形狀的大小,封裝一個(gè)width屬性和height屬性,兩者均為Double類(lèi)型。
  • 一定范圍內(nèi)的路徑,封裝一個(gè)start屬性和length屬性,兩者均為Int類(lèi)型。
  • 三維坐標(biāo)系內(nèi)一點(diǎn),封裝x,y和z屬性,三者均為Double類(lèi)型。

在所有其它案例中,定義一個(gè)類(lèi),生成一個(gè)它的實(shí)例,并通過(guò)引用來(lái)管理和傳遞。實(shí)際中,這意味著絕大部分的自定義數(shù)據(jù)構(gòu)造都應(yīng)該是類(lèi),而非結(jié)構(gòu)體。

5.字符串、數(shù)組、和字典類(lèi)型的賦值與復(fù)制行為

Swift 中,許多基本類(lèi)型,諸如String,Array和Dictionary類(lèi)型均以結(jié)構(gòu)體的形式實(shí)現(xiàn)。這意味著被賦值給新的常量或變量,或者被傳入函數(shù)或方法中時(shí),它們的值會(huì)被拷貝。

Objective-C 中NSString,NSArray和NSDictionary類(lèi)型均以類(lèi)的形式實(shí)現(xiàn),而并非結(jié)構(gòu)體。它們?cè)诒毁x值或者被傳入函數(shù)或方法時(shí),不會(huì)發(fā)生值拷貝,而是傳遞現(xiàn)有實(shí)例的引用。

注意
以上是對(duì)字符串、數(shù)組、字典的“拷貝”行為的描述。在你的代碼中,拷貝行為看起來(lái)似乎總會(huì)發(fā)生。然而,Swift 在幕后只在絕對(duì)必要時(shí)才執(zhí)行實(shí)際的拷貝。Swift 管理所有的值拷貝以確保性能最優(yōu)化,所以你沒(méi)必要去回避賦值來(lái)保證性能最優(yōu)化。

6.類(lèi)和結(jié)構(gòu)體總結(jié)
// 類(lèi)和結(jié)構(gòu)體

// 定義
struct Resolution {
    var width = 0
    var height = 0
}

class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

// 實(shí)例
let someResolution = Resolution()
let someVideoMode = VideoMode()

// 屬性訪問(wèn)
someResolution.width
// 訪問(wèn)子屬性
someVideoMode.resolution.width
// 為變量屬性賦值
someVideoMode.resolution.width = 1280
someVideoMode.resolution.width

let vga = Resolution(width: 640, height: 480)
vga.width

// 結(jié)構(gòu)體是值類(lèi)型
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
hd.width

// 類(lèi)是引用類(lèi)型
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

let alsoTenEight = tenEighty
alsoTenEight.frameRate = 30.0
tenEighty.frameRate

// 恒等運(yùn)算符 用來(lái)判斷兩個(gè)常量或者變量是否引用同一個(gè)類(lèi)實(shí)例
if alsoTenEight === tenEighty {
    print("恒等的")
}


屬性 (Properties)

屬性將值跟特定的類(lèi)、結(jié)構(gòu)或枚舉關(guān)聯(lián)。存儲(chǔ)屬性存儲(chǔ)常量或變量作為實(shí)例的一部分,而計(jì)算屬性計(jì)算(不是存儲(chǔ))一個(gè)值。計(jì)算屬性可以用于類(lèi)、結(jié)構(gòu)體和枚舉,存儲(chǔ)屬性只能用于類(lèi)和結(jié)構(gòu)體。

存儲(chǔ)屬性和計(jì)算屬性通常與特定類(lèi)型的實(shí)例關(guān)聯(lián)。但是,屬性也可以直接作用于類(lèi)型本身,這種屬性稱(chēng)為類(lèi)型屬性。

另外,還可以定義屬性觀察器來(lái)監(jiān)控屬性值的變化,以此來(lái)觸發(fā)一個(gè)自定義的操作。屬性觀察器可以添加到自己定義的存儲(chǔ)屬性上,也可以添加到從父類(lèi)繼承的屬性上。

1.存儲(chǔ)屬性

簡(jiǎn)單來(lái)說(shuō),一個(gè)存儲(chǔ)屬性就是存儲(chǔ)在特定類(lèi)或結(jié)構(gòu)體實(shí)例里的一個(gè)常量或變量。存儲(chǔ)屬性可以是變量存儲(chǔ)屬性(用關(guān)鍵字 var 定義),也可以是常量存儲(chǔ)屬性(用關(guān)鍵字 let 定義)。

可以在定義存儲(chǔ)屬性的時(shí)候指定默認(rèn)值。也可以在構(gòu)造過(guò)程中設(shè)置或修改存儲(chǔ)屬性的值,甚至修改常量存儲(chǔ)屬性的值。

下面的例子定義了一個(gè)名為 FixedLengthRange 的結(jié)構(gòu)體,該結(jié)構(gòu)體用于描述整數(shù)的范圍,且這個(gè)范圍值在被創(chuàng)建后不能被修改.

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 該區(qū)間表示整數(shù)0,1,2
rangeOfThreeItems.firstValue = 6
// 該區(qū)間現(xiàn)在表示整數(shù)6,7,8

FixedLengthRange 的實(shí)例包含一個(gè)名為 firstValue 的變量存儲(chǔ)屬性和一個(gè)名為 length 的常量存儲(chǔ)屬性。在上面的例子中,length 在創(chuàng)建實(shí)例的時(shí)候被初始化,因?yàn)樗且粋€(gè)常量存儲(chǔ)屬性,所以之后無(wú)法修改它的值。

  • 常量結(jié)構(gòu)體的存儲(chǔ)屬性

如果創(chuàng)建了一個(gè)結(jié)構(gòu)體的實(shí)例并將其賦值給一個(gè)常量,則無(wú)法修改該實(shí)例的任何屬性,即使有屬性被聲明為變量也不行:

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 該區(qū)間表示整數(shù)0,1,2,3
rangeOfFourItems.firstValue = 6
// 盡管 firstValue 是個(gè)變量屬性,這里還是會(huì)報(bào)錯(cuò)

因?yàn)?rangeOfFourItems 被聲明成了常量(用 let 關(guān)鍵字),即使 firstValue 是一個(gè)變量屬性,也無(wú)法再修改它了。

這種行為是由于結(jié)構(gòu)體(struct)屬于值類(lèi)型。當(dāng)值類(lèi)型的實(shí)例被聲明為常量的時(shí)候,它的所有屬性也就成了常量。

屬于引用類(lèi)型的類(lèi)(class)則不一樣。把一個(gè)引用類(lèi)型的實(shí)例賦給一個(gè)常量后,仍然可以修改該實(shí)例的變量屬性。

  • 延遲存儲(chǔ)屬性

延遲存儲(chǔ)屬性是指當(dāng)?shù)谝淮伪徽{(diào)用的時(shí)候才會(huì)計(jì)算其初始值的屬性。在屬性聲明前使用 lazy 來(lái)標(biāo)示一個(gè)延遲存儲(chǔ)屬性。

注意
必須將延遲存儲(chǔ)屬性聲明成變量(使用 var 關(guān)鍵字),因?yàn)閷傩缘某跏贾悼赡茉趯?shí)例構(gòu)造完成之后才會(huì)得到。而常量屬性在構(gòu)造過(guò)程完成之前必須要有初始值,因此無(wú)法聲明成延遲屬性。

延遲屬性很有用,當(dāng)屬性的值依賴(lài)于在實(shí)例的構(gòu)造過(guò)程結(jié)束后才會(huì)知道影響值的外部因素時(shí),或者當(dāng)獲得屬性的初始值需要復(fù)雜或大量計(jì)算時(shí),可以只在需要的時(shí)候計(jì)算它。

下面的例子使用了延遲存儲(chǔ)屬性來(lái)避免復(fù)雜類(lèi)中不必要的初始化。例子中定義了 DataImporter 和 DataManager 兩個(gè)類(lèi),下面是部分代碼:

class DataImporter {
    /*
    DataImporter 是一個(gè)負(fù)責(zé)將外部文件中的數(shù)據(jù)導(dǎo)入的類(lèi)。
    這個(gè)類(lèi)的初始化會(huì)消耗不少時(shí)間。
    */
    var fileName = "data.txt"
    // 這里會(huì)提供數(shù)據(jù)導(dǎo)入功能
}

class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // 這里會(huì)提供數(shù)據(jù)管理功能
}

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 實(shí)例的 importer 屬性還沒(méi)有被創(chuàng)建

DataManager 類(lèi)包含一個(gè)名為 data 的存儲(chǔ)屬性,初始值是一個(gè)空的字符串(String)數(shù)組。這里沒(méi)有給出全部代碼,只需知道 DataManager 類(lèi)的目的是管理和提供對(duì)這個(gè)字符串?dāng)?shù)組的訪問(wèn)即可。

DataManager 的一個(gè)功能是從文件導(dǎo)入數(shù)據(jù)。該功能由 DataImporter 類(lèi)提供,DataImporter 完成初始化需要消耗不少時(shí)間:因?yàn)樗膶?shí)例在初始化時(shí)可能要打開(kāi)文件,還要讀取文件內(nèi)容到內(nèi)存。

DataManager 管理數(shù)據(jù)時(shí)也可能不從文件中導(dǎo)入數(shù)據(jù)。所以當(dāng) DataManager 的實(shí)例被創(chuàng)建時(shí),沒(méi)必要?jiǎng)?chuàng)建一個(gè) DataImporter 的實(shí)例,更明智的做法是第一次用到 DataImporter 的時(shí)候才去創(chuàng)建它。

由于使用了 lazy ,importer 屬性只有在第一次被訪問(wèn)的時(shí)候才被創(chuàng)建。比如訪問(wèn)它的屬性 fileName 時(shí):

print(manager.importer.fileName)
// DataImporter 實(shí)例的 importer 屬性現(xiàn)在被創(chuàng)建了
// 輸出 "data.txt”

注意
如果一個(gè)被標(biāo)記為 lazy 的屬性在沒(méi)有初始化時(shí)就同時(shí)被多個(gè)線(xiàn)程訪問(wèn),則無(wú)法保證該屬性只會(huì)被初始化一次。

  • 存儲(chǔ)屬性和實(shí)例變量

如果您有過(guò) Objective-C 經(jīng)驗(yàn),應(yīng)該知道 Objective-C 為類(lèi)實(shí)例存儲(chǔ)值和引用提供兩種方法。除了屬性之外,還可以使用實(shí)例變量作為屬性值的后端存儲(chǔ)。

Swift 編程語(yǔ)言中把這些理論統(tǒng)一用屬性來(lái)實(shí)現(xiàn)。Swift 中的屬性沒(méi)有對(duì)應(yīng)的實(shí)例變量,屬性的后端存儲(chǔ)也無(wú)法直接訪問(wèn)。這就避免了不同場(chǎng)景下訪問(wèn)方式的困擾,同時(shí)也將屬性的定義簡(jiǎn)化成一個(gè)語(yǔ)句。屬性的全部信息——包括命名、類(lèi)型和內(nèi)存管理特征——都在唯一一個(gè)地方(類(lèi)型定義中)定義。

2.計(jì)算屬性

除存儲(chǔ)屬性外,類(lèi)、結(jié)構(gòu)體和枚舉可以定義計(jì)算屬性。計(jì)算屬性不直接存儲(chǔ)值,而是提供一個(gè) getter 和一個(gè)可選的 setter,來(lái)間接獲取和設(shè)置其他屬性或變量的值。

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
    size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// 打印 "square.origin is now at (10.0, 10.0)”

這個(gè)例子定義了 3 個(gè)結(jié)構(gòu)體來(lái)描述幾何形狀:

Point 封裝了一個(gè) (x, y) 的坐標(biāo)
Size 封裝了一個(gè) width 和一個(gè) height
Rect 表示一個(gè)有原點(diǎn)和尺寸的矩形

Rect也提供了一個(gè)名為center 的計(jì)算屬性。一個(gè)矩形的中心點(diǎn)可以從原點(diǎn)(origin)和大小(size)算出,所以不需要將它以顯式聲明的 Point 來(lái)保存。Rect 的計(jì)算屬性 center 提供了自定義的 getter 和 setter 來(lái)獲取和設(shè)置矩形的中心點(diǎn),就像它有一個(gè)存儲(chǔ)屬性一樣。

上述例子中創(chuàng)建了一個(gè)名為 square 的 Rect 實(shí)例,初始值原點(diǎn)是 (0, 0),寬度高度都是 10。如下圖中藍(lán)色正方形所示。

square 的 center 屬性可以通過(guò)點(diǎn)運(yùn)算符(square.center)來(lái)訪問(wèn),這會(huì)調(diào)用該屬性的 getter 來(lái)獲取它的值。跟直接返回已經(jīng)存在的值不同,getter 實(shí)際上通過(guò)計(jì)算然后返回一個(gè)新的 Point 來(lái)表示 square 的中心點(diǎn)。如代碼所示,它正確返回了中心點(diǎn) (5, 5)。

center 屬性之后被設(shè)置了一個(gè)新的值 (15, 15),表示向右上方移動(dòng)正方形到如下圖橙色正方形所示的位置。設(shè)置屬性center的值會(huì)調(diào)用它的 setter 來(lái)修改屬性 origin 的 x 和 y 的值,從而實(shí)現(xiàn)移動(dòng)正方形到新的位置。


  • 簡(jiǎn)化 setter 聲明

如果計(jì)算屬性的 setter 沒(méi)有定義表示新值的參數(shù)名,則可以使用默認(rèn)名稱(chēng) newValue。下面是使用了簡(jiǎn)化 setter 聲明的 Rect 結(jié)構(gòu)體代碼:

struct AlternativeRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}
  • 只讀計(jì)算屬性

只有 getter 沒(méi)有 setter 的計(jì)算屬性就是只讀計(jì)算屬性。只讀計(jì)算屬性總是返回一個(gè)值,可以通過(guò)點(diǎn)運(yùn)算符訪問(wèn),但不能設(shè)置新的值。

注意
必須使用 var 關(guān)鍵字定義計(jì)算屬性,包括只讀計(jì)算屬性,因?yàn)樗鼈兊闹挡皇枪潭ǖ摹et 關(guān)鍵字只用來(lái)聲明常量屬性,表示初始化后再也無(wú)法修改的值。

只讀計(jì)算屬性的聲明可以去掉 get 關(guān)鍵字和花括號(hào):

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// 打印 "the volume of fourByFiveByTwo is 40.0"

這個(gè)例子定義了一個(gè)名為 Cuboid 的結(jié)構(gòu)體,表示三維空間的立方體,包含 width、height 和 depth 屬性。結(jié)構(gòu)體還有一個(gè)名為 volume 的只讀計(jì)算屬性用來(lái)返回立方體的體積。為 volume 提供 setter 毫無(wú)意義,因?yàn)闊o(wú)法確定如何修改 width、height 和 depth 三者的值來(lái)匹配新的 volume。然而,Cuboid 提供一個(gè)只讀計(jì)算屬性來(lái)讓外部用戶(hù)直接獲取體積是很有用的。

3.屬性觀察器

屬性觀察器監(jiān)控和響應(yīng)屬性值的變化,每次屬性被設(shè)置值的時(shí)候都會(huì)調(diào)用屬性觀察器,即使新值和當(dāng)前值相同的時(shí)候也不例外。

可以為除了延遲存儲(chǔ)屬性之外的其他存儲(chǔ)屬性添加屬性觀察器,也可以通過(guò)重寫(xiě)屬性的方式為繼承的屬性(包括存儲(chǔ)屬性和計(jì)算屬性)添加屬性觀察器。你不必為非重寫(xiě)的計(jì)算屬性添加屬性觀察器,因?yàn)榭梢酝ㄟ^(guò)它的 setter 直接監(jiān)控和響應(yīng)值的變化。

可以為屬性添加如下的一個(gè)或全部觀察器:

  • willSet 在新的值被設(shè)置之前調(diào)用
  • didSet 在新的值被設(shè)置之后立即調(diào)用

willSet 觀察器會(huì)將新的屬性值作為常量參數(shù)傳入,在 willSet 的實(shí)現(xiàn)代碼中可以為這個(gè)參數(shù)指定一個(gè)名稱(chēng),如果不指定則參數(shù)仍然可用,這時(shí)使用默認(rèn)名稱(chēng) newValue 表示。

同樣,didSet 觀察器會(huì)將舊的屬性值作為參數(shù)傳入,可以為該參數(shù)命名或者使用默認(rèn)參數(shù)名 oldValue。如果在 didSet 方法中再次對(duì)該屬性賦值,那么新值會(huì)覆蓋舊的值。

注意
父類(lèi)的屬性在子類(lèi)的構(gòu)造器中被賦值時(shí),它在父類(lèi)中的 willSet 和 didSet 觀察器會(huì)被調(diào)用,隨后才會(huì)調(diào)用子類(lèi)的觀察器。在父類(lèi)初始化方法調(diào)用之前,子類(lèi)給屬性賦值時(shí),觀察器不會(huì)被調(diào)用。

下面是一個(gè) willSet 和 didSet 實(shí)際運(yùn)用的例子,其中定義了一個(gè)名為 StepCounter 的類(lèi),用來(lái)統(tǒng)計(jì)一個(gè)人步行時(shí)的總步數(shù)。這個(gè)類(lèi)可以跟計(jì)步器或其他日常鍛煉的統(tǒng)計(jì)裝置的輸入數(shù)據(jù)配合使用。

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

StepCounter 類(lèi)定義了一個(gè) Int 類(lèi)型的屬性 totalSteps,它是一個(gè)存儲(chǔ)屬性,包含 willSet 和 didSet 觀察器。

當(dāng) totalSteps 被設(shè)置新值的時(shí)候,它的 willSet 和 didSet 觀察器都會(huì)被調(diào)用,即使新值和當(dāng)前值完全相同時(shí)也會(huì)被調(diào)用。

例子中的 willSet 觀察器將表示新值的參數(shù)自定義為 newTotalSteps,這個(gè)觀察器只是簡(jiǎn)單的將新的值輸出。

didSet 觀察器在 totalSteps 的值改變后被調(diào)用,它把新值和舊值進(jìn)行對(duì)比,如果總步數(shù)增加了,就輸出一個(gè)消息表示增加了多少步。didSet 沒(méi)有為舊值提供自定義名稱(chēng),所以默認(rèn)值 oldValue 表示舊值的參數(shù)名。

注意
如果將屬性通過(guò) in-out 方式傳入函數(shù),willSet 和 didSet 也會(huì)調(diào)用。這是因?yàn)?in-out 參數(shù)采用了拷入拷出模式:即在函數(shù)內(nèi)部使用的是參數(shù)的 copy,函數(shù)結(jié)束后,又對(duì)參數(shù)重新賦值。

4.全局變量和局部變量

計(jì)算屬性和屬性觀察器所描述的功能也可以用于全局變量和局部變量。全局變量是在函數(shù)、方法、閉包或任何類(lèi)型之外定義的變量。局部變量是在函數(shù)、方法或閉包內(nèi)部定義的變量。

前面章節(jié)提到的全局或局部變量都屬于存儲(chǔ)型變量,跟存儲(chǔ)屬性類(lèi)似,它為特定類(lèi)型的值提供存儲(chǔ)空間,并允許讀取和寫(xiě)入。

另外,在全局或局部范圍都可以定義計(jì)算型變量和為存儲(chǔ)型變量定義觀察器。計(jì)算型變量跟計(jì)算屬性一樣,返回一個(gè)計(jì)算結(jié)果而不是存儲(chǔ)值,聲明格式也完全一樣。

注意
全局的常量或變量都是延遲計(jì)算的,跟延遲存儲(chǔ)屬性相似,不同的地方在于,全局的常量或變量不需要標(biāo)記lazy修飾符。
局部范圍的常量或變量從不延遲計(jì)算。

5.類(lèi)型屬性

實(shí)例屬性屬于一個(gè)特定類(lèi)型的實(shí)例,每創(chuàng)建一個(gè)實(shí)例,實(shí)例都擁有屬于自己的一套屬性值,實(shí)例之間的屬性相互獨(dú)立。

也可以為類(lèi)型本身定義屬性,無(wú)論創(chuàng)建了多少個(gè)該類(lèi)型的實(shí)例,這些屬性都只有唯一一份。這種屬性就是類(lèi)型屬性。

類(lèi)型屬性用于定義某個(gè)類(lèi)型所有實(shí)例共享的數(shù)據(jù),比如所有實(shí)例都能用的一個(gè)常量(就像 C 語(yǔ)言中的靜態(tài)常量),或者所有實(shí)例都能訪問(wèn)的一個(gè)變量(就像 C 語(yǔ)言中的靜態(tài)變量)。

存儲(chǔ)型類(lèi)型屬性可以是變量或常量,計(jì)算型類(lèi)型屬性跟實(shí)例的計(jì)算型屬性一樣只能定義成變量屬性。

注意
跟實(shí)例的存儲(chǔ)型屬性不同,必須給存儲(chǔ)型類(lèi)型屬性指定默認(rèn)值,因?yàn)轭?lèi)型本身沒(méi)有構(gòu)造器,也就無(wú)法在初始化過(guò)程中使用構(gòu)造器給類(lèi)型屬性賦值。
存儲(chǔ)型類(lèi)型屬性是延遲初始化的,它們只有在第一次被訪問(wèn)的時(shí)候才會(huì)被初始化。即使它們被多個(gè)線(xiàn)程同時(shí)訪問(wèn),系統(tǒng)也保證只會(huì)對(duì)其進(jìn)行一次初始化,并且不需要對(duì)其使用 lazy 修飾符。

  • 類(lèi)型屬性的語(yǔ)法

在 C 或 Objective-C 中,與某個(gè)類(lèi)型關(guān)聯(lián)的靜態(tài)常量和靜態(tài)變量,是作為全局(global)靜態(tài)變量定義的。但是在 Swift 中,類(lèi)型屬性是作為類(lèi)型定義的一部分寫(xiě)在類(lèi)型最外層的花括號(hào)內(nèi),因此它的作用范圍也就在類(lèi)型支持的范圍內(nèi)。

使用關(guān)鍵字 static 來(lái)定義類(lèi)型屬性。在為類(lèi)定義計(jì)算型類(lèi)型屬性時(shí),可以改用關(guān)鍵字 class 來(lái)支持子類(lèi)對(duì)父類(lèi)的實(shí)現(xiàn)進(jìn)行重寫(xiě)。下面的例子演示了存儲(chǔ)型和計(jì)算型類(lèi)型屬性的語(yǔ)法:

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 6
    }
}
class SomeClass {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 27
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}
  • 獲取和設(shè)置類(lèi)型屬性的值

跟實(shí)例屬性一樣,類(lèi)型屬性也是通過(guò)點(diǎn)運(yùn)算符來(lái)訪問(wèn)。但是,類(lèi)型屬性是通過(guò)類(lèi)型本身來(lái)訪問(wèn),而不是通過(guò)實(shí)例。比如:

print(SomeStructure.storedTypeProperty)
// 打印 "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 打印 "Another value.”
print(SomeEnumeration.computedTypeProperty)
// 打印 "6"
print(SomeClass.computedTypeProperty)
// 打印 "27"

下面的例子定義了一個(gè)結(jié)構(gòu)體,使用兩個(gè)存儲(chǔ)型類(lèi)型屬性來(lái)表示兩個(gè)聲道的音量,每個(gè)聲道具有 0 到 10 之間的整數(shù)音量。

下圖展示了如何把兩個(gè)聲道結(jié)合來(lái)模擬立體聲的音量。當(dāng)聲道的音量是 0,沒(méi)有一個(gè)燈會(huì)亮;當(dāng)聲道的音量是 10,所有燈點(diǎn)亮。本圖中,左聲道的音量是 9,右聲道的音量是 7:



上面所描述的聲道模型使用 AudioChannel 結(jié)構(gòu)體的實(shí)例來(lái)表示:

struct AudioChannel {
    static let thresholdLevel = 10
    static var maxInputLevelForAllChannels = 0
    var currentLevel: Int = 0 {
        didSet {
            if currentLevel > AudioChannel.thresholdLevel {
                // 將當(dāng)前音量限制在閾值之內(nèi)
                currentLevel = AudioChannel.thresholdLevel
            }
            if currentLevel > AudioChannel.maxInputLevelForAllChannels {
                // 存儲(chǔ)當(dāng)前音量作為新的最大輸入音量
                AudioChannel.maxInputLevelForAllChannels = currentLevel
            }
        }
    }
}

結(jié)構(gòu) AudioChannel 定義了 2 個(gè)存儲(chǔ)型類(lèi)型屬性來(lái)實(shí)現(xiàn)上述功能。第一個(gè)是 thresholdLevel,表示音量的最大上限閾值,它是一個(gè)值為 10 的常量,對(duì)所有實(shí)例都可見(jiàn),如果音量高于 10,則取最大上限值 10(見(jiàn)后面描述)。

第二個(gè)類(lèi)型屬性是變量存儲(chǔ)型屬性 maxInputLevelForAllChannels,它用來(lái)表示所有 AudioChannel 實(shí)例的最大音量,初始值是0。

AudioChannel 也定義了一個(gè)名為 currentLevel 的存儲(chǔ)型實(shí)例屬性,表示當(dāng)前聲道現(xiàn)在的音量,取值為 0 到 10。

屬性 currentLevel 包含 didSet 屬性觀察器來(lái)檢查每次設(shè)置后的屬性值,它做如下兩個(gè)檢查:

  • 如果 currentLevel 的新值大于允許的閾值 thresholdLevel,屬性觀察器將 currentLevel 的值限定為閾值 thresholdLevel。
  • 如果修正后的 currentLevel 值大于靜態(tài)類(lèi)型屬性 maxInputLevelForAllChannels 的值,屬性觀察器就將新值保存在 maxInputLevelForAllChannels 中。

注意
在第一個(gè)檢查過(guò)程中,didSet 屬性觀察器將 currentLevel 設(shè)置成了不同的值,但這不會(huì)造成屬性觀察器被再次調(diào)用。

可以使用結(jié)構(gòu)體 AudioChannel 創(chuàng)建兩個(gè)聲道 leftChannel 和 rightChannel,用以表示立體聲系統(tǒng)的音量:

var leftChannel = AudioChannel()
var rightChannel = AudioChannel()

如果將左聲道的 currentLevel 設(shè)置成 7,類(lèi)型屬性 maxInputLevelForAllChannels 也會(huì)更新成 7:

leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// 輸出 "7"
print(AudioChannel.maxInputLevelForAllChannels)
// 輸出 "7"

如果試圖將右聲道的 currentLevel 設(shè)置成 11,它會(huì)被修正到最大值 10,同時(shí) maxInputLevelForAllChannels 的值也會(huì)更新到 10:

rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// 輸出 "10"
print(AudioChannel.maxInputLevelForAllChannels)
// 輸出 "10"
6.屬性總結(jié)
屬性

// 存儲(chǔ)屬性
struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// firstValue是變量存儲(chǔ)屬性 因此可以更改
rangeOfThreeItems.firstValue = 6
// length是常量存儲(chǔ)屬性因此不可以再更改
//rangeOfThreeItems.length = 7

// 常量結(jié)構(gòu)體存儲(chǔ)屬性,無(wú)法修改實(shí)例的任何屬性,即使有屬性被聲明為變量也不行
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 不可以修改實(shí)例的任何屬性 因?yàn)槎x的是常量結(jié)構(gòu)體
//rangeOfFourItems.firstValue = 3

// 上邊也從靈位一個(gè)角度證明了結(jié)構(gòu)體是值類(lèi)型

// 延遲存儲(chǔ)屬性 lazy
// 延遲屬性很有用,當(dāng)屬性的值依賴(lài)于在實(shí)例的構(gòu)造過(guò)程結(jié)束后才會(huì)知道影響值的外部因素時(shí),或者當(dāng)獲得屬性的初始值需要復(fù)雜或大量計(jì)算時(shí),可以只在需要的時(shí)候計(jì)算它。
class DataImporter {
    // 導(dǎo)入數(shù)據(jù),存入內(nèi)存,這一系列動(dòng)作需要不少時(shí)間
    var fileName = "data.txt"
}

class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
}
let manager = DataManager()
manager.data.append("Some Data")
manager.data.append("Some More Data")
// 由于使用了 lazy ,importer 屬性只有在第一次被訪問(wèn)的時(shí)候才被創(chuàng)建。比如訪問(wèn)它的屬性 fileName 時(shí):
print(manager.importer.fileName)

// 計(jì)算屬性
struct Point {
    var x = 0.0, y = 0.0
}

struct Size {
    var width = 0.0, height = 0.0
}

struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

var square = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 10.0))
// center屬性計(jì)算屬性,在調(diào)用center屬性的時(shí)候,會(huì)調(diào)用計(jì)算屬性的get方法通過(guò)計(jì)算得出中心點(diǎn)的位置
let initialSquareCenter = square.center
print("square.center is now at (\(initialSquareCenter.x), \(initialSquareCenter.y))")
// 現(xiàn)在修改中心點(diǎn),center屬性通過(guò)set方法,重新計(jì)算原點(diǎn)的位置
square.center = Point(x: 15.0, y: 15.0)
print("origin is now at (\(square.origin.x), \(square.origin.y))")

// 只讀計(jì)算屬性 就是只有g(shù)etter 沒(méi)有 setter 這種計(jì)算屬性不能設(shè)置新的值,只讀計(jì)算屬性的聲明可以去掉get 
struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
       return width * height * depth
    }
}
let fourBtFiveByTwo = Cuboid(width: 4, height: 5, depth: 2)
fourBtFiveByTwo.volume

// 屬性觀察器
class StepCounter {
    var totalSteps: Int = 0 {
        willSet {
            print("About to set totalSteps to \(newValue)")
        }
        didSet {
            if totalSteps > oldValue {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
stepCounter.totalSteps = 360

// 類(lèi)型屬性  多個(gè)實(shí)例公用一套屬性值,勢(shì)力之間的屬性相互獨(dú)立 使用static關(guān)鍵字
struct SomeStructure {
    // 存儲(chǔ)型類(lèi)型屬性
    static var storedTypeProperty = "Some Value."
    // 計(jì)算型類(lèi)型屬性
    static var computedTypeProperty: Int {
        return 1
    }
}

class SomeClass {
    // 存儲(chǔ)型類(lèi)型屬性
    static var storedTypeProperty = "Some Value."
    // 計(jì)算型類(lèi)型屬性
    static var computedTypeProperty: Int {
        return 27
    }
    // 計(jì)算型類(lèi)型屬性 支持子類(lèi)對(duì)父類(lèi)的實(shí)現(xiàn)進(jìn)行重寫(xiě) 改用class
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}
// 獲取和設(shè)置類(lèi)型屬性的值
print(SomeStructure.storedTypeProperty)
SomeStructure.storedTypeProperty = "Another Value"
print(SomeStructure.storedTypeProperty)
print(SomeClass.computedTypeProperty)
print(SomeClass.overrideableComputedTypeProperty)

// 示例
struct AudioChannel {
    static let thresholdLevel = 10
    static var maxInputLevelForAllChannels = 0
    var currentLevel: Int = 0 {
        didSet {
            if currentLevel > AudioChannel.thresholdLevel {
                // 將當(dāng)前音量限制在閾值之內(nèi)
                currentLevel = AudioChannel.thresholdLevel
            }
            if currentLevel > AudioChannel.maxInputLevelForAllChannels {
                // 存儲(chǔ)當(dāng)前音量作為新的最大輸入音量
                AudioChannel.maxInputLevelForAllChannels = currentLevel
            }
        }
    }
}
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
leftChannel.currentLevel = 7
print(leftChannel.currentLevel) // 7
print(AudioChannel.maxInputLevelForAllChannels) // 7

rightChannel.currentLevel = 11
print(rightChannel.currentLevel) // 10
print(AudioChannel.maxInputLevelForAllChannels) // 10

方法(Method)

方法是與某些特定類(lèi)型相關(guān)聯(lián)的函數(shù)。類(lèi)、結(jié)構(gòu)體、枚舉都可以定義實(shí)例方法;實(shí)例方法為給定類(lèi)型的實(shí)例封裝了具體的任務(wù)與功能。類(lèi)、結(jié)構(gòu)體、枚舉也可以定義類(lèi)型方法;類(lèi)型方法與類(lèi)型本身相關(guān)聯(lián)。類(lèi)型方法與 Objective-C 中的類(lèi)方法(class methods)相似。

結(jié)構(gòu)體和枚舉能夠定義方法是 Swift 與 C/Objective-C 的主要區(qū)別之一。在 Objective-C 中,類(lèi)是唯一能定義方法的類(lèi)型。但在 Swift 中,你不僅能選擇是否要定義一個(gè)類(lèi)/結(jié)構(gòu)體/枚舉,還能靈活地在你創(chuàng)建的類(lèi)型(類(lèi)/結(jié)構(gòu)體/枚舉)上定義方法。

1.實(shí)例方法(Instance Methods)

實(shí)例方法是屬于某個(gè)特定類(lèi)、結(jié)構(gòu)體或者枚舉類(lèi)型實(shí)例的方法。實(shí)例方法提供訪問(wèn)和修改實(shí)例屬性的方法或提供與實(shí)例目的相關(guān)的功能,并以此來(lái)支撐實(shí)例的功能。實(shí)例方法的語(yǔ)法與函數(shù)完全一致。

實(shí)例方法要寫(xiě)在它所屬的類(lèi)型的前后大括號(hào)之間。實(shí)例方法能夠隱式訪問(wèn)它所屬類(lèi)型的所有的其他實(shí)例方法和屬性。實(shí)例方法只能被它所屬的類(lèi)的某個(gè)特定實(shí)例調(diào)用。實(shí)例方法不能脫離于現(xiàn)存的實(shí)例而被調(diào)用。

下面的例子,定義一個(gè)很簡(jiǎn)單的Counter類(lèi),Counter能被用來(lái)對(duì)一個(gè)動(dòng)作發(fā)生的次數(shù)進(jìn)行計(jì)數(shù):

class Counter {
    var count = 0
    func increment() {
        count += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}

Counter類(lèi)定義了三個(gè)實(shí)例方法:

  • increment讓計(jì)數(shù)器按一遞增;
  • increment(by: Int)讓計(jì)數(shù)器按一個(gè)指定的整數(shù)值遞增;
  • reset將計(jì)數(shù)器重置為0。

Counter這個(gè)類(lèi)還聲明了一個(gè)可變屬性count,用它來(lái)保持對(duì)當(dāng)前計(jì)數(shù)器值的追蹤。和調(diào)用屬性一樣,用點(diǎn)語(yǔ)法(dot syntax)調(diào)用實(shí)例方法:

let counter = Counter()
// 初始計(jì)數(shù)值是0
counter.increment()
// 計(jì)數(shù)值現(xiàn)在是1
counter.increment(by: 5)
// 計(jì)數(shù)值現(xiàn)在是6
counter.reset()
// 計(jì)數(shù)值現(xiàn)在是0

函數(shù)參數(shù)可以同時(shí)有一個(gè)局部名稱(chēng)(在函數(shù)體內(nèi)部使用)和一個(gè)外部名稱(chēng)(在調(diào)用函數(shù)時(shí)使用)。方法參數(shù)也一樣,因?yàn)榉椒ň褪呛瘮?shù),只是這個(gè)函數(shù)與某個(gè)類(lèi)型相關(guān)聯(lián)了。

  • self屬性

類(lèi)型的每一個(gè)實(shí)例都有一個(gè)隱含屬性叫做self,self完全等同于該實(shí)例本身。你可以在一個(gè)實(shí)例的實(shí)例方法中使用這個(gè)隱含的self屬性來(lái)引用當(dāng)前實(shí)例。

上面例子中的increment方法還可以這樣寫(xiě):

func increment() {
    self.count += 1
}

實(shí)際上,你不必在你的代碼里面經(jīng)常寫(xiě)self。不論何時(shí),只要在一個(gè)方法中使用一個(gè)已知的屬性或者方法名稱(chēng),如果你沒(méi)有明確地寫(xiě)self,Swift 假定你是指當(dāng)前實(shí)例的屬性或者方法。這種假定在上面的Counter中已經(jīng)示范了:Counter中的三個(gè)實(shí)例方法中都使用的是count(而不是self.count)。

使用這條規(guī)則的主要場(chǎng)景是實(shí)例方法的某個(gè)參數(shù)名稱(chēng)與實(shí)例的某個(gè)屬性名稱(chēng)相同的時(shí)候。在這種情況下,參數(shù)名稱(chēng)享有優(yōu)先權(quán),并且在引用屬性時(shí)必須使用一種更嚴(yán)格的方式。這時(shí)你可以使用self屬性來(lái)區(qū)分參數(shù)名稱(chēng)和屬性名稱(chēng)。

下面的例子中,self消除方法參數(shù)x和實(shí)例屬性x之間的歧義:

struct Point {
    var x = 0.0, y = 0.0
    func isToTheRightOfX(x: Double) -> Bool {
        return self.x > x
    }
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOfX(1.0) {
    print("This point is to the right of the line where x == 1.0")
}
// 打印 "This point is to the right of the line where x == 1.0"

如果不使用self前綴,Swift 就認(rèn)為兩次使用的x都指的是名稱(chēng)為x的函數(shù)參數(shù)。

  • 在實(shí)例方法中修改值類(lèi)型

結(jié)構(gòu)體和枚舉是值類(lèi)型。默認(rèn)情況下,值類(lèi)型的屬性不能在它的實(shí)例方法中被修改。

但是,如果你確實(shí)需要在某個(gè)特定的方法中修改結(jié)構(gòu)體或者枚舉的屬性,你可以為這個(gè)方法選擇可變(mutating)行為,然后就可以從其方法內(nèi)部改變它的屬性;并且這個(gè)方法做的任何改變都會(huì)在方法執(zhí)行結(jié)束時(shí)寫(xiě)回到原始結(jié)構(gòu)中。方法還可以給它隱含的self屬性賦予一個(gè)全新的實(shí)例,這個(gè)新實(shí)例在方法結(jié)束時(shí)會(huì)替換現(xiàn)存實(shí)例。

要使用可變方法,將關(guān)鍵字mutating 放到方法的func關(guān)鍵字之前就可以了:

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveByX(deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// 打印 "The point is now at (3.0, 4.0)"

上面的Point結(jié)構(gòu)體定義了一個(gè)可變方法 moveByX(_:y:) 來(lái)移動(dòng)Point實(shí)例到給定的位置。該方法被調(diào)用時(shí)修改了這個(gè)點(diǎn),而不是返回一個(gè)新的點(diǎn)。方法定義時(shí)加上了mutating關(guān)鍵字,從而允許修改屬性。

注意,不能在結(jié)構(gòu)體類(lèi)型的常量(a constant of structure type)上調(diào)用可變方法,因?yàn)槠鋵傩圆荒鼙桓淖儯词箤傩允亲兞繉傩浴?/p>

  • 在可變方法中給 self 賦值

可變方法能夠賦給隱含屬性self一個(gè)全新的實(shí)例。上面Point的例子可以用下面的方式改寫(xiě):

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY)
    }
}

枚舉的可變方法可以把self設(shè)置為同一枚舉類(lèi)型中不同的成員:

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 現(xiàn)在等于 .High
ovenLight.next()
// ovenLight 現(xiàn)在等于 .Off
2.類(lèi)型方法

實(shí)例方法是被某個(gè)類(lèi)型的實(shí)例調(diào)用的方法。你也可以定義在類(lèi)型本身上調(diào)用的方法,這種方法就叫做類(lèi)型方法。在方法的func關(guān)鍵字之前加上關(guān)鍵字static,來(lái)指定類(lèi)型方法。類(lèi)還可以用關(guān)鍵字class來(lái)允許子類(lèi)重寫(xiě)父類(lèi)的方法實(shí)現(xiàn)。

注意
在 Objective-C 中,你只能為 Objective-C 的類(lèi)類(lèi)型(classes)定義類(lèi)型方法(type-level methods)。在 Swift 中,你可以為所有的類(lèi)、結(jié)構(gòu)體和枚舉定義類(lèi)型方法。每一個(gè)類(lèi)型方法都被它所支持的類(lèi)型顯式包含。

類(lèi)型方法和實(shí)例方法一樣用點(diǎn)語(yǔ)法調(diào)用。但是,你是在類(lèi)型上調(diào)用這個(gè)方法,而不是在實(shí)例上調(diào)用。下面是如何在SomeClass類(lèi)上調(diào)用類(lèi)型方法的例子:

class SomeClass {
    class func someTypeMethod() {
        // 在這里實(shí)現(xiàn)類(lèi)型方法
    }
}
SomeClass.someTypeMethod()

在類(lèi)型方法的方法體(body)中,self指向這個(gè)類(lèi)型本身,而不是類(lèi)型的某個(gè)實(shí)例。這意味著你可以用self來(lái)消除類(lèi)型屬性和類(lèi)型方法參數(shù)之間的歧義(類(lèi)似于我們?cè)谇懊嫣幚韺?shí)例屬性和實(shí)例方法參數(shù)時(shí)做的那樣)。

一般來(lái)說(shuō),在類(lèi)型方法的方法體中,任何未限定的方法和屬性名稱(chēng),可以被本類(lèi)中其他的類(lèi)型方法和類(lèi)型屬性引用。一個(gè)類(lèi)型方法可以直接通過(guò)類(lèi)型方法的名稱(chēng)調(diào)用本類(lèi)中的其它類(lèi)型方法,而無(wú)需在方法名稱(chēng)前面加上類(lèi)型名稱(chēng)。類(lèi)似地,在結(jié)構(gòu)體和枚舉中,也能夠直接通過(guò)類(lèi)型屬性的名稱(chēng)訪問(wèn)本類(lèi)中的類(lèi)型屬性,而不需要前面加上類(lèi)型名稱(chēng)。

下面的例子定義了一個(gè)名為L(zhǎng)evelTracker結(jié)構(gòu)體。它監(jiān)測(cè)玩家的游戲發(fā)展情況(游戲的不同層次或階段)。這是一個(gè)單人游戲,但也可以存儲(chǔ)多個(gè)玩家在同一設(shè)備上的游戲信息。

游戲初始時(shí),所有的游戲等級(jí)(除了等級(jí) 1)都被鎖定。每次有玩家完成一個(gè)等級(jí),這個(gè)等級(jí)就對(duì)這個(gè)設(shè)備上的所有玩家解鎖。LevelTracker結(jié)構(gòu)體用類(lèi)型屬性和方法監(jiān)測(cè)游戲的哪個(gè)等級(jí)已經(jīng)被解鎖。它還監(jiān)測(cè)每個(gè)玩家的當(dāng)前等級(jí)。

struct LevelTracker {
    static var highestUnlockedLevel = 1
    var currentLevel = 1

    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }

    static func isUnlocked(_ level: Int) -> Bool {
        return level <= highestUnlockedLevel
    }

    @discardableResult
    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}

LevelTracker監(jiān)測(cè)玩家已解鎖的最高等級(jí)。這個(gè)值被存儲(chǔ)在類(lèi)型屬性highestUnlockedLevel中。

LevelTracker還定義了兩個(gè)類(lèi)型方法與highestUnlockedLevel配合工作。第一個(gè)類(lèi)型方法是unlock(_:),一旦新等級(jí)被解鎖,它會(huì)更新highestUnlockedLevel的值。第二個(gè)類(lèi)型方法是isUnlocked(_:),如果某個(gè)給定的等級(jí)已經(jīng)被解鎖,它將返回true。(注意,盡管我們沒(méi)有使用類(lèi)似LevelTracker.highestUnlockedLevel的寫(xiě)法,這個(gè)類(lèi)型方法還是能夠訪問(wèn)類(lèi)型屬性highestUnlockedLevel)

除了類(lèi)型屬性和類(lèi)型方法,LevelTracker還監(jiān)測(cè)每個(gè)玩家的進(jìn)度。它用實(shí)例屬性currentLevel來(lái)監(jiān)測(cè)每個(gè)玩家當(dāng)前的等級(jí)。

為了便于管理currentLevel屬性,LevelTracker定義了實(shí)例方法advance(to:)。這個(gè)方法會(huì)在更新currentLevel之前檢查所請(qǐng)求的新等級(jí)是否已經(jīng)解鎖。advance(to:)方法返回布爾值以指示是否能夠設(shè)置currentLevel。因?yàn)樵试S在調(diào)用advance(to:)時(shí)候忽略返回值,不會(huì)產(chǎn)生編譯警告,所以函數(shù)被標(biāo)注為@ discardableResult屬性。

下面,Player類(lèi)使用LevelTracker來(lái)監(jiān)測(cè)和更新每個(gè)玩家的發(fā)展進(jìn)度:

class Player {
    var tracker = LevelTracker()
    let playerName: String
    func complete(level: Int) {
        LevelTracker.unlock(level + 1)
        tracker.advance(to: level + 1)
    }
    init(name: String) {
        playerName = name
    }
}

Player類(lèi)創(chuàng)建一個(gè)新的LevelTracker實(shí)例來(lái)監(jiān)測(cè)這個(gè)用戶(hù)的進(jìn)度。它提供了complete(level:)方法,一旦玩家完成某個(gè)指定等級(jí)就調(diào)用它。這個(gè)方法為所有玩家解鎖下一等級(jí),并且將當(dāng)前玩家的進(jìn)度更新為下一等級(jí)。(我們忽略了advance(to:)返回的布爾值,因?yàn)橹罢{(diào)用LevelTracker.unlock(_:)時(shí)就知道了這個(gè)等級(jí)已經(jīng)被解鎖了)。

你還可以為一個(gè)新的玩家創(chuàng)建一個(gè)Player的實(shí)例,然后看這個(gè)玩家完成等級(jí)一時(shí)發(fā)生了什么:

var player = Player(name: "Argyrios")
player.complete(level: 1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// 打印 "highest unlocked level is now 2"

如果你創(chuàng)建了第二個(gè)玩家,并嘗試讓他開(kāi)始一個(gè)沒(méi)有被任何玩家解鎖的等級(jí),那么試圖設(shè)置玩家當(dāng)前等級(jí)將會(huì)失敗:

player = Player(name: "Beto")
if player.tracker.advance(to: 6) {
    print("player is now on level 6")
} else {
    print("level 6 has not yet been unlocked")
}
// 打印 "level 6 has not yet been unlocked"
3.方法(Methods)總結(jié)
方法

// 實(shí)例方法
class Counter {
    var count = 0

    func increment() {
        count += 1
    }

    func increment(by amount: Int) {
        // by是外部名稱(chēng)(在調(diào)用函數(shù)時(shí)調(diào)用) amount是局部名稱(chēng)(在函數(shù)體內(nèi)部使用)
        count += amount
    }


    func reset() {
        count = 0
    }
}
let counter = Counter()
print(counter.count)
counter.increment()
print(counter.count)
counter.increment(by: 10)
print(counter.count)
counter.reset()
print(counter.count)

// self 屬性 類(lèi)型的每一個(gè)實(shí)例都有一個(gè)隱含屬性叫做self self完全等同于該實(shí)例本身 當(dāng)出現(xiàn)實(shí)例方法的某個(gè)參數(shù)與實(shí)例的某個(gè)屬性名稱(chēng)相同時(shí)使用,避免混淆
struct Point {
    var x = 0.0, y = 0.0
    func isToTheRightOfX(x: Double) -> Bool {
        return self.x > x
    }

    // 在實(shí)例方法中修改值類(lèi)型 使用mutating修飾符
    mutating func moveByX(deltaX: Double, deltaY: Double) {
        x += deltaX
        y += deltaY
    }

    // 在可變方法中給self賦值  與上邊的方法效果一樣
    mutating func moveByX2(deltaX: Double, deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY)
    }
}
var somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOfX(x: 1.0) {
    print("在x==1右邊")
}
somePoint.moveByX(deltaX: 1, deltaY: 2)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
somePoint.moveByX2(deltaX: 2, deltaY: 4)
print("The point is now at (\(somePoint.x), \(somePoint.y))")

// 枚舉類(lèi)型也可以在可變方法中給 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()
print(ovenLight)
ovenLight.next()
print(ovenLight)
ovenLight.next()
print(ovenLight)

// 類(lèi)型方法
struct LevelTracker {
    static var highestUnlockedLevel = 1
    var currentLevel = 1

    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }

    static func isUnlocked(_ level: Int) -> Bool {
        // LevelTracker.highestUnlockedLevel
        return level <= LevelTracker.highestUnlockedLevel
    }

    @discardableResult
    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}

class Player {
    var tracker = LevelTracker()
    let playerName: String
    func complete(level: Int) {
        LevelTracker.unlock(level + 1)
        tracker.advance(to: level + 1)
    }
    init(name: String) {
        playerName = name
    }
}

var player = Player(name: "XiaoMing")
player.complete(level: 1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")

player = Player(name: "XiaoLi")
if player.tracker.advance(to: 6) {
    print("player is now on level 6")
} else {
    print("level 6 has not yet been unlocked")
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容