Swift中的自定義類型

WechatIMG32.jpeg

在實際編程中挣跋,很多時候萍诱,我們都需要使用比Int悬嗓,String這類簡單類型更復(fù)雜的類型,例如裕坊,需要兩個Double表達(dá)的2D坐標(biāo)位置包竹;需要兩個String表達(dá)的人的姓和名等等。因此,如果我們的程序里真的可以使用叫做locationname的類型周瞎,程序的可讀性就會提高很多苗缩。于是,Swift允許我們根據(jù)實際的需要声诸,定義自己的類型系統(tǒng)酱讶。并把這種自定義的類型,叫做named types彼乌。

根據(jù)實際的應(yīng)用場景泻肯,Swift提供了4種不同的named typesstructclass慰照,enumprotocol灶挟。首先,我們來看表達(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ò)展SwiftString類型灾搏,例如判斷一個字符串中字符的個數(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ū)別是:classstruct對“常量”的理解是不同的赴精。我們分別定義一個PointValuePointRef的常量:

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之于structclass

看似都是在structclass中定一個函數(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ù)制

我們要講到的structclass的最后一個區(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類似的語法概念。但是Swiftenumeration做了諸多改進(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不同阔籽,Swiftenum默認(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來生成一個enumvalue值:

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 valuecase后面放上和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)容時箕憾,我們可以把letvar寫在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)在structclass中使用了自定義類型的屬性九巡。實際上,除了像定義變量一樣的使用屬性蹂季,Swift為自定義類型的屬性提供了更多功能

Stored properties

顧名思義冕广,這種屬性是用來真正存儲值的,就像之前我們?yōu)?code>Location和PointRef定義的xy一樣偿洁,它們有以下特點:

可以分別使用letvar定義成常量或變量撒汉;
init方法要確保每一個stored property被初始化;
可以在定義property的時候指定初始值涕滋;
實際占用內(nèi)存空間睬辐;
使用obj.property的形式讀取;
使用obj.property = val的形式賦值溯饵;
只有structclass可以定義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`的用法,它可以幫助我們很方便的描述一個類型所有對象的屬性嘴办。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞬场,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子涧郊,更是在濱河造成了極大的恐慌贯被,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妆艘,死亡現(xiàn)場離奇詭異彤灶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)批旺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門幌陕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汽煮,你說我怎么就攤上這事搏熄。” “怎么了逗物?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵搬卒,是天一觀的道長。 經(jīng)常有香客問我翎卓,道長契邀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任失暴,我火速辦了婚禮坯门,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逗扒。我一直安慰自己古戴,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布矩肩。 她就那樣靜靜地躺著现恼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪黍檩。 梳的紋絲不亂的頭發(fā)上叉袍,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機(jī)與錄音刽酱,去河邊找鬼喳逛。 笑死,一個胖子當(dāng)著我的面吹牛棵里,可吹牛的內(nèi)容都是我干的润文。 我是一名探鬼主播姐呐,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼典蝌!你這毒婦竟也來了曙砂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤骏掀,失蹤者是張志新(化名)和其女友劉穎麦轰,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砖织,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年末荐,在試婚紗的時候發(fā)現(xiàn)自己被綠了侧纯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡甲脏,死狀恐怖眶熬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情块请,我是刑警寧澤娜氏,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站墩新,受9級特大地震影響贸弥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜海渊,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一绵疲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧臣疑,春花似錦盔憨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缺狠,卻和暖如春问慎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背儒老。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工蝴乔, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驮樊。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓薇正,卻偏偏與公主長得像片酝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子挖腰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

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

  • Swift 介紹 簡介 Swift 語言由蘋果公司在 2014 年推出雕沿,用來撰寫 OS X 和 iOS 應(yīng)用程序 ...
    大L君閱讀 3,210評論 3 25
  • title: "Swift 中枚舉高級用法及實踐"date: 2015-11-20tags: [APPVENTUR...
    guoshengboy閱讀 2,580評論 0 2
  • 轉(zhuǎn)載自:https://github.com/Tim9Liu9/TimLiu-iOS[https://github...
    香橙柚子閱讀 8,539評論 0 35
  • 新的開始是我在反反復(fù)復(fù)習(xí)慣強調(diào)后的重新來過。再次回到微笑的時候猴仑,我突然明白了自己在乎的梗审轮。 回家的路途是折磨多踹的...
    黃小卷閱讀 120評論 0 0
  • 簡書這名字起得很簡潔易懂,簡易書寫嘛辽俗,為想寫文的娃提供一個干凈的平臺疾渣,安靜地寫,最主要是內(nèi)心平靜地寫崖飘。為什么這樣說...
    虛圖閱讀 9,408評論 0 8