MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Swift枚举与结构体实战

2022-10-241.4k 阅读

Swift枚举实战

枚举基础回顾

在Swift中,枚举是一种强大的自定义数据类型,它允许我们定义一组相关的值。与C和Objective - C不同,Swift的枚举更加灵活和强大,不仅仅局限于整数值。

例如,定义一个表示星期几的枚举:

enum Weekday {
    case sunday
    case monday
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
}

这里我们使用enum关键字定义了Weekday枚举,它包含了七个不同的case,分别代表一周中的每一天。

关联值枚举

简单关联值示例

枚举可以关联值,这使得枚举更加灵活。例如,定义一个表示交通方式及其速度的枚举:

enum Transportation {
    case car(speed: Int)
    case bicycle(speed: Int)
    case train(speed: Int)
}

这里carbicycletrain都关联了一个Int类型的速度值。我们可以这样使用它:

let myCar = Transportation.car(speed: 60)
let myBike = Transportation.bicycle(speed: 15)

处理关联值

当我们需要根据枚举的不同情况处理关联值时,可以使用switch语句。例如:

func describeTransport(_ transport: Transportation) {
    switch transport {
    case let.car(speed):
        print("I'm driving a car at \(speed) km/h.")
    case let.bicycle(speed):
        print("I'm riding a bicycle at \(speed) km/h.")
    case let.train(speed):
        print("The train is running at \(speed) km/h.")
    }
}

describeTransport(myCar)
describeTransport(myBike)

在这个switch语句中,我们使用let绑定了关联值,以便在case块中使用。

递归枚举

定义递归枚举

递归枚举是指枚举的某个case可以包含自身类型的实例。在Swift中,我们需要使用indirect关键字来定义递归枚举。例如,定义一个表示算术表达式的递归枚举:

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

这里additionmultiplication case是递归的,因为它们包含了ArithmeticExpression类型的实例。我们也可以在枚举定义前使用indirect关键字,使所有递归case生效:

indirect enum ArithmeticExpression2 {
    case number(Int)
    case addition(ArithmeticExpression2, ArithmeticExpression2)
    case multiplication(ArithmeticExpression2, ArithmeticExpression2)
}

计算递归枚举的值

对于这样的递归枚举,我们可以编写递归函数来计算表达式的值。例如:

func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let.number(value):
        return value
    case let.addition(left, right):
        return evaluate(left) + evaluate(right)
    case let.multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, five)

print(evaluate(product))

evaluate函数中,我们递归地处理additionmultiplication case,最终返回表达式的计算结果。

枚举在实际项目中的应用

网络请求状态管理

在一个网络请求频繁的应用中,我们可以使用枚举来管理网络请求的状态。例如:

enum NetworkRequestState {
    case idle
    case loading
    case success(data: Data)
    case failure(error: Error)
}

class NetworkManager {
    var requestState: NetworkRequestState = .idle
    
    func startRequest() {
        requestState = .loading
        // 模拟网络请求
        DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
            let success = Bool.random()
            if success {
                let mockData = "Mock response data".data(using: .utf8)!
                self.requestState = .success(data: mockData)
            } else {
                let mockError = NSError(domain: "NetworkError", code: 500, userInfo: nil)
                self.requestState = .failure(error: mockError)
            }
        }
    }
}

let manager = NetworkManager()
manager.startRequest()

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    switch manager.requestState {
    case .idle:
        print("Request is idle.")
    case .loading:
        print("Request is loading.")
    case let .success(data):
        if let response = String(data: data, encoding: .utf8) {
            print("Request success: \(response)")
        }
    case let .failure(error):
        print("Request failed: \(error)")
    }
}

这里NetworkRequestState枚举清晰地表示了网络请求可能处于的各种状态。NetworkManager类根据请求的进展更新requestState,然后通过switch语句可以方便地处理不同状态下的逻辑。

游戏中的角色状态管理

在游戏开发中,我们可以用枚举来管理角色的状态。比如一个角色扮演游戏中,角色可能有站立、行走、跳跃、攻击等状态:

enum CharacterState {
    case standing
    case walking(speed: CGFloat)
    case jumping(height: CGFloat)
    case attacking(type: String)
}

