关于Swift中Struct,Class和Enum的那些事儿

点上方安卓巴士Android开发者门户,获取更多干货及教程

关于Swift中Struct,Class和Enum的那些事儿_第1张图片

Swift type System

关于Swift中Struct,Class和Enum的那些事儿_第2张图片

Swift是强类型的,尽管只有六种类型。

命名类型:protocol, class , struct , enum

复合类型:tuple, function

可能会有疑问,那些基本类型:Bool,Int,UInt, Float, Double, Character, String, Array, Set, Dictionary, Optional。实际上他们都是通过命名类型创建的。

| Struct Class and Enum 比较 |

Swift中提供了多种可以结构化存储数据的方式,它们是: struct、enum和

class。Swift标准库中的绝大多数类型都是struct,甚至Foundation中的一些类也提供了它们在Swift中的struct版本,而class和enum只占很少一部分。

Class,Struct and Enum对比表

关于Swift中Struct,Class和Enum的那些事儿_第3张图片

共同点:

都可以当作protocol

都可以使用extension,扩充method

都可以使用泛型

 如何抉择?|

通常,在平时的编程中,按照对象的生命周期形态,可以把使用的类型分成两大类:

一类必须有明确生命周期的,它们必须被明确的初始化、使用、最后明确的被释放。例如:文件句柄、数据库连接、线程同步锁等等。这些类型的初始化和释放都不是拷贝内存这么简单,通常,这类内容,我们会选择使用class来实现。

另一类,则是没有那么明显的生命周期。例如:整数、字符串、URL等等。这些对象一旦被创建之后,就很少被修改,我们只是需要使用这些对象的值,用完之后,我们也无需为这些对象的销毁做更多额外的工作,只是把它们占用的内存回收就好了。这类内容,通常我们会选择使用struct或enum来实现。

| Struct |

Struct的定义和初始化

定义结构体

下面定义了一个二维空间坐标的类型:

structPoint {

varx: Double

vary: Double

}

这个结构体包含两个名x和y的存储属性。存储属性是被绑定和存储在结构体中的常量或变量。

初始化

结构体类型的逐一初始化

所有的结构体都有一个自动生成的成员逐一构造器

varpointA = Point(x:10, y:20)

默认初始化

我们也可以在定义的时候直接给属性初始化

structPoint {

varx =0.0

vary =0.0

}

varpointB = Point()

使用这种方法,必须给每一个属性指定默认值。因为Swift中要求init方法必须初始化自定义类型每个属性。如果无法做到,我们可以自定义逐一初始化方法。

structPoint {

varx : Double

vary :Double

init(_ x : Double =0.0, y : Double =0.0)

{

self.x = x

self.y = y

}

}

当我们自定义init方法之后,Swift将不会再自动创建逐一初始化方法。

Struct 值类型本质

varpointB = Point(200, y:100)

varpointC = Point(100, y:200) {

didSet {

print("\(pointC)")

}

}

pointC = pointB

// Point(x: 200.0, y: 100.0)

pointC.x =200

//Point(x: 200.0, y: 100.0)

通过didSet观察pointC的变化。当修改pointC变量值时,控制台输出Point(x: 200.0, y: 100.0), 但是,修改pointC的修改某个属性,也会触发didSet。

这就是值语义的本质:即使字面上修改了pointC变量的某个属性,但实际执行的逻辑是重新给pointC赋值一个新的Point对象。

为Struct添加方法

给struct添加的方法,默认的都是只读的。计算Point之间的距离

extension Point {

func distance(to: Point) -> Double {

letdistX = self.x - to.x

letdistY = self.y - to.y

returnsqrt(distX * distX + distY * distY)

}

}

pointC.distance(to: Point(0, y: 0))

当我们定义一个移动X轴坐标点的方法时,会导致编译错误:

extensionPoint {

funcmove(to: Point) {

self= to

}

}

这里提示self is immutable , 必须使用mutating修饰这个方法, Swift编译器就会在所有的mutating方法第一个参数的位置,自动添加一个 inout Self参数。

extension Point {

/* self: inout Self */

mutating funcmove(to: Point){

self = to

}

}

以上,是关于Struct类型的基本内容。

init方法的合成规则

值语义在struct上的表现

| Enum |

在Swift中,对enum做了诸多改进和增强,它可以有自己的属性,方法,还可以遵从protocol。

定义enum

定义了一个colorName枚举

enumColorName {

caseblack

casesilver

casegray

casewhite

casered

//.... and so on ....

}

// 也可以写在同一行上,用逗号隔开:

