枚举
枚举为一组相关值定义了一个通用类型,从而可以让你在代码中类型安全地操作这些值。
Swift 中的枚举是具有自己权限的一类类型。
枚举语法
枚举我觉得一直算是我的一个盲点,认真学一学。
用 enum关键字来定义一个枚举,然后将其所有的定义内容放在一个大括号( {})中:
enum CompassPoint {
case north
case south
case east
case west
}
!注意,Swift不会给枚举的成员分配默认值,也就是说上述的north,south,east,west不是0,1,2,3。他们在自己的权限中都是合法的值并且是CompassPoint显示定义的类型。
多个成员的值可以出现在同一行。
赋值方法第一次写明枚举类型,往后的赋值直接用一个点即可。
var directionToHead = CompassPoint.west
directionToHead = .east
使用 Switch 语句来匹配枚举值
使用switch去匹配一个枚举类型的时候,要保证全覆盖,即每一个case对应的操作都要写出来,或者写个default:,因为这样可以保证枚举成员不会意外的被漏掉。
遍历枚举情况(case)
如果想让枚举成员能被遍历,在枚举类型后面写:CaseIterable来允许遍历,这样Swift就会暴露一个对应枚举类型的所有集合名为 .allCases
关联值
Swift中枚举类型的关联值用括号在成员后面括起来定义,并且与成员一块存储。在switch中也可以通过let读取出来。
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
原始值
枚举成员可以用相同类型的默认值预先填充(称为原始值)。
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
原始值与关联值不同。原始值是当你第一次定义枚举的时候,它们用来预先填充的值,正如上面的三个 ASCII 码。特定枚举成员的原始值是始终相同的。关联值在你基于枚举成员的其中之一创建新的常量或变量时设定,并且在你每次这么做的时候这些关联值可以是不同的。
隐式指定的原始值
当你在操作存储整数或字符串原始值枚举的时候,你不必显式地给每一个成员都分配一个原始值。
当整数值被用于作为原始值时,每成员的隐式值都比前一个大一。
当字符串被用于原始值,那么每一个成员的隐式原始值则是那个成员的名称。
可以用 rawValue属性来访问一个枚举成员的原始值。
从原始值初始化
原始值类型来定义一个枚举,那么枚举就会自动收到一个可以接受原始值类型的值的初始化器(叫做 rawValue的形式参数)然后返回一个枚举成员或者 nil 。
此时可以用if-let与switch搭配执行。
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
} else {
print("There isn't a planet at position \(positionToFind)")
}
递归枚举
枚举在对序号考虑固定数量可能性的数据建模时表现良好 递归枚举是拥有另一个枚举作为枚举成员关联值的枚举。 你可以在声明枚举成员之前使用 indirect关键字来明确它是递归的。
类与结构体
相同点: 定义属性用来存储值; 定义方法用于提供功能; 定义下标脚本用来允许使用下标语法访问值; 定义初始化器用于初始化状态; 可以被扩展来默认所没有的功能; 遵循协议来针对特定类型提供标准功能。
不同点: 继承允许一个类继承另一个类的特征; 类型转换允许你在运行检查和解释一个类实例的类型; 反初始化器允许一个类实例释放任何其所被分配的资源; 引用计数允许不止一个对类实例的引用。
定义语法 & 类与结构体实例
不说了用的很多
访问属性
不同于 Objective-C,Swift 允许你直接设置一个结构体属性中的子属性。
举个例子,假设someVideoMode是类VideoMode的一个实例,resolution是类Resolution的实例,同时也是someVideoMode的一个属性,那么就可以用下面这种方式对resolution的属性设置,而不用重新设置整个resolution属性到一个新值。
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
结构体类型的成员初始化器
所有的结构体都有一个自动生成的成员初始化器,所有成员都在里面。
结构体和枚举是值类型
值类型是一种当它被指定到常量或者变量,或者被传递给函数时会被拷贝的类型。
Swift 中所有的基本类型——整数,浮点数,布尔量,字符串,数组和字典——都是值类型,并且都以结构体的形式在后台实现。 – 在代码传递中总是被拷贝的。
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
事实上,他们在后台完全是两个不同的实例。
cinema.width = 2048
print("hd is still \(hd.width) pixels wide")
类是引用类型
不同于值类型,在引用类型被赋值到一个常量,变量或者本身被传递到一个函数的时候它是不会被拷贝的。相对于拷贝,这里使用的是同一个对现存实例的引用。
这也意味着用常量指向一个类实例,并不是说类实例里的属性不能改变,而是这个常量不能指向别的实例了。
特征运算符
有时候找出两个常量或者变量是否引用自同一个类实例非常有用,为了允许这样,Swift提供了两个特点运算符:
相同于 ( ===) 不相同于( !==)
相同于是判断两个变量或者常量是否引用同一个实例。
指针
不同于c,c++,oc,swift不用*指向内存地址,直接用变量或常量指向引用类型则相当于指针的作用。
合适选择结构
结构体的主要目的是为了封装一些相关的简单数据值;(封装简单数据) 当你在赋予或者传递结构实例时,有理由需要封装的数据值被拷贝而不是引用;(赋值的实例应当是拷贝的时候) 任何存储在结构体中的属性是值类型,也将被拷贝而不是被引用;(储存的类型也是值类型) 结构体不需要从一个已存在类型继承属性或者行为。(不需要继承)
否则还是使用类。
字符串,数组和字典的赋值与拷贝行为
Swift 的 String , Array 和 Dictionary类型是作为结构体来实现的 不同于OC的基础库这三个都是作为类型去实现的。
能够这么做的原因是因为后台 Swift 只有在需要这么做时才会实际去拷贝。Swift 能够管理所有的值的拷贝来确保最佳的性能,所有也没必要为了保证最佳性能来避免赋值。
属性
属性可以将值与特定的类、结构体或者是枚举联系起来。存储属性会存储常量或变量作为实例的一部分,反之计算属性会计算(而不是存储)值。计算属性可以由类、结构体和枚举定义。存储属性只能由类和结构体定义。
存储属性和计算属性通常和特定类型的实例相关联。总之,属性也可以与类型本身相关联。这种属性就是所谓的类型属性。
另外,你也可以定义属性观察器来检查属性中值的变化,这样你就可以用自定义的行为来响应。属性观察器可以被添加到你自己定义的存储属性中,也可以添加到子类从他的父类那里所继承来的属性中。
存储属性
在其最简单的形式下,存储属性是一个作为特定类和结构体实例一部分的常量或变量。即用var和let去定义。
常量结构体实例的存储属性
结构体是值类型,所以结构体作为属性一旦被复制就不可以被修改。
延迟存储属性
在属性前面加入一个lazy可以让属性懒加载,也就是用到的时候再分配内存,但注意一定要使用var,因为let要求常量在初始化之前就要取得值。
计算属性
除了存储属性,类、结构体和枚举也能够定义计算属性,而它实际并不存储值。相反,他们提供一个读取器和一个可选的设置器来间接得到和设置其他的属性和值。只能用var进行声明。
简写设置器(setter)声明
如果一个计算属性的设置器没有为将要被设置的值定义一个名字,那么他将被默认命名为 newValue 。
struct AlternativeRect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
缩写读取器(getter)声明
和函数的隐式省略return相同,就是当闭包里面只有一个表达式的时候不用写return。
struct CompactRect {
var origin = Point()
var size = Size()
var center: Point {
get {
Point(x: origin.x + (size.width / 2),
y: origin.y + (size.height / 2))
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
只读计算属性
一个有读取器但是没有设置器的计算属性就是所谓的只读计算属性。
可以省略get和小括号直接写出来。
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
属性观察者
属性观察者会观察并对属性值的变化做出回应。
可以在如下地方添加属性观察者: 你定义的存储属性; 你继承的存储属性; 你继承的计算属性。
可以选择将这些观察者或其中之一定义在属性上: willSet 会在该值被存储之前被调用。 didSet 会在一个新值被存储后被调用。
其中willSet和didSet都可以自己重新对参数进行定义,否则取默认值为newValue或者oldValue。didSet中的oldValue的值即为对应的被覆盖的值。
属性包装
可以再struct前面使用@propertyWrapper
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
要注意的点是结构里面一定要含有这个wrappedValue属性。 然后有两种使用这种属性包装的方式。
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
这种是用包装在定义属性的时候写好。
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { return _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
}
另外一种是直接访问对应的wrappedValue。
设定包装属性的初始值
可以再包装属性的结构体当中设置init函数去设定。 可以设置含有wrappedValue的init初始函数,那么在赋值的时候默认调用了对应的含有wrappedValue参数的构造函数。
通过属性包装映射值
可以再包装属性结构体内部定义一个projectedValue属性,任意类型,然后在外部通过在对应属性的前面加 $符号来调用他的映射值(即不用 $就是它原本的类型,用了就是它映射的类型)
全局变量和局部变量
全局变量是定义在任何函数、方法、闭包或者类型环境之外的变量。
全局常量和变量永远是延迟计算的,且不用加lazy标记符。
类型属性
static用法就和平时语法里面静态变量静态常量差不多
不同点:用class声明,则说明允许子类重写父类的实现。
方法
实例方法
类,枚举,结构内都可以有实例方法。
self 属性
在不会产生歧义的情况下写不写self问题都不大。默认调用当前实例的特定属性值。
在实例方法中修改值类型
结构体和枚举是值类型。默认情况下,值类型属性不能被自身的实例方法修改。
所以如果要在结构或者枚举的方法中对自己的值进行修改,就要在前面加上mutating的限定符。
在异变方法里指定自身
在mutating中异变方法可以指定整个实例给隐含的 self属性。 甚至可以用self = 新的量完成赋值,同时枚举类型也能在一边方法中通过switch self来编写方法。
类型方法
和static差不多,函数里面不能调用不是静态的属性。也是类的所有对象共用的方法。用class同样也是表示这是允许子类重写父类的实现。
下标
可以为一个类型定义多个下标,并且下标会基于传入的索引值的类型选择合适的下标重载使用。
下标语法
使用关键字 subscript 来定义下标,并且指定一个或多个输入形式参数和返回类型。
subscript(index: Int) -> Int {
get {
}
set(newValue) {
}
}
下标用法
用很多了可以不说了,记得Dictionary结构可以用下标复制nil来删除字典的对象。
下标选项
下标可以是任意类型任意数量的参数,但是不能用inout参数或者提供默认形式参数值。但规定好的形式参数类型可以提供默认参数值。(好好理解)
类型下标
同样的,可以定义整个类型的下标,用static关键字修饰,如果用class则代表允许子类对父类这个类型下标进行修改。
|