Swift中的自定義類型

WechatIMG32.jpeg

在實際編程中,很多時候,我們都需要使用比IntString這類簡單類型更復雜的類型,例如,需要兩個Double表達的2D坐標位置;需要兩個String表達的人的姓和名等等。因此,如果我們的程序里真的可以使用叫做locationname的類型,程序的可讀性就會提高很多。于是,Swift允許我們根據(jù)實際的需要,定義自己的類型系統(tǒng)。并把這種自定義的類型,叫做named types

根據(jù)實際的應用場景,Swift提供了4種不同的named typesstructclassenumprotocol。首先,我們來看表達值類型(value type)的struct

如何來理解這個value type呢?在前面我們也提到過,有時,我們需要把沒有任何意義的Double,近一步抽象成一個點坐標的location。而無論是Double還是location,它們所表達的,都是“某種值”這樣的概念,而這,就是值類型的含義。

定義一個struct

先來看一個例子,我們要計算平面坐標里某個點距點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。但是這樣有一個不足,當我們需要比較很多個點和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坐標。定義好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ī)則導致的。

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只是默認為我們提供了一個按成員初始化的方法,它并不會自動為成員設(shè)置默認值。”

特別提示
在我們自定義了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的道理是一樣的。

這里,有一個小細節(jié)。之前我們講函數(shù)的時候,說過函數(shù)的第一個參數(shù)是省略outername的,但是在init里,第一個參數(shù)的outername是不會省略的,我們必須指定每一個參數(shù)的outername

Member default values

如果我們希望給structmember設(shè)置一個默認值,而不用每次創(chuàng)建的時候去指定它們,我們可以像下面這樣:

struct Location {
    let x = 100.0
    var y = 100.0
}

在這里,如果我們可以明確給成員設(shè)置默認值,就可以省掉type annotation的部分了,type inference會正確的推導成員的類型。

Default initializer

如果我們?yōu)?code>struct的每一個成員都設(shè)置了默認值,并且,我們沒有自定義init方法,Swift就會自動為我們生成一個default initializer。有了它,我們就可以這樣創(chuàng)建Location了:

let center = Location()

“如果,我們沒有為每一個member設(shè)置默認值而直接使用default initializer,編譯器會報錯。”

特別提示
memberwise initializer一樣,如果我們自定義了init方法,那么這個默認的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)

盡管達成了目的,但這樣做有很多問題。首先,x是一個Location內(nèi)部的成員,這個代表水平坐標的名字無需被Location的使用者關(guān)心;其次,移動坐標點本身就是一個只和Location自身計算有關(guān)的行為,我們應該像下面這樣來完成這個任務:

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默認不允許我們在method里修改成員的值,如果我們要修改它,需要在對應的方法前面使用mutating關(guān)鍵字。之后,我們就可以這樣:

pointA.moveHorizental(10.0)
來水平移動pointA了。

Struct extension

如果我們使用的Location不是自己定義的,但是我們?nèi)耘f希望在自己的項目里擴展Location的操作,Swift也能幫我們達成,這個機制,叫做extension。例如,我們希望給Location添加垂直移動的方法:

extension Location {
    mutating func moveVertical(dist: Double) {
        self.y += dist
    }
}

這里,同樣我們要使用mutating。之后,在我們的項目里,就可以針對Location類型的變量,使用moveVertical方法了:

pointA.moveVertical(10.0)

接下來,我們再舉一個更明顯的例子,我們甚至可以擴展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,它用來表達某種和“值”有關(guān)的概念。除了語義上的描述之外,value type還有一個特點,就是當它被拷貝的時候,會把“整個值"拷貝過去

var pointA = Location(x: 100, y: 200)
var pointB = pointA
pointB.y = 500.0
pointA.y

從結(jié)果里,我們就能看到,所謂的“拷貝整個值”,就是指修改pointB完整復制了pointA的內(nèi)容,而不是和A共享同一個內(nèi)容。

Struct無處不在

其實,在Swift,我們已經(jīng)使用了很多struct,甚至是哪些我們叫做“基礎(chǔ)類型”的IntString。在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 typeclass具有很多struct不具備的表達能力,這些能力,都源自于它們要表達的內(nèi)容不同。class表達一個“對象”,而struct表達一個值。我們通過一個例子來簡單的理解下這種區(qū)別。

Value Type vs Reference Type

第一個區(qū)別是:class類型沒有默認的init方法。如果我們不指定它,Swift編譯器會報錯。