class Character {
    var state: CharacterState = .standing
    
    func changeState(to newState: CharacterState) {
        state = newState
        switch newState {
        case .standing:
            print("The character is standing.")
        case let .walking(speed):
            print("The character is walking at a speed of \(speed) units per second.")
        case let .jumping(height):
            print("The character is jumping to a height of \(height) units.")
        case let .attacking(type):
            print("The character is attacking with \(type) attack.")
        }
    }
}

let player = Character()
player.changeState(to: .walking(speed: 2.5))
player.changeState(to: .jumping(height: 5))

CharacterState枚举详细地定义了角色可能的状态,Character类通过changeState方法根据不同状态进行相应的处理和输出。

Swift结构体实战

结构体基础

结构体定义

结构体是Swift中一种重要的自定义数据类型,它用于封装相关的变量和函数。结构体是值类型,当被赋值或传递时,会进行值拷贝。

例如,定义一个表示点的结构体:

struct Point {
    var x: Int
    var y: Int
}

这里Point结构体包含两个Int类型的属性xy,用于表示点在坐标系中的位置。

结构体实例化

我们可以通过以下方式实例化Point结构体:

let myPoint = Point(x: 10, y: 20)

这里我们使用指定的初始化器,通过名称指定属性的值来创建Point的实例。

结构体属性

存储属性

存储属性就是结构体中用于存储值的变量。在Point结构体中,xy就是存储属性。存储属性可以是常量(let)或变量(var)。

例如,如果我们希望Point的某个坐标不可变,可以这样定义:

struct ImmutablePoint {
    let x: Int
    var y: Int
}

let fixedPoint = ImmutablePoint(x: 5, y: 10)
// fixedPoint.x = 15 // 这行代码会报错,因为x是常量
fixedPoint.y = 15

计算属性

计算属性不存储实际的值,而是通过一个getter方法(也可以有setter方法)来计算值。例如,定义一个表示矩形的结构体,并添加一个计算属性来获取矩形的面积:

struct Rectangle {
    var width: Int
    var height: Int
    
    var area: Int {
        get {
            return width * height
        }
    }
}

let myRectangle = Rectangle(width: 5, height: 10)
print("The area of the rectangle is \(myRectangle.area)")

这里area是一个只读的计算属性,通过getter方法返回矩形的面积。如果我们希望它是可读写的,可以这样定义:

struct AdjustableRectangle {
    var width: Int
    var height: Int
    
    var area: Int {
        get {
            return width * height
        }
        set(newArea) {
            if width != 0 {
                height = newArea / width
            }
        }
    }
}

var adjustableRect = AdjustableRectangle(width: 5, height: 10)
print("Original area: \(adjustableRect.area)")
adjustableRect.area = 50
print("New height: \(adjustableRect.height)")

在这个可读写的计算属性area中,setter方法根据新的面积值调整height属性。

结构体方法

实例方法

实例方法是属于结构体实例的函数。例如,给Point结构体添加一个方法,用于移动点的位置:

struct MovablePoint {
    var x: Int
    var y: Int
    
    func move(by dx: Int, dy: Int) {
        x += dx
        y += dy
    }
}

var myMovablePoint = MovablePoint(x: 10, y: 20)
myMovablePoint.move(by: 5, dy: 10)
print("New position: (\(myMovablePoint.x), \(myMovablePoint.y))")

这里move方法是MovablePoint结构体的实例方法,它可以修改结构体实例的属性值。

变异方法

由于结构体是值类型,在实例方法中默认不能修改结构体的属性,除非使用mutating关键字。例如,我们可以将上面的move方法改写成变异方法,直接在方法内修改属性:

struct AnotherMovablePoint {
    var x: Int
    var y: Int
    
    mutating func move(by dx: Int, dy: Int) {
        x += dx
        y += dy
    }
}

var anotherPoint = AnotherMovablePoint(x: 10, y: 20)
anotherPoint.move(by: 5, dy: 10)
print("New position of another point: (\(anotherPoint.x), \(anotherPoint.y))")

这里move方法被标记为mutating,这样它就可以修改AnotherMovablePoint实例的属性。

类型方法

