swift-枚举、结构体和类

295 阅读14分钟

继续吧,这一节可以属于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语言由于其安全性的特征,要求结构体和类必须在构造方法结束前完成其中存储属性(懒加载属性除外)的构造,因此在设计类时,可以采用两种方式来处理存储属性:

  1. 声明存储属性时直接设置其初始默认值
  2. 在构造方法中对存储属性进行构造或设置默认值

但是,如果类或者结构体中所有的存储属性都有初始默认值,那么就不需要显式的提供任何构造方法,编译器会自动生成一个无参的构造方法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!

错误处理

错误处理的三种方式:

  1. 使用do-catch语句处理。完全处理错误。
  2. 使用try?,有错误,返回nil。无错误,返回值。
  3. 使用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()