美文网首页swift开源库
Swift中的自定义类型

Swift中的自定义类型

作者: AKyS佐毅 | 来源:发表于2017-02-19 23:51 被阅读569次
    WechatIMG32.jpeg

    在实际编程中,很多时候,我们都需要使用比IntString这类简单类型更复杂的类型,例如,需要两个Double表达的2D坐标位置;需要两个String表达的人的姓和名等等。因此,如果我们的程序里真的可以使用叫做locationname的类型,程序的可读性就会提高很多。于是,Swift允许我们根据实际的需要,定义自己的类型系统。并把这种自定义的类型,叫做named types

    根据实际的应用场景,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提供的库函数。定义好inRange之后,我们就可以像:

    inRange(100, y: 500)
    inRange(300, y: 800)
    

    来调用inRange。但是这样有一个不足,当我们需要比较很多个点和Center的距离的时候,这些数字并不能明确告诉我们它们代表的位置的意义,甚至我们都无法知道它们代表一个数字。如果我们可以像这样来比较位置:

    inRange(location1)
    inRange(myHome)
    

    相比数字,它们看上去就会直观的多,而这,就是我们需要自定义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
    

    有了这些内容之后,我们就可以修改之前的inRange函数了,让它接受一个Location类型的参数:

    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")
    

    为了实现这个功能,我们需要给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的定义发生了一个错误:

    这是由于struct initializer创建规则导致的。

    Initialization rules

    Memberwise initializer

    对于我们一开始的Location定义:

    struct Location {
        let x: Double
        var y: Double
    }
    

    它没有自定义任何init方法,Swift就会自动创建一个struct memberwise initializer。因此,我们可以逐个成员的去定义一个Location

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

    “Memberwise只是默认为我们提供了一个按成员初始化的方法,它并不会自动为成员设置默认值。”

    特别提示
    在我们自定义了init方法之后,Swift就不会自动为我们创建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的参数中,使用了和member同样的名字,在init的实现中,我们使用了关键字self来表示要创建的struct本身,来帮助编译器区分member x和参数x,这和之前我们用pointA.x的道理是一样的。

    这里,有一个小细节。之前我们讲函数的时候,说过函数的第一个参数是省略outername的,但是在init里,第一个参数的outername是不会省略的,我们必须指定每一个参数的outername

    Member default values

    如果我们希望给structmember设置一个默认值,而不用每次创建的时候去指定它们,我们可以像下面这样:

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

    在这里,如果我们可以明确给成员设置默认值,就可以省掉type annotation的部分了,type inference会正确的推导成员的类型。

    Default initializer

    如果我们为struct的每一个成员都设置了默认值,并且,我们没有自定义init方法,Swift就会自动为我们生成一个default initializer。有了它,我们就可以这样创建Location了:

    let center = Location()
    

    “如果,我们没有为每一个member设置默认值而直接使用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

    假设我们希望让Location水平移动一段距离,我们可以这样定义一个函数:

    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内部的成员,这个代表水平坐标的名字无需被Location的使用者关心;其次,移动坐标点本身就是一个只和Location自身计算有关的行为,我们应该像下面这样来完成这个任务:

    var pointA = Location(x: 100, y: 200)
    pointA.moveHorizental(10.0)
    

    这就是我们要为struct类型定义methods的原因,它让和类型相关的计算表现的更加自然。定义method很简单,和定义函数是类似的:

    struct Location {
        let x = 100.0
        var y = 100.0
        
        mutating func moveHorizental(dist: Double) {
            self.x = self.x + dist;
        }
    }
    

    由于作为一个值类型,Swift默认不允许我们在method里修改成员的值,如果我们要修改它,需要在对应的方法前面使用mutating关键字。之后,我们就可以这样:

    pointA.moveHorizental(10.0)
    来水平移动pointA了。
    

    Struct extension

    如果我们使用的Location不是自己定义的,但是我们仍旧希望在自己的项目里扩展Location的操作,Swift也能帮我们达成,这个机制,叫做extension。例如,我们希望给Location添加垂直移动的方法:

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

    这里,同样我们要使用mutating。之后,在我们的项目里,就可以针对Location类型的变量,使用moveVertical方法了:

    pointA.moveVertical(10.0)
    

    接下来,我们再举一个更明显的例子,我们甚至可以扩展SwiftString类型,例如判断一个字符串中字符的个数是奇数还是偶数:

    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,它用来表达某种和“值”有关的概念。除了语义上的描述之外,value type还有一个特点,就是当它被拷贝的时候,会把“整个值"拷贝过去

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

    从结果里,我们就能看到,所谓的“拷贝整个值”,就是指修改pointB完整复制了pointA的内容,而不是和A共享同一个内容。

    Struct无处不在

    其实,在Swift,我们已经使用了很多struct,甚至是哪些我们叫做“基础类型”的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不具备的表达能力,这些能力,都源自于它们要表达的内容不同。class表达一个“对象”,而struct表达一个值。我们通过一个例子来简单的理解下这种区别。

    Value Type vs Reference Type

    第一个区别是:class类型没有默认的init方法。如果我们不指定它,Swift编译器会报错。

    为什么要如此呢?因为,class不简单表达一个“值”的概念。Swift要求我们明确通过init方法说明“打造”一个对象的过程。相反,struct表达一个自定义的“值”,在没有特别说明的情况下,一个值的初始化当然是把它的每一个member都按顺序初始化

    第二个区别是: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对象。但是,它可以改变其引用的对象自身。

    这就是引用类型代表的“对象”和值类型代表的“值本身”之间的区别,理解了这种区别,我们才能正确的理解struct和class的用法。

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

    如果我们定义下面两个变量:

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

    尽管它们都表示同一个坐标点,但是它们相等么?针对引用类型,为了区分“同样的值”和“同样的对象”,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中定一个函数,但是method之于它们,也有着不同的含义。如果struct中的method要修改其成员,我们要明确把它定义为mutating

    struct PointVal {
        var x: Int
        var y: Int
    
        mutating func moveX(x: Int) {
            self.x += x
        }
    }
    

    这是因为,在Swift看来一个PointVal的值,和我们在程序中使用的诸如:123这样的字面值是没有本质区别的,一个字面值理论上是不应该有修改其自身值的方法的。

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

    特别提示

     但是类对象不同,它的数据成员对他来说,只是一个用于描述其特征的属性,我们当然可以为其定义修改的方法:
    
    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的最后一个区别是:

    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"]
    

    在上面这些例子里,无论是用数字表示方向,还是用字符串表示月份,它们都有一个共同的问题:我们让一个类型承载了本不属于他的语意。因此我们无法安全的避免“正确的类型,却是无意义的值”这样的问题。例如:数字5表示什么方向呢?Jan.可以用来表示一月么?还有JAN呢?因此,面对“把一组有相关意义的值定义成一个独立的类型”这样的任务,Swift为我们提供了一个叫做:enumeration的工具。

    Enumeration并不是一个新生事物,几乎任何一种编程语言都有和enumeration类似的语法概念。但是Swiftenumeration做了诸多改进和增强,它已经不再是一个简单的“值的替身“。它可以有自己的属性、方法,还可以遵从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比直接使用数字和字符串有很多“天生”的好处:一来我们可以借助Xcode的auto complete避免输入错误;二来,使用enum是类型安全的,需要使用方向和月份内容的时候,不会发生“类型正确,值却无意义的情况”。

    理解Enumeration的“各种”value

    Swift里,enum的值,可以通过不同的方式表达出来。而不像Objective-C,只能通过一个整数来替代。

    Enumeration case自身就是它的值

    例如在上面的例子里,当我们当我们使用Direction.NORTH时,我们就已经在访问一个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“绑定”一个整数值。如果你一定要这么做,你可以手工进行“绑定”。而这样“绑定”来的值,叫做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就会自动为其他月份“绑定”对应的整数值了。如果我们要读取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的各种机制和方式,传统且易于理解。但是,这并不是给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的所有内容时,我们可以把letvar写在case后面,例如.POST的用法;
    当我们想分别“提取associated value中的某些值时,我们可以把let或var写在associated value里面,例如.DELETE的用法;

    Optional是一个enumeration

    其实,有了associated value之后就不难想象,Swift中的Optional,是基于enum实现的了。可以把一个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
    }
    

    其中,我们已经在structclass中使用了自定义类型的属性。实际上,除了像定义变量一样的使用属性,Swift为自定义类型的属性提供了更多功能

    Stored properties

    顾名思义,这种属性是用来真正存储值的,就像之前我们为LocationPointRef定义的xy一样,它们有以下特点:

    可以分别使用letvar定义成常量或变量;
    init方法要确保每一个stored property被初始化;
    可以在定义property的时候指定初始值;
    实际占用内存空间;
    使用obj.property的形式读取;
    使用obj.property = val的形式赋值;
    只有structclass可以定义stored property
    每一个stored property都表示了某一个自定义类型对象的某种特点,但是有些属性是需要访问的时候被计算出来的,而不是定义之后一成不变的。这种属性叫做computed properties

    Computed properties

    顾名思义,作为properties,它用来表示对象的某种属性。但是,它的值在每次被访问的时候,要被计算出来,而不是内存中读取出来。我们来看一个例子:

    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)
        }
    }
    

    这样,我们就能“动态”读取到一个矩形的中心了:

    rect1.center
    rect1.height = 200
    rect1.center
    ```
    从`Playground`结果里,我们可以看到`center`会随着`height`的改变而改变。在我们的这个例子里我们只是读取了一个`computed property`,我们可以对`computed property`赋值么?
    
    答案是可以的,但是由于`computed property`并不实际占用内存,因此我们要把传入的值“拆”给`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关键字分别表示一个`computed propterty`的读取和赋值操作;
    当我们对一个`computed property`赋值的时候,由于它被拆分成多个`stored property`,因此我们在拆分的过程中总要做一些假设。在我们的例子里,我们假设当`center`变化时,矩形的宽高不变,移动了原点。
    
    然后,我们可以通过下面的代码来测试结果:
    ```
    var center = rect1.center
    rect1.origin
    center.x += 100
    rect1.center = center
    rect1.origin
    ```
    从`Playground`的结果可以看到,`rect1`的`orgin`向右移动了`100`。以上就是`computed property`的用法,接下来,我们回到`stored property`,如果我们想在`stored property`赋值的时候自动过滤掉“非法值”,或者在`stored property`赋值后自动更新相关的`property`怎么办呢?在`Swift`里,我们可以给`stored property`添加`observer`。
    
    ###Property observer
    
    `Swift`给`stored property`提供了两个`observer`:`willSet`和`didSet`,它们分别在`stored property`被赋值前和后被调用。定义`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
    ```
    从结果我们就可以看到,只有在`width大于0`的时候,`width`和`height`才会被更新。在上面的这两个例子里,有以下几点是我们要特别强调的:
    
    在`didSet``里,我们可以直接使用width读取MyRect的width属性`,但是我们必须使用`self`读取其它的属性;
    `willSet`和`didSet`不在对象的`init`过程中生效,仅针对一个已经完整初始化的对象在对属性赋值的时候生效;
    如果我们不指定`willSet`和`didSet`的参数,`Swift`默认使用`newValue`和`oldValue`作为它们的参数;
    
    ###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`关键字为`named type`定义`type property`,定义好之后,我们不能通过具体的对象访问`type property`,而是要直接使用类型的名字。例如:
    ```
    // WRONG!!! rect1.shape
    let shape = MyRect.shape
    ```
    这就是`type property`的用法,它可以帮助我们很方便的描述一个类型所有对象的属性。
    
    

    相关文章

      网友评论

        本文标题:Swift中的自定义类型

        本文链接:https://www.haomeiwen.com/subject/rmhswttx.html