类型方法是属于结构体类型本身的方法,而不是属于结构体实例。我们使用static关键字来定义类型方法。例如,定义一个表示数学运算的结构体,并添加一个类型方法来计算两个整数的和:

struct MathOperations {
    static func add(_ a: Int, _ b: Int) -> Int {
        return a + b
    }
}

let sumResult = MathOperations.add(5, 10)
print("The sum is \(sumResult)")

这里addMathOperations结构体的类型方法,我们通过结构体类型直接调用它。

结构体在实际项目中的应用

数据存储与管理

在一个简单的待办事项应用中,我们可以使用结构体来存储待办事项的信息。例如:

struct TodoItem {
    var title: String
    var isCompleted: Bool
    
    mutating func markAsCompleted() {
        isCompleted = true
    }
}

var todoList = [
    TodoItem(title: "Buy groceries", isCompleted: false),
    TodoItem(title: "Do laundry", isCompleted: false)
]

todoList[0].markAsCompleted()
for item in todoList {
    let status = item.isCompleted ? "Completed" : "Not completed"
    print("\(item.title): \(status)")
}

这里TodoItem结构体用于存储待办事项的标题和完成状态,markAsCompleted变异方法用于更新事项的完成状态。todoList数组用于管理多个待办事项。

图形渲染中的应用

在图形渲染相关的项目中,结构体可以用于表示图形的各种属性。例如,定义一个表示颜色的结构体:

struct Color {
    var red: CGFloat
    var green: CGFloat
    var blue: CGFloat
    var alpha: CGFloat
    
    static let black = Color(red: 0, green: 0, blue: 0, alpha: 1)
    static let white = Color(red: 1, green: 1, blue: 1, alpha: 1)
    
    func mix(with other: Color) -> Color {
        return Color(
            red: (red + other.red) / 2,
            green: (green + other.green) / 2,
            blue: (blue + other.blue) / 2,
            alpha: (alpha + other.alpha) / 2
        )
    }
}

let myColor = Color(red: 0.5, green: 0.3, blue: 0.8, alpha: 1)
let anotherColor = Color(red: 0.2, green: 0.6, blue: 0.1, alpha: 1)
let mixedColor = myColor.mix(with: anotherColor)
print("Mixed color: (\(mixedColor.red), \(mixedColor.green), \(mixedColor.blue), \(mixedColor.alpha))")

这里Color结构体表示颜色的RGBA值,包含了存储属性和实例方法mix用于混合两种颜色,还定义了两个类型属性blackwhite表示常见的颜色。

枚举与结构体的结合使用

在实际开发中,枚举和结构体常常结合使用以实现更复杂的功能。例如,在一个简单的绘图应用中,我们可以定义一个枚举表示图形类型,再用结构体表示每种图形的属性。

enum ShapeType {
    case circle
    case rectangle
}

struct Circle {
    var radius: CGFloat
    var center: Point
}

struct Rectangle {
    var width: CGFloat
    var height: CGFloat
    var origin: Point
}

struct Shape {
    var type: ShapeType
    var properties: Any
    
    func area() -> CGFloat {
        switch type {
        case .circle:
            if let circle = properties as? Circle {
                return CGFloat.pi * circle.radius * circle.radius
            }
        case .rectangle:
            if let rect = properties as? Rectangle {
                return rect.width * rect.height
            }
        }
        return 0
    }
}

let myCircle = Circle(radius: 5, center: Point(x: 10, y: 10))
let myRectangle = Rectangle(width: 10, height: 20, origin: Point(x: 0, y: 0))

let circleShape = Shape(type: .circle, properties: myCircle)
let rectShape = Shape(type: .rectangle, properties: myRectangle)

print("Circle area: \(circleShape.area())")
print("Rectangle area: \(rectShape.area())")

这里ShapeType枚举定义了图形的类型,CircleRectangle结构体分别表示圆形和矩形的属性。Shape结构体结合了枚举和任意类型的属性,通过area方法根据不同的图形类型计算面积。这种结合方式使得代码结构清晰,易于扩展和维护。

通过以上对Swift枚举与结构体的实战讲解,希望你对它们在实际项目中的应用有了更深入的理解和掌握,能够在你的Swift开发工作中灵活运用这两种强大的数据类型。