enumMonth {

casejanuary, februray, march,

april, may, june, july,

august, september, october,

november, december

}

使用

letblack = ColorName.black

letjan = Month.january

注意:

与C和Objective-C不同,Swift的枚举成员在被创建时不会被赋予一个默认的整数值。上面定义的枚举成员是完备的值,这些值的类型就是定义好的枚举

ColorName或Month。

理解Enum的“Value”

case 本身就是值

funcmyColor(color: ColorName) -> String{

switchcolor {

case.black:

return"black"

case.red:

return"red"

default:

return"other"

}

}

注意

color的类型可以通过type inference推导出是ColorName。因此,可以省略enum的名字。

当Switch...case...将color的所有的值都列举出来时,可以省略default。

绑定值(raw values)

在Swift中,enum默认不会为case绑定一个整数值。但是我们可以手动的绑定值,这个“绑定”来的值,叫做raw values。

enumDirection : Int {

caseeast

casesouth

casewest

casenorth

}

现在定义Direction,Swift就会依次把case绑定上值。

leteast = Direction.east.rawValue// 0

关联值(Associated value)

在Swift中, 我们可以给每一个case绑定不同类型的值,我们管这种值叫做Associated value。

定义了一个表示CSSColor的enum:

enumCSSColor {

casenamed(ColorName)

casergb(UInt8,UInt8,UInt8)

}

使用:

varcolor1 = CSSColor.named(.black)

varcolor2 = CSSColor.rgb(0xAA,0xAA,0xAA)

switchcolor2 {

caselet.named(color):

print("\(color)")

case.rgb(letr,letg,letb):

print("\(r), \(g), \(b)")

}

注意:

提取”关联值“的内容时,可以把

let和var写在case前面或者后面。例如:named和rgb。

协议和方法(Protocol and Method)

在Swift中,enum和其他的命名类型一样,也可以采用protocol。

例如:给CSSColor添加一个文本表示。

extension CSSColor: CustomStringConvertible {

vardescription:String{

switchself {

case.named(letcolorname):

returncolorname.rawValue

case.rgb(letred,letgreen,letblue):

returnString(format:"#%02X%02X%02X", red, green, blue)

}

}

}

结果:

letcolor3 = CSSColor.named(.red)

letcolor4 = CSSColor.rgb(0xBB, 0xBB, 0xBB)

print("color3=\(color3), color4=\(color4)")

//color3=red, color4=#BBBBBB

|  什么是Copy on write (COW) ?|

COW是一种常见的计算机技术,有助于在复制结构时提高性能。例如:一个数组中有1000个元素,如果你复制数组到另一个变量,Swift将复制全部的元素,即使最终两个数组的内容相同。

这个问题可以使用COW解决:当将两个变量指向同一数组时,他们指向相同的底层数据。两个变量指向相同的数据可能看起来矛盾。解决方法:当修改第二个变量的时候,Swift才会去复制一个副本,第一个不会改变。

通过延迟复制操作,直到实际使用到的时候 才去复制,以此确保没有浪费的工作。

注意:COW是特别添加到Swift数组和字典的功能,自定义的数据类型不会自动实现。

值类型和引用类型(Value vs. Reference Type)

Class和Struct有很多相似的地方,他们都可以用来自定义类型、都可以有属性、都可以有方法。作为Swift中的引用类型,class表达的是一个具有明生命周期的对象,我们关心的是类的生命周期。而值类型,我关注的是值本身。

差异对比

引用类型必须明确指定init方法

Swift中

class不会自动生成init方法。如果不定义编译器报错。

引用类型关注的是对象本身

Circle (定义为Class)

vara = Circle()

a.radius =80

varb = a

a.radius =1000

b.radius// 1000

Circle(定义为Struct)

vara = Circle()

a.radius =80

varb = a

a.radius =1000

b.radius// 80

使用值类型创建新对象时,将复制;使用引用类型时,新变量引用同一个对象。这是两者的关键区别。

引用类型的默认值是可以修改的

我们之前提到过,给

struct添加的方法,默认的都是只读的。如果要修改必须用mutating来修饰。class中则不同,我们可以直接给 self赋值。

| Class |

理解class类型的各种init方法

由于class之间可以存在继承关系,因此它的初始化过程要比struct复杂,为了保证一个class中的所有属性都被初始化,Swift中引入一系列特定规则。

classPoint2D{

varx : Double

vary : Double

}

这项写是不行了,因为没有定义初始化方法。

指定构造器(Designated init)