為什么要如此呢?因為,class不簡單表達一個“值”的概念。Swift要求我們明確通過init方法說明“打造”一個對象的過程。相反,struct表達一個自定義的“值”,在沒有特別說明的情況下,一個值的初始化當然是把它的每一個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作為一個值類型,常量的意義當然是:“它的值不能被改變”。但是p2作為一個引用類型,常量的意義則變成了,它不能再引用其他的PointRef對象。但是,它可以改變其引用的對象自身。

這就是引用類型代表的“對象”和值類型代表的“值本身”之間的區(qū)別,理解了這種區(qū)別,我們才能正確的理解struct和class的用法。

"相等的值"還是“相等的引用”?

如果我們定義下面兩個變量:

let p2 = PointRef(x: 0, y: 0)
let p3 = PointRef(x: 0, y: 0)

盡管它們都表示同一個坐標點,但是它們相等么?針對引用類型,為了區(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ū)別的,一個字面值理論上是不應該有修改其自身值的方法的。

“通常你需要修改一個struct的本意,是需要一個新的值。”

特別提示

 但是類對象不同,它的數(shù)據(jù)成員對他來說,只是一個用于描述其特征的屬性,我們當然可以為其定義修改的方法:
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 復制

我們要講到的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
引用類型的變量賦值,會復制一個指向相同對象的引用,因此修改p2.x會影響p5.x

不再只是“值替身”的enum

很多時候,我們需要用一組特定的值,來表達一個公共的含義。例如用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)意義的值定義成一個獨立的類型”這樣的任務,Swift為我們提供了一個叫做:enumeration的工具。

Enumeration并不是一個新生事物,幾乎任何一種編程語言都有和enumeration類似的語法概念。但是Swiftenumeration做了諸多改進和增強,它已經(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的值,可以通過不同的方式表達出來。而不像Objective-C,只能通過一個整數(shù)來替代。

Enumeration case自身就是它的值

例如在上面的例子里,當我們當我們使用Direction.NORTH時,我們就已經(jīng)在訪問一個enum的值了,它的case就是它的值本身,我們無需特意給它找一個“值替身”來表示。另外,如果通過type inference可以推導出enum的類型,我們可以在讀取值的時候,省掉enum的名字:

func direction(val: Direction) -> String {
    switch val {
    case .NORTH, .SOUTH:
        return "up down"
    case .EAST, .WEST:
        return "left right"
    }
}

這個例子里,有兩個地方值得注意:

因為val的類型可以通過type inference推導出是Direction,因此,在case里,我們可以省略掉enum的名字;
對于一個enum來說,它全部的值就是它所有的case,因此在一個switch...case...里,只要列舉了enum所有的case,它就被認為是exhausitive的,因此,我們可以沒有default分支;

Raw values

Objective-C不同,Swiftenum默認不會為它的case“綁定”一個整數(shù)值。如果你一定要這么做,你可以手工進行“綁定”。而這樣“綁定”來的值,叫做raw values

enum: Type { 
    case value 
    case value 
}

enum Direction: Int {
    case EAST
    case SOUTH
    case WEST
    case NORTH
}

當我們這樣定義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就會自動為其他月份“綁定”對應的整數(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的各種機制和方式,傳統(tǒng)且易于理解。但是,這并不是給enum“綁定”值的唯一辦法,在Swift里,我們甚至可以給每一個case“綁定”不同類型的值。我們管這樣的值叫做associated values

例如,我們定義一個表達HTTP action的enum

enum HTTPAction {
    case GET
    case POST(String)
    case PUT(Int, String)
}

我們在每一個需要有associated valuecase后面放上和case對應的值的類型,就可以了。然后,我們可以這樣來使用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)")
}

這個例子里,有兩點是應該注意的:

不是每一個case必須有associated value,例如.GET就只有自己的enum value
當我們想“提取”associated value的所有內(nèi)容時,我們可以把letvar寫在case后面,例如.POST的用法;
當我們想分別“提取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)")
}

而當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)

我們使用原點以及寬高定義了一個矩形。除了這些“原始”的矩形屬性之外,當我們想訪問矩形的“中心”時,我們就可以定義一個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`的讀取和賦值操作;
當我們對一個`computed property`賦值的時候,由于它被拆分成多個`stored property`,因此我們在拆分的過程中總要做一些假設(shè)。在我們的例子里,我們假設(shè)當`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`默認使用`newValue`和`oldValue`作為它們的參數(shù);

###Type property

首先我們定義一個`enum`表示各種形狀:
```
enum Shape {
    case RECT
    case TRIANGLE
    case CIRCLE
}
```
然后,我們可以給`MyRect`添加一個屬性表示它對應的形狀,由于這個屬性對于MyRect的所有對象都是一樣的,都應該是`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)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

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