继续吧,这一节可以属于swift进阶了。
枚举
Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为原始值),则该值的类型可以是字符串、字符,或是一个整型值或浮点数。
定义
枚举可定义为不同类型,使用 enum 关键词来创建枚举并且把它们的整个定义放在一对大括号内:
enum TestEnmu {
// case A,B,C 多个成员值可以出现在同一行上,用逗号隔开
case A
case B
case C
}
print(TestEnmu.C)
func play(param:TestEnmu) {
if param == TestEnmu.A {
print("a")
} else if param == TestEnmu.B {
print("b")
} else {
print("c")
}
}
play(param: TestEnmu.B)
与 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的TestEnmu中,A,B,C不会被隐式的赋值为0,1,2,这些枚举成员本身就是一个完备的值,并且类型也明确为TestEnmu类型。
上面示例中param已经被明确声明为TestEnmu类型,使用时,还可以省略枚举类型名,用点语法取值
param == .A。
//赋初始值
enum TestEnmu1:Int {
case A = 1
case B = 2
}
print(TestEnmu1.A)//A
print(TestEnmu1.A.rawValue)//1
枚举嵌套
enum LoopDoll{
enum Disneyland{
case A
case B
}
enum Barbie{
case X
case Y
}
}
关联值
你可以定义 Swift 枚举来存储任意类型的关联值,如果需要的话,每个枚举成员的关联值类型可以各不相同。定义枚举时不需要提供任何关联值,只是定义,使用时,把枚举赋值给一个常量或者变量,那这个常量或变量就可以存储这个枚举以及他的关联值了。
enum TestEnum {
case name(String)
case age(Int)
case xy(Int,Int)
}
func play(param:TestEnum) {
switch param {
case TestEnum.name("hello"):
print("hello")
case TestEnum.age(20):
print(20)
case TestEnum.xy(10, 20):
print(10,20)
default:
print("not match")
}
}
play(param: TestEnum.name("swift"))//not match
play(param: TestEnum.age(20))
在判断一个枚举类型的值时,switch 语句必须穷举所有情况,不然将无法通过编译,当不需要匹配每一个值时,可以放在default分支来处理。
你可以在 switch 的 case 分支代码中提取每个关联值作为一个常量(用 let 前缀)或者作为一个变量(用 var 前缀)来使用:
enum Name{
case first(String)
case last(String)
}
var name = Name.first("Jiang")
switch name {
case .first(let s):
print("Your first name is \(s)")
case .last(let s):
print("Your last name is \(s)")
}
// Prints Your first name is Jiang
枚举成员当然也可以赋初始值,这些初始值类型必须相同。原始值和关联值是不同的,原始值是在定义时被赋值的,且始终不会变化,关联值是创建一个基于枚举成员的变量时,才需要设置的值,关联值是可以改变的。
enum TestEnmu1:Int {
case A = 1
case B = 2
}
print(TestEnmu1.A)//A
print(TestEnmu1.A.rawValue)//1
遍历
想要遍历枚举的所有成员,需要使枚举遵循CaseIterable协议,swift会生成一个allCases的属性,是包含了枚举的所有成员的一个集合,集合中的元素,是枚举类型的实例。
enum TestEnmu:CaseIterable {
case A
case B
case C
}
print(type(of: TestEnmu.allCases))//Array<TestEnmu>
for item in TestEnmu.allCases {
print(item)
}
for index in 0..<TestEnmu.allCases.count {
print(TestEnmu.allCases[index])
}
结构体 和 类
与其他编程语言所不同的是,Swift 并不要求你为自定义的结构体和类的接口与实现代码分别创建文件。你只需在单一的文件中定义一个结构体或者类,系统将会自动生成面向其它代码的外部接口。
Swift 中结构体和类有很多共同点。两者都可以:
- 定义属性用于存储值
- 定义方法用于提供功能
- 定义下标操作用于通过下标语法访问它们的值
- 定义构造器用于设置初始值
- 通过扩展以增加默认实现之外的功能
- 遵循协议以提供某种标准功能
类还有如下特有属性需要注意一下:
- Inheritance 继承,类有可继承性。
- Deinitialization 析构,通过析构函数来实现,类实例消毁时调用。
- Type Casting 类型转换,判断实例的类型,通过is, as将其看做是父类或者子类的实例。
- Automatic Reference Counting 自动引用计,所以纳入了自动内存管理机制。
需要注意的是,Swift语言中数据类型分为值类型和引用类型。结构体,枚举等除类以外的所有数据类型都属于值类型。只有类是引用类型的。值类型和引用类型最大的区别在于当进行数据传递时,值类型总是被赋值,而引用类型不会被复制,引用类型是通过引用计数来管理生命周期的。 在swift语言中,array、string、dictionary等这些数据类型都是采用结构体来实现,所以在数据传递时总会被复制,但是开发者不需要担心由此引发的性能问题,swift语言幕后会控制只有绝对需要时才会进行真正的复制操作,以确保性能最优。
定义
struct Student {
var name = "unknow"
var age = 0
var score = 0.0
var isPass = false
static var schoolName = "JLD大学"
//初始化器
init() {
}
init(name:String,age:Int, score:Double) {
self.name = name
self.age = age
self.score = score
if sel true
} else {f.score >= 60 {
self.isPass =
self.isPass = false
}
}
//函数
func getName() -> String {
return self.name
}
func getAge() -> Int {
return self.age
}
func getScore() -> Double {
return self.score
}
func getIsPass() -> Bool {
return self.isPass
}
//添加mutating 关键字 才可以在函数中修改自己的属性
mutating func setScore(score:Double) -> Void {
self.score = score
if self.score >= 60 {
self.isPass = true
} else {
self.isPass = false
}
}
}
var a:Student = Student.init(name: "lily", age: 20, score: 99)
var b:Student = Student()
//获取结构体中 各种属性的值
print(a.getName())
print(Student.schoolName) //直接用结构体名称取调用
print(a.age)
a.setScore(score: 20)//set方法
print(a.score)
Student.schoolName = "QH大学"//修改结构体中的static修饰的变量
//print(a.schoolName) 实例a调用不了schoolName
属性
Swift中的属性可以分为存储属性和计算属性两类。区别在于存储属性用于描述存储值,在内存中需要为这个属性分配响应的空间,而计算属性用于描述计算过程并获取计算结果,不需要空间来存储。
存储属性可以是常量也可以是变量,常量存储属性在类实例被构造出来之后就不能进行修改了
struct Test {
var a = 10
let b = 100
}
var t1 = Test()
print(t1.a)
print(t1.b)
t1.a = 100
//t1.b = 200 //Cannot assign to property: 'b' is a 'let' constant
let t2 = Test()
//t2.a = 22//Cannot assign to property: 't2' is a 'let' constant
实例通过点语法调用其属性,需要注意,变量类型的属性可以修改,常量类型的属性不可以修改。但是对于值类型实例,如果实例是常量接收的,则其中变量的属性也不可以修改。而对于引用类型实例,无论实例是变量还是常量。
struct Point {
var x:Double
var y:Double
}
class PointC {
var x:Double
var y:Double
init(x:Double, y:Double) {
self.x = x
self.y = y
}
}
let point = Point(x: 2, y: 1)
//错误示范
point.x = 33//Cannot assign to property: 'point' is a 'let' constant
let point2 = PointC(x: 3, y: 3)
point2.x = 88
与存储属性相比,计算属性更像是运算过程:
class Circle {
var r:Double = 0.0
var center:(Double, Double)
var l:Double {
get {
return 2 * r * Double.pi
}
set {
r = newValue / 2 / Double.pi
}
}
var s:Double {
get {
return Double.pi * r * r
}
set {
r = sqrt(newValue / Double.pi)
}
}
init(r:Double, center:(Double, Double)) {
self.r = r
self.center = center
}
}
计算属性可以定义get和set方法,其实属性本身并没有存储值,只是作为一个接口向外界提供经过计算之后的数据。get方法是必不可少的,而set方法是可选的。只有get方法时,该属性是只读的。
Property Observer 属性观察
属性观察用于监听存储属性赋值的过程,在进行属性的构造或初始化是,是不会调用到属性观察的方法,初始化后第二次为属性赋值开始,才会被监听到属性的改变。
class observer {
var number: Int = 0 {
willSet(value) {
print("~~~", value, number)
}
didSet {
print("...", number, oldValue) // oldValue 系统提供
}
}
}
let obj = observer()
obj.number = 10
// Prints
// ~~~ 10 0
// ... 10 0
只有存储属性可以设置属性监听,计算属性不可以。我理解是只要自定义了get和set方法,就不允许在监听属性的改变了。
subscript语法
Swift语言支持subscript 下标语法,为自定义的数据类型赋予通过下标访问的功能,就是使用类似字典或数组的方式赋值与取值。
class City{
var province:String
var info:Dictionary<String, String>?
init(province: String){
self.province = province
self.info = [:]
}
// 这里是使用下标语法
subscript(city: String) -> String {
get {
return self.info?[city] ?? ""
}
set {
self.info?[city] = newValue
}
}
}
let city = City(province:"HuBei")
city["Daye"] = "Daye is a top 100 city"
print(city.province)
// Prints HuBei
print(city["Daye"])
// Prints Daye is a top 100 city
使用subscript来定义下标,subscript实现部分和计算属性类似,必须实现一个get代码块和一个可选的set代码块。subscript中参数的个数和类型,以及返回值的类型都可以由开发者自由定义。
构造器
类与结构体创建属性与方法的代码基本都一样,不同的是,在结构体中,开发者并不需要提供构造方法,结构体会根据属性自动生成一个构造方法,而类则要求开发者自己提供构造方法。
swift语言由于其安全性的特征,要求结构体和类必须在构造方法结束前完成其中存储属性(懒加载属性除外)的构造,因此在设计类时,可以采用两种方式来处理存储属性:
- 声明存储属性时直接设置其初始默认值
- 在构造方法中对存储属性进行构造或设置默认值
但是,如果类或者结构体中所有的存储属性都有初始默认值,那么就不需要显式的提供任何构造方法,编译器会自动生成一个无参的构造方法init()。
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
let vga = Resolution(width: 640, height: 480)
对于值类型,比如结构体,如果开发者提供了一个自定义的构造方法,那么系统生成的构造方法将会失效,防止调用自定义构造方法时却误调用到了系统生成的构造方法中。
继承是类独有的特性,子类和父类通过继承建立关系,并且子类可以对父类进行扩展,可以通过super调用父类的方法,通过override重写父类方法。swift中还提供了一个final关键字,通过final修饰的属性和方法,不允许被子类复写,而被final修饰的类,不可以被继承。
值传递
值类型是这样一种类型,当它被赋值给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。
实际上,Swift 中所有的基本类型:整数(integer)、浮点数(floating-point number)、布尔值(boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,其底层也是使用结构体实现的。
Swift 中所有的结构体和枚举类型都是值类型。这意味着它们的实例,以及实例中所包含的任何值类型的属性,在代码中传递的时候都会被复制。
结构体实例 是值传递
struct Test {
var a = 10
}
var t1 = Test()
print(t1.a)//10
var t2 = t1
print(t2.a)//10
t2.a = 100
print(t1.a)//10
print(t2.a)//100
与结构体不同,类是引用类型。与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,使用的是已存在实例的引用,而不是其拷贝。
Extension 拓展
支持Extension 拓展。使用关键词extension对已有结构体与类实现新的功能。
extension Int {
func add(_ number:Int)-> Int {return self + number }
}
print(8.add(10))
// Prints 18
Delegate 委托 、 Protocol 协议
/** 1. 定义一下协议 **/
protocol MyDelegate {
func doSomething(str:String) -> ()
}
/** 2. 应用这个协议 **/
class Part {
var delegate:MyDelegate?
func show() {
delegate?.doSomething(str: "Hello world!")
}
}
/** 3. 实现这个协议 **/
class Layout: MyDelegate {
let part:Part
init(){
self.part = Part()
part.delegate = self;
}
func doSomething(str: String) {
print("Layout: " + str)
}
}
let layout = Layout()
layout.part.show()
// Prints Layout: Layout: Hello world!
静态属性和静态方法
static 静态属性,也叫类属性,理论是一种全局数据,通过定义的类名称或结构体名称来调用。与前面说到的其他属性(也可成为实例属性)的区别就是,实例属性需要构造一个类实例才可进行调用。
struct SomeStruct {
static var store = "Some value."
static var compute: Int {
return 1
}
}
enum SomeEnum {
static var store = "Some value."
static var compute: Int {
return 6
}
}
class SomeClass {
static var store = "Some value."
static var compute: Int {
return 27
}
}
print(SomeStruct.store, SomeStruct.compute) // Prints Some value. 1
print(SomeEnum.store, SomeEnum.compute) // Prints Some value. 6
print(SomeClass.store, SomeClass.compute) // Prints Some value. 27
同样,static修饰的方法叫做静态方法,也叫类方法,可以用类名直接调用,却不可以被子类重写。这里换要注意self的意义,在实例方法中,self代表这个实例,也可以通过self调用其他的实例属性。而在类方法中,self则代表这个类,只能调用这个类的类属性。
修饰符
- private 当前类内使用
- public 可以在其他作用域被访问,但是不能在override、extension中被访问
- open 可以在其他作用域被访问
语法糖
在最后再补充几个语法糖
懒加载
通过下面的示例,先理解一下什么叫懒加载:
let numbers = 1...3
/** 常规 **/
let map1 = numbers.map { (i: Int) -> Int in
print("...",i)
return i + 3
}
// Prints
// ... 1
// ... 2
// ... 3
/** lazy,这事就这么定了,没有处理 **/
let map2 = numbers.lazy.map { (i: Int) -> Int in
print("~~~",i)
return i + 3
}
// 没有被调用,所以没有输出
/** 遍历map1 **/
for i in map1 {
print("...",i)
}
// Prints
// ... 4
// ... 5
// ... 6
/** 遍历map2, 现在一起处理吧 **/
for i in map2 {
print("~~~",i)
}
// Prints
// ~~~ 1
// ~~~ 4
// ~~~ 2
// ~~~ 5
// ~~~ 3
// ~~~ 6
在class中使用懒加载,是我们开发中比较常用到的场景:
class ViewController: NSViewController {
lazy var editScrollView : NSScrollView = {
let _editScrollView = NSScrollView()
_editScrollView.hasVerticalScroller = true
_editScrollView.hasVerticalRuler = false
_editScrollView.hasHorizontalScroller = false
_editScrollView.contentInsets = NSEdgeInsetsMake(10, 10, 10, 10)
_editScrollView.contentView.copiesOnScroll = true
return _editScrollView
}()
}
懒加载的属性在类实例构造的时候,并不进行构造或初始化,只有当开发者调用到这个属性时,此属性才完成构造或初始化。
懒加载并不是线程安全的,如果在多个线程中对懒加载的属性进行调用,不能确保其只被构造一次。
可选型链 ?!
“?”是判断行不行,不行就返nil,行就继续。“!”是一定可以的,不行则返回系统错误。
/** 多层的Dictionary, 用Go里想死的心都有,在这里就简单多了,那一层没有,就直接回空。 **/
var countrys = ["china" : ["hubai" : ["daye" : ["ID1997":["name":"JiangYouhua"]]]]];
if let name = countrys["china"]?["hubai"]?["daye"]?["ID1997"]?["name"]{
print("Your name is \(name)")
}else{
print("Without your name")
}
// Prints Your name is JiangYouhua
/** 类里的应用 **/
class Stationery{
let name: String
init(name: String) {
self.name = name
}
}
class Student{
let name: String
init(name: String){
self.name = name
}
var stationery : Stationery?
}
let student = Student(name: "Jiang YouHua")
student.stationery = Stationery(name: "pen")
if let stationery = student.stationery?.name{
print("\(student.name)'s \(stationery)!")
}else{
print("Error")
}
// Prints Jiang YouHua's pen!
错误处理
错误处理的三种方式:
- 使用do-catch语句处理。完全处理错误。
- 使用try?,有错误,返回nil。无错误,返回值。
- 使用try!,有错误,抛出系统错误,无错误,返回值。
/** 一个购物的示例 **/
enum ShopError: Error { // 定义购买错误
case notLoggedError // 未登录错误
case notMemberError // 不是会员错误
case freeToUseError // 免费使用
case insufficientAmountError(score:Int) // 积分不足错误
}
struct User{ // 定义用户
var uid:Int // 用户ID
var role:Int // 权限值,权限值为0,则为普通用户,非会员
var score:Int // 积分值
}
class Shopping{ // 定义购买类
let user:User
var total:Int = 0
init(user:User){
self.user = user
}
// 处理购物单
func handleOrder(commodity:Dictionary<String, Int>)->(code:Int, info:String){
guard user.uid > 0 else {
return (code:1, info:"Not Logged in")
}
guard user.role > 0 else {
return (code:2, info:"Not a Member")
}
// 计算总价
for (_, i) in commodity {
self.total += i
}
guard self.total > 0 else {
return (code:3, info:"Free to Use")
}
guard user.score > total else {
return (code:3, info:"Insufficient Amount")
}
// TODO
return (code:0, info:"Purchase success")
}
// 提交购物单,处理服务器返回结果
func submitoOrder(commodity:Dictionary<String, Int>)throws->Int{
let result = self.handleOrder(commodity: commodity)
switch result.code {
case 1:
throw ShopError.notLoggedError
case 2:
throw ShopError.notMemberError
case 3 :
throw ShopError.insufficientAmountError(score:user.score)
default:
return self.total
}
}
}
/** 处理方式一:错误向上层转移,即未处理 **/
func buy1() throws {
let user = User(uid: 1997, role: 1, score: 0)
let order = ["pen":12,"book":17]
let shop = Shopping(user: user)
try shop.submitoOrder(commodity: order)
}
// buy1()
// 无法调用,因为上一层也需要处理错误
// Playground execution terminated: An error was thrown and was not caught.
/** 处理方式二:使用do-catch语句处理 **/
func buy2(){
let user = User(uid: 1997, role: 0, score: 0)
let order = ["pen":12,"book":17]
let shop = Shopping(user: user)
do {
try shop.submitoOrder(commodity: order)
}catch ShopError.notLoggedError{
print("You are not logged in, please login")
}catch ShopError.notMemberError{
print("You are not a member, please join the membership")
}catch ShopError.insufficientAmountError(let score){
print("Your score are not enough, the score is only 10 \(score)")
}catch{
print("Unexpected error: \(error).")
}
}
buy2()
// 处理了所有错误
// Prints You are not a member, please join the membership
/** 处理方式三:将错误作为可选值处理, **/
func buy3(){
let user = User(uid: 1997, role: 1, score: 200)
let order = ["pen":12,"book":17]
let shop = Shopping(user: user)
if let result = try? shop.submitoOrder(commodity: order){
print("Please pay \(result) yuan")
}
}
buy3()
// 有错就返回nil
// Prints Please pay 29 yuan
/** 处理方式四:断言错误不会发生 **/
func buy4(){
let user = User(uid: 1997, role: 1, score: 1000)
let order = ["pen":12,"book":17, "toy":479]
let shop = Shopping(user: user)
let result = try! shop.submitoOrder(commodity: order)
print("Please pay \(result) yuan")
}
buy4()