在實際編程中挣跋,很多時候萍诱,我們都需要使用比Int
悬嗓,String
這類簡單類型更復(fù)雜的類型,例如裕坊,需要兩個Double
表達(dá)的2D
坐標(biāo)位置包竹;需要兩個String
表達(dá)的人的姓和名等等。因此,如果我們的程序里真的可以使用叫做location
或name
的類型周瞎,程序的可讀性就會提高很多苗缩。于是,Swift
允許我們根據(jù)實際的需要声诸,定義自己的類型系統(tǒng)酱讶。并把這種自定義的類型,叫做named types
彼乌。
根據(jù)實際的應(yīng)用場景泻肯,Swift
提供了4種不同的named types
:struct
,class
慰照,enum
和protocol
灶挟。首先,我們來看表達(dá)值類型(value type)的struct
毒租。
如何來理解這個value type
呢稚铣?在前面我們也提到過,有時墅垮,我們需要把沒有任何意義的Double
惕医,近一步抽象成一個點坐標(biāo)的location
。而無論是Double
還是location
算色,它們所表達(dá)的抬伺,都是“某種值”這樣的概念,而這剃允,就是值類型的含義沛简。
定義一個struct
先來看一個例子,我們要計算平面坐標(biāo)里某個點距點A的距離是否小于200斥废,算起來很簡單椒楣,勾股定理就搞定了:
let centerX = 100.0
let centerY = 100.0
let distance = 200.0
func inRange(x: Double, y: Double) -> Bool {
// sqrt(n) pow(x, n)
let distX = x - centerX
let distY = y - centerY
let dist =
sqrt(pow(distX, 2) + pow(distY, 2))
return dist < distance
}
其中sqrt(n)
用來計算n的平方根,pow(x, n)
用來計算x的n次方牡肉,它們都是Swift提供的庫函數(shù)捧灰。定義好inRange
之后,我們就可以像:
inRange(100, y: 500)
inRange(300, y: 800)
來調(diào)用inRange
统锤。但是這樣有一個不足毛俏,當(dāng)我們需要比較很多個點和Center的距離的時候,這些數(shù)字并不能明確告訴我們它們代表的位置的意義饲窿,甚至我們都無法知道它們代表一個數(shù)字煌寇。如果我們可以像這樣來比較位置:
inRange(location1)
inRange(myHome)
相比數(shù)字,它們看上去就會直觀的多逾雄,而這阀溶,就是我們需要自定義struct類型最直接的原因腻脏。
初始化struct
我們可以像這樣定義一個struct類型:
struct StructName {/* struct memberes*/}
struct Location {
let x: Double
var y: Double
}
在我們的例子里,struct members
就是兩個Double
來表示一個位置的xy坐標(biāo)银锻。定義好struct類型之后永品,我們就可以像這樣定義location
變量并訪問和修改location members
:
var pointA = Location(x: 100, y: 200)
pointA.x
pointA.y
pointA.y = 500
有了這些內(nèi)容之后,我們就可以修改之前的inRange函數(shù)了击纬,讓它接受一個Location類型的參數(shù):
func inPointRange(point: Location) -> Bool {
// sqrt(n) pow(x, n)
let distX = point.x - centerX
let distY = point.y - centerY
let dist = sqrt(pow(distX, 2) + pow(distY, 2))
return dist < distance
}
然后鼎姐,我們用來比較位置的代碼,就易懂多了:
inPointRange(pointA)
Struct initializer
除了使用:
var pointA = Location(x: 100, y: 200)
這樣的方式初始化Location
之外更振,我們幾乎可以使用任意一種我們“希望”的方式炕桨,例如:
var pointA = Location("100,200")
為了實現(xiàn)這個功能,我們需要給Location添加自定義的initializer:
struct Location {
let x: Double
var y: Double
// Initializer
init(stringPoint: String) {
// "100,200"
let xy = stringPoint.characters.split(",")
x = atof(String(xy.first!))
y = atof(String(xy.last!))
}
}
這樣殃饿,我們就可以使用特定格式的字符串谋作,初始化Location
了。但是乎芳,這時,我們會看到編譯器告訴我們之前pointA
的定義發(fā)生了一個錯誤:
這是由于struct initializer
創(chuàng)建規(guī)則導(dǎo)致的帖池。
Initialization rules
Memberwise initializer
對于我們一開始的Location
定義:
struct Location {
let x: Double
var y: Double
}
它沒有自定義任何init方法奈惑,Swift就會自動創(chuàng)建一個struct memberwise initializer
。因此睡汹,我們可以逐個成員的去定義一個Location
肴甸。
var pointA = Location(x: 100, y: 200)
“Memberwise只是默認(rèn)為我們提供了一個按成員初始化的方法,它并不會自動為成員設(shè)置默認(rèn)值囚巴≡冢”
特別提示
在我們自定義了init
方法之后,Swift
就不會自動為我們創(chuàng)建memberwise initializer
了彤叉,這也就是編譯器會報錯的原因庶柿,不過我們可以手工打造一個:
struct Location {
let x: Double
var y: Double
// Initializer
init(x: Double, y: Double) {
self.x = x;
self.y = y;
}
init(stringPoint: String) {
// "100,200"
let xy = stringPoint.characters.split(",")
x = atof(String(xy.first!))
y = atof(String(xy.last!))
}
}
我們手工打造的memberwise
版本和String
大同小異。只是秽浇,由于我們在init
的參數(shù)中浮庐,使用了和member
同樣的名字,在init
的實現(xiàn)中柬焕,我們使用了關(guān)鍵字self
來表示要創(chuàng)建的struct
本身审残,來幫助編譯器區(qū)分member x
和參數(shù)x
,這和之前我們用pointA.x
的道理是一樣的斑举。
這里搅轿,有一個小細(xì)節(jié)。之前我們講函數(shù)的時候富玷,說過函數(shù)的第一個參數(shù)是省略outername
的璧坟,但是在init
里既穆,第一個參數(shù)的outername
是不會省略的,我們必須指定每一個參數(shù)的outername
沸柔。
Member default values
如果我們希望給structmember設(shè)置一個默認(rèn)值循衰,而不用每次創(chuàng)建的時候去指定它們,我們可以像下面這樣:
struct Location {
let x = 100.0
var y = 100.0
}
在這里褐澎,如果我們可以明確給成員設(shè)置默認(rèn)值会钝,就可以省掉type annotation
的部分了,type inference
會正確的推導(dǎo)成員的類型工三。
Default initializer
如果我們?yōu)?code>struct的每一個成員都設(shè)置了默認(rèn)值迁酸,并且,我們沒有自定義init
方法俭正,Swift
就會自動為我們生成一個default initializer
奸鬓。有了它,我們就可以這樣創(chuàng)建Location
了:
let center = Location()
“如果掸读,我們沒有為每一個member
設(shè)置默認(rèn)值而直接使用default initializer
串远,編譯器會報錯《梗”
特別提示
和memberwise initializer
一樣澡罚,如果我們自定義了init
方法,那么這個默認(rèn)的initializer就不存在了
肾请。但是留搔,我們同樣可以手工打造一個:
struct Location {
let x = 100.0
var y = 100.0
// Default initializer
init() {}
// Initializer
init(x: Double, y: Double) {
self.x = x;
self.y = y;
}
init(stringPoint: String) {
// "100,200"
let xy = stringPoint.characters.split(",")
x = atof(String(xy.first!))
y = atof(String(xy.last!))
}
}
Default initializer最簡單,看上去铛铁,就像一個什么都不做的方法一樣隔显。
Methods in struct
假設(shè)我們希望讓Location水平移動一段距離,我們可以這樣定義一個函數(shù):
var pointA = Location(x: 100, y: 200)
func moveHorizental(dist: Double, inout point: Location) {
point.x = point.x + dist
}
moveHorizental(10.0, point: PointA)
盡管達(dá)成了目的饵逐,但這樣做有很多問題括眠。首先,x
是一個Location
內(nèi)部的成員梳毙,這個代表水平坐標(biāo)的名字無需被Location
的使用者關(guān)心哺窄;其次,移動坐標(biāo)點本身就是一個只和Location
自身計算有關(guān)的行為账锹,我們應(yīng)該像下面這樣來完成這個任務(wù):
var pointA = Location(x: 100, y: 200)
pointA.moveHorizental(10.0)
這就是我們要為struct
類型定義methods
的原因萌业,它讓和類型相關(guān)的計算表現(xiàn)的更加自然。定義method很簡單奸柬,和定義函數(shù)是類似的:
struct Location {
let x = 100.0
var y = 100.0
mutating func moveHorizental(dist: Double) {
self.x = self.x + dist;
}
}
由于作為一個值類型生年,Swift
默認(rèn)不允許我們在method
里修改成員的值,如果我們要修改它廓奕,需要在對應(yīng)的方法前面使用mutating
關(guān)鍵字抱婉。之后档叔,我們就可以這樣:
pointA.moveHorizental(10.0)
來水平移動pointA了。
Struct extension
如果我們使用的Location
不是自己定義的蒸绩,但是我們?nèi)耘f希望在自己的項目里擴(kuò)展Location
的操作衙四,Swift
也能幫我們達(dá)成,這個機(jī)制患亿,叫做extension
传蹈。例如,我們希望給Location
添加垂直移動的方法:
extension Location {
mutating func moveVertical(dist: Double) {
self.y += dist
}
}
這里步藕,同樣我們要使用mutating
惦界。之后,在我們的項目里咙冗,就可以針對Location
類型的變量沾歪,使用moveVertical
方法了:
pointA.moveVertical(10.0)
接下來,我們再舉一個更明顯的例子雾消,我們甚至可以擴(kuò)展Swift
的String
類型灾搏,例如判斷一個字符串中字符的個數(shù)是奇數(shù)還是偶數(shù):
extension String {
func isEven() -> Bool {
return self.characters.count % 2 == 0 ? true : false
}
}
"An even string".isEven()
Struct is a value type
在一開始,我們就講到struct
是一個value type
立润,它用來表達(dá)某種和“值”有關(guān)的概念确镊。除了語義上的描述之外,value type還有一個特點范删,就是當(dāng)它被拷貝的時候,會把“整個值"拷貝過去
拷肌。
var pointA = Location(x: 100, y: 200)
var pointB = pointA
pointB.y = 500.0
pointA.y
從結(jié)果里到旦,我們就能看到,所謂的“拷貝整個值”巨缘,就是指修改pointB
完整復(fù)制了pointA
的內(nèi)容添忘,而不是和A共享同一個內(nèi)容。
Struct無處不在
其實若锁,在Swift
搁骑,我們已經(jīng)使用了很多struct
,甚至是哪些我們叫做“基礎(chǔ)類型”的Int
又固,String
仲器。在Playground
里,如果我們按住Command
鍵單擊某個類型仰冠,例如:Double
乏冀,就會看到,它是一個struct
洋只。
public struct Double {
public var value: Builtin.FPIEEE64
/// Create an instance initialized to zero.
public init()
public init(_bits v: Builtin.FPIEEE64)
/// Create an instance initialized to `value`.
public init(_ value: Double)
}
這和我們之前用過的諸如C辆沦、Objective-C是完全不同的昼捍。
作為Swift中的“named type”,從語法上來說肢扯,class和struct有很多相似的地方妒茬,例如:
struct PointValue {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
class PointRef {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
都可以用來自定義類型
;
都可以有properties
蔚晨;
都可以有methods
乍钻;
而實際上,作為一個reference type
蛛株,class具有很多struct不具備的表達(dá)能力
团赁,這些能力,都源自于它們要表達(dá)的內(nèi)容不同谨履。class
表達(dá)一個“對象
”欢摄,而struct
表達(dá)一個值
。我們通過一個例子來簡單的理解下這種區(qū)別笋粟。
Value Type vs Reference Type
第一個區(qū)別是:class
類型沒有默認(rèn)的init
方法怀挠。如果我們不指定它,Swift
編譯器會報錯害捕。
為什么要如此呢绿淋?因為,class
并不簡單表達(dá)一個“值”
的概念尝盼。Swift
要求我們明確通過init
方法說明“打造”一個對象的過程吞滞。相反,struct
表達(dá)一個自定義的“值
”盾沫,在沒有特別說明的情況下裁赠,一個值的初始化當(dāng)然是把它的每一個member都按順序初始化
。
第二個區(qū)別是:class
和struct
對“常量
”的理解是不同的赴精。我們分別定義一個PointValue
和PointRef
的常量:
let p1 = PointVal(x: 0, y: 0)
let p2 = PointRef(x: 0, y: 0)
p1.x = 10
p2.x = 10
同樣是常量佩捞,但是編譯器會對p1.x = 10報錯:
這是因為,p1
作為一個值類型
蕾哟,常量的意義當(dāng)然是:“它的值不能被改變”一忱。但是p2
作為一個引用類型,常量的意義則變成了谭确,它不能再引用其他的PointRef
對象帘营。但是,它可以改變其引用的對象自身琼富。
這就是引用類型代表的“對象”和值類型代表的“值本身”之間的區(qū)別仪吧,理解了這種區(qū)別,我們才能正確的理解struct和class的用法鞠眉。
"相等的值
"還是“相等的引用
”薯鼠?
如果我們定義下面兩個變量:
let p2 = PointRef(x: 0, y: 0)
let p3 = PointRef(x: 0, y: 0)
盡管它們都表示同一個坐標(biāo)點择诈,但是它們相等么?針對引用類型出皇,為了區(qū)分“同樣的值”和“同樣的對象”羞芍,Swift定義了Identity Operator:===和!==。
// Identity operator
if p2 === p3 {
print("They are the same object")
}
if p2 !== p3 {
print("They are not the same object")
}
它們分別用于比較兩個引用類型是否引用相同的對象郊艘,返回的是一個Bool類型荷科。
Method
之于struct
和class
看似都是在struct
和class
中定一個函數(shù),但是method
之于它們纱注,也有著不同的含義畏浆。如果struct
中的method
要修改其成員,我們要明確把它定義為mutating
:
struct PointVal {
var x: Int
var y: Int
mutating func moveX(x: Int) {
self.x += x
}
}
這是因為狞贱,在Swift
看來一個PointVal
的值刻获,和我們在程序中使用的諸如:123這樣的字面值是沒有本質(zhì)區(qū)別的,一個字面值理論上是不應(yīng)該有修改其自身值的方法的瞎嬉。
“通常你需要修改一個struct
的本意蝎毡,是需要一個新的值⊙踉妫”
特別提示
但是類對象不同沐兵,它的數(shù)據(jù)成員對他來說,只是一個用于描述其特征的屬性便监,我們當(dāng)然可以為其定義修改的方法:
class PointRef {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
func moveX(x: Int) {
self.x += x
}
}
賦值 Vs 復(fù)制
我們要講到的struct
和class
的最后一個區(qū)別是:
var p1 = PointVal(x: 0, y: 0)
var p4 = p1
p4.x = 10
p1.x // p1.x still 0
var p2 = PointRef(x: 0, y: 0)
var p5 = p2
p2.x = 10
p5.x // p5.x = 10
值類型的變量賦值扎谎,會把變量的值完整的拷貝,因此修改p4.x
不會影響p1.x
烧董;
引用類型的變量賦值簿透,會復(fù)制一個指向相同對象的引用,因此修改p2.x
會影響p5.x
解藻;
不再只是“值替身”的enum
很多時候,我們需要用一組特定的值葡盗,來表達(dá)一個公共的含義螟左。例如用1,2觅够,3胶背, 4表示東、南喘先、西钳吟、北:
let EAST = 1
let SOUTH = 2
let WEST = 3
let NORTH = 4
或者用一個字符串表示一年的月份:
let months = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"]
在上面這些例子里,無論是用數(shù)字表示方向窘拯,還是用字符串表示月份红且,它們都有一個共同的問題:我們讓一個類型承載了本不屬于他的語意坝茎。因此我們無法安全的避免“正確的類型,卻是無意義的值”這樣的問題暇番。例如:數(shù)字5表示什么方向呢嗤放?Jan.可以用來表示一月么?還有JAN呢壁酬?因此次酌,面對“把一組有相關(guān)意義的值定義成一個獨立的類型”這樣的任務(wù),Swift為我們提供了一個叫做:enumeration
的工具舆乔。
Enumeration
并不是一個新生事物岳服,幾乎任何一種編程語言都有和enumeration
類似的語法概念。但是Swift
對enumeration
做了諸多改進(jìn)和增強希俩,它已經(jīng)不再是一個簡單的“值的替身“吊宋。它可以有自己的屬性、方法斜纪,還可以遵從protocol
贫母。
定義一個Enumeration
我們這樣來來定義一個enum:
enum {
case value
case value
}
例如,我們可以把開始提到過的方向和月份定義成enum:
enum Direction {
case EAST
case SOUTH
case WEST
case NORTH
}
enum Month {
case January, Februray, March, April, May, June, July,
August, September, October, November, December
}
這樣盒刚,我們就用enum
定義了兩個新的類型腺劣,用來表示“方向”和“月份”,它們是兩個有限定值的類型因块。然后橘原,我們可以像下面這樣,使用它們代表的值:
let north = Direction.NORTH
let jan = Month.January
直觀上看涡上,使用enum
比直接使用數(shù)字和字符串有很多“天生”的好處:一來我們可以借助Xcode的auto complete避免輸入錯誤趾断;二來,使用enum是類型安全的吩愧,需要使用方向和月份內(nèi)容的時候芋酌,不會發(fā)生“類型正確,值卻無意義的情況”雁佳。
理解Enumeration
的“各種”value
在Swift
里脐帝,enum
的值,可以通過不同的方式表達(dá)出來糖权。而不像Objective-C堵腹,只能通過一個整數(shù)來替代。
Enumeration case
自身就是它的值
例如在上面的例子里星澳,當(dāng)我們當(dāng)我們使用Direction.NORTH
時疚顷,我們就已經(jīng)在訪問一個enum
的值了,它的case
就是它的值本身,我們無需特意給它找一個“值替身”來表示腿堤。另外阀坏,如果通過type inference
可以推導(dǎo)出enum
的類型,我們可以在讀取值的時候释液,省掉enum
的名字:
func direction(val: Direction) -> String {
switch val {
case .NORTH, .SOUTH:
return "up down"
case .EAST, .WEST:
return "left right"
}
}
這個例子里全释,有兩個地方值得注意:
因為val
的類型可以通過type inference
推導(dǎo)出是Direction
,因此误债,在case
里浸船,我們可以省略掉enum
的名字;
對于一個enum
來說寝蹈,它全部的值就是它所有的case
李命,因此在一個switch...case...
里,只要列舉了enum
所有的case
箫老,它就被認(rèn)為是exhausitive
的封字,因此,我們可以沒有default
分支耍鬓;
Raw values
和Objective-C
不同阔籽,Swift
的enum
默認(rèn)不會為它的case
“綁定”一個整數(shù)值。如果你一定要這么做牲蜀,你可以手工進(jìn)行“綁定”笆制。而這樣“綁定”來的值,叫做raw values
涣达。
enum: Type {
case value
case value
}
enum Direction: Int {
case EAST
case SOUTH
case WEST
case NORTH
}
當(dāng)我們這樣定義Direction之后在辆,Swift就會依次把EAST / SOUTH / WEST / NORTH“綁定”上0 / 1 / 2 / 3。我們也可以像下面這樣給所有的case單獨指定值:
enum Direction: Int {
case EAST = 2
case SOUTH = 4
case WEST = 6
case NORTH = 8
}
或者度苔,我們可以給所有的case指定一個初始值:
enum Month: Int {
case January = 1, Februray, March, April, May, June, July,
August, September, October, November, December
}
這樣匆篓,Swift就會自動為其他月份“綁定”對應(yīng)的整數(shù)值了。如果我們要讀取enum的raw value寇窑,可以訪問case的rawProperty方法:
let north = Direction.NORTH.rawValue
let jan = Month.January.rawValue
或者鸦概,我們可以通過一個rawValue
來生成一個enum
的value
值:
let north = Direction(rawValue: 4)
if let n = Direction(rawValue: 4) {
print("North!")
}
但是,不一定所有傳入的值都是一個合法的rawValue
甩骏。因此完残,Direction(rawValue:)
是一個failable initializer
,它返回的類型是一個Optional<Dierection>
横漏。
Associated values
Raw value
的各種機(jī)制和方式,傳統(tǒng)且易于理解熟掂。但是缎浇,這并不是給enum
“綁定”值的唯一辦法,在Swift
里秋度,我們甚至可以給每一個case
“綁定”不同類型的值曲秉。我們管這樣的值叫做associated values
。
例如朦蕴,我們定義一個表達(dá)HTTP action的enum
:
enum HTTPAction {
case GET
case POST(String)
case PUT(Int, String)
}
我們在每一個需要有associated value
的case
后面放上和case
對應(yīng)的值的類型指厌,就可以了刊愚。然后,我們可以這樣來使用HTTPAction
:
var action1 = HTTPAction.GET
var action2 = HTTPAction.POST("BOXUE")
switch action1 {
case .GET:
print("HTTP GET")
case let .POST(msg):
print("\(msg)")
case .DELETE(let id, let value):
print("\(id)=\(value)")
}
這個例子里踩验,有兩點是應(yīng)該注意的:
不是每一個case
必須有associated value
鸥诽,例如.GET
就只有自己的enum value
;
當(dāng)我們想“提取”associated value
的所有內(nèi)容時箕憾,我們可以把let
或var
寫在case
后面牡借,例如.POST
的用法;
當(dāng)我們想分別“提取associated value
中的某些值時袭异,我們可以把let或var寫在associated value
里面钠龙,例如.DELETE的用法;
Optional
是一個enumeration
其實御铃,有了associated value
之后就不難想象碴里,Swift中的Optional,是基于enum實現(xiàn)的了上真∫б福可以把一個Optional理解為包含兩個case的enum,一個是.None谷羞,表示空值帝火;一個是.Some用來表示非空的值。下面這兩種定義optional的方式是一樣的:
var address: String? = nil
var address1: Optional<String> = nil
如果我們按住option然后點擊Optional湃缎,就可以看到Xcode提示我們Optional
是一個enum
犀填。
var address: Optional<String> = .Some("Beijing")
switch address {
case .None:
print("No address")
case let .Some(addr):
print("\(addr)")
}
而當(dāng)address為.None時,它和nil是相等的:
address = .None
if address == nil {
print(".None is equal to nil")
}
自定義 properties
struct Location {
var x = 100.0
var y = 100.0
}
class PointRef {
var x: Int
var y: Int
}
enum Direction: Int {
case EAST = 2
case SOUTH = 4
case WEST = 6
case NORTH = 8
}
其中嗓违,我們已經(jīng)在struct
和class
中使用了自定義類型的屬性九巡。實際上,除了像定義變量一樣的使用屬性蹂季,Swift
為自定義類型的屬性提供了更多功能
Stored properties
顧名思義冕广,這種屬性是用來真正存儲值的,就像之前我們?yōu)?code>Location和PointRef
定義的x
和y
一樣偿洁,它們有以下特點:
可以分別使用let
或var
定義成常量或變量撒汉;
init
方法要確保每一個stored property
被初始化;
可以在定義property
的時候指定初始值涕滋;
實際占用內(nèi)存空間睬辐;
使用obj.property
的形式讀取;
使用obj.property = val
的形式賦值溯饵;
只有struct
和class
可以定義stored property
侵俗;
每一個stored property
都表示了某一個自定義類型對象的某種特點,但是有些屬性是需要訪問的時候被計算出來的丰刊,而不是定義之后一成不變的隘谣。這種屬性叫做computed properties
。
Computed properties
顧名思義啄巧,作為properties
寻歧,它用來表示對象的某種屬性。但是棵帽,它的值在每次被訪問的時候熄求,要被計算出來,而不是內(nèi)存中讀取出來逗概。我們來看一個例子:
struct MyRect {
var origin: Point
var width: Double
var height: Double
}
var rect1 = MyRect(origin: Point(1, 1), width: 200, height: 100)
我們使用原點以及寬高定義了一個矩形弟晚。除了這些“原始”的矩形屬性之外,當(dāng)我們想訪問矩形的“中心”時逾苫,我們就可以定義一個computed property:
struct MyRect {
var origin: Point
var width: Double
var height: Double
var center: Point {
let centerX = origin.x + self.width / 2
let centerY = origin.Y + self.height / 2
return Point(x: centerX, y: centerY)
}
}
這樣卿城,我們就能“動態(tài)”讀取到一個矩形的中心了:
rect1.center
rect1.height = 200
rect1.center
```
從`Playground`結(jié)果里,我們可以看到`center`會隨著`height`的改變而改變铅搓。在我們的這個例子里我們只是讀取了一個`computed property`瑟押,我們可以對`computed property`賦值么?
答案是可以的星掰,但是由于`computed property`并不實際占用內(nèi)存多望,因此我們要把傳入的值“拆”給`class`的各種`stored properties`。并且氢烘,一旦我們需要給`computed property`賦值怀偷,我們就要在定義它的時候,明確它的`get`和`set`方法播玖,像下面這樣:
```
struct MyRect {
var origin: Point
var width: Double
var height: Double
var center: Point {
get {
let centerX = origin.x + self.width / 2
let centerY = origin.Y + self.height / 2
return Point(x: centerX, y: centerY)
}
set(newCenter) {
self.origin.x = newCenter.x - self.width / 2
self.origin.y = newCenter.y - self.height / 2
}
}
}
```
對于上面的例子椎工,有幾點值得注意:
我們使用get和set關(guān)鍵字分別表示一個`computed propterty`的讀取和賦值操作;
當(dāng)我們對一個`computed property`賦值的時候蜀踏,由于它被拆分成多個`stored property`维蒙,因此我們在拆分的過程中總要做一些假設(shè)。在我們的例子里果覆,我們假設(shè)當(dāng)`center`變化時颅痊,矩形的寬高不變,移動了原點局待。
然后斑响,我們可以通過下面的代碼來測試結(jié)果:
```
var center = rect1.center
rect1.origin
center.x += 100
rect1.center = center
rect1.origin
```
從`Playground`的結(jié)果可以看到吗讶,`rect1`的`orgin`向右移動了`100`。以上就是`computed property`的用法恋捆,接下來,我們回到`stored property`重绷,如果我們想在`stored property`賦值的時候自動過濾掉“非法值”沸停,或者在`stored property`賦值后自動更新相關(guān)的`property`怎么辦呢?在`Swift`里昭卓,我們可以給`stored property`添加`observer`愤钾。
###Property observer
`Swift`給`stored property`提供了兩個`observer`:`willSet`和`didSet`,它們分別在`stored property`被賦值前和后被調(diào)用候醒。定義`observer`和定義`computed property`是類似的能颁。首先我們來看`willSet`,我們在`width`被賦值之前倒淫,向控制臺打印一個信息:
```
struct MyRect {
var origin: Point
var width: Double {
willSet(newWidth) {
print("width will be updated")
}
}
}
rect1.width = 300
這時伙菊,打開控制臺,就可以看到willSet的輸出了敌土。接下來镜硕,我們來看didSet的用法,如果我們希望width大于0的時候才更新并且保持寬高相等返干,可以這樣:
struct MyRect {
var origin: Point
var width: Double {
didSet(oldWidth) {
if width <= 0 {
width = oldWidth
}
else {
self.height = width
}
}
}
}
rect1.width=-300
rect1.height
rect1.width=300
rect1.height
```
從結(jié)果我們就可以看到兴枯,只有在`width大于0`的時候,`width`和`height`才會被更新矩欠。在上面的這兩個例子里财剖,有以下幾點是我們要特別強調(diào)的:
在`didSet``里,我們可以直接使用width讀取MyRect的width屬性`癌淮,但是我們必須使用`self`讀取其它的屬性躺坟;
`willSet`和`didSet`不在對象的`init`過程中生效,僅針對一個已經(jīng)完整初始化的對象在對屬性賦值的時候生效该默;
如果我們不指定`willSet`和`didSet`的參數(shù)瞳氓,`Swift`默認(rèn)使用`newValue`和`oldValue`作為它們的參數(shù);
###Type property
首先我們定義一個`enum`表示各種形狀:
```
enum Shape {
case RECT
case TRIANGLE
case CIRCLE
}
```
然后栓袖,我們可以給`MyRect`添加一個屬性表示它對應(yīng)的形狀匣摘,由于這個屬性對于MyRect的所有對象都是一樣的,都應(yīng)該是`Shape.RECT`裹刮,這時音榜,我們就可以為`MyRect`添加一個`type property`:
```
struct MyRect {
var origin: Point
var width: Double
var height: Double
// Type property
static let shape = Shape.RECT
}
```
我們使用`static`關(guān)鍵字為`named type`定義`type property`,定義好之后捧弃,我們不能通過具體的對象訪問`type property`赠叼,而是要直接使用類型的名字擦囊。例如:
```
// WRONG!!! rect1.shape
let shape = MyRect.shape
```
這就是`type property`的用法,它可以幫助我們很方便的描述一個類型所有對象的屬性嘴办。