上面的Point2D有一个默认的初始化方法,有两种办法:第一种给每一个属性都添加默认值。

classPoint2D{

varx : Double =0

vary : Double =0

}

letorigin = Point2D()

这种方法只能创建一个固定的class。另外一种,添加一个memberwise init 方法

classPoint2D{

varx : Double =0

vary : Double =0

init(x: Double, y: Double) {

self.x = x

self.y = y

}

}

添加个一个memberwise init方法,我们可以使用

letpoint = Point2D(x: 1, y: 1)

但是,如果你现在使用

letpoint = Point2D()// Error

结果会导致编译错误。因为,我们接手了init的定义后,编译就不会插手init工作。所以,在定义init方法时添加默认参数, 我们称这种初始化为 designated init。

classPoint2D{

varx : Double =0

vary : Double =0

init(x: Double =0, y: Double =0) {

self.x = x

self.y = y

}

}

| 便利构造器 (convenience init)|

classPoint2D{

varx : Double =0

vary : Double =0

init(x: Double =0, y: Double =0) {

self.x = x

self.y = y

}

convenience init(at: (Double, Double) ) {

self.init(x: at.0, y: at.1)

}

}

使用convenience关键字修改;

必须调用designated init完成对象的初始化;如果直接调用self.x或self.y,会导致编译错误。

可失败构造器 (Failable init )|

classPoint2D{

// ....

convenience init?(at: (String,String)) {

guardletx = Double(at.0),lety = Double(at.1)else{

returnnil

}

self.init(at:(x, y))

}

}

由于String tuple版的init可能失败,所以需要用init?形式定义。在实现里面,如果String无法转换为成Double, 则返回nil。

注意:

严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此,

return nil表示构造失败,而不能return表示成功。

类的继承和构造过程 |

当类之间存在继承关系的时候,为了保证派生类和基类的属性都被初始化,Swift采用以下三条规则限制构造器之间的代理调用:

指定构造器必须调用其直接父类的指定构造器

便利构造器必须调用同类中定义的其它构造器

便利构造器必须最终导致一个指定构造器被调用

简单说:

指定构造器必须总是向上代理

便利构造器必须总是横向代理

init的继承 |

classPoint3D:Point2D{

varz: Double =0

}

letorigin3D = Point3D()

letpoint31 = Point3D(x:1, y:1)

letpoint33 = Point3D(at: (2,3))// 继承基类 convenience init

如果派生类没有定义任何designated initializer,那么它将自动继承所有基类的designated initializer。

如果一个派生类定义了所有基类的designated init,那么它将自动继承基类所有的convenience init。

重载init方法 |

classPoint3D:Point2D{

varz:Double

init(x: Double =0, y: Double =0, z: Double =0)

{

self.z = z

super.init(x: x, y: y)

}

}

在派生类自定义designated init, 表示明确控制派生类的初始化构造过程, Swift 就不会干涉构造过程。那么,之前创建Point3D就会出现错误。

letpoint33 = Point3D(at: (2,3))// Error

如果想让Point3D从Point2D继承所有的convenience init,只有在派生类中实现所有的designated init方法。

classPoint3D:Point2D{

varz:Double

init(x: Double =0, y: Double =0, z: Double =0)

{

self.z = z

super.init(x: x, y: y)

}

overrideinit(x: Double, y: Double){

// 注意先后顺序

self.z =0

super.init(x: x, y: y)

}

}

此时,就可以正常工作了。只要派生类拥有基类所有的designated init方法,他就会自动获得所有基类的convenience init方法。另外,重载基类convenience init方法,是不需要override关键字修饰的。

两段式构造过程 |

Swift为了保证在一个继承关系中,派生类和基类的属性都可以正确初始化而约定的初始化机制。简单来说,这个机制把派生类的初始化过程分成了两个阶段。

阶段一:从派生类到基类,自下而上让类的每个属性有初始值

阶段二:所有属性都有初始值之后,从基类到派生类,自上而下对类的每个属性进行进一步加工。

两段式构造过程让构造过程更安全,同时整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问,也可以防止属性被另外一个构造器意外赋予不同的值。

相关推荐 |

推荐相关课程:

>swift实战项目开发今日头条

关于Swift中Struct,Class和Enum的那些事儿_第4张图片

感谢您一直以来的关注,欢迎在文章下方留言评论

巴士官网:www.apkbus.com

相关实战开发课程:qiantao.ke.qq.com

关于Swift中Struct,Class和Enum的那些事儿_第5张图片

你可能感兴趣的:(关于Swift中Struct,Class和Enum的那些事儿)