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

Swift面向对象编程基础

2022-06-051.4k 阅读

类与对象基础

在Swift的面向对象编程中,类(class)是构建程序的基本单元,它定义了一种新的数据类型,包含属性(properties)和方法(methods)。属性用于存储数据,而方法则用于定义可对数据执行的操作。

类的定义

定义一个类使用class关键字,后面跟着类名,类名通常采用大写字母开头的驼峰命名法(CamelCase)。例如,定义一个简单的Person类:

class Person {
    // 属性定义
    var name: String
    var age: Int

    // 构造函数
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    // 方法定义
    func introduce() {
        print("Hello, my name is \(name) and I'm \(age) years old.")
    }
}

在上述代码中,Person类有两个属性nameage,分别表示人的姓名和年龄。init函数是构造函数,用于初始化类的实例。introduce方法用于打印个人信息。

对象的创建与使用

创建类的实例(对象)时,使用类名加上括号,并传入构造函数所需的参数。例如:

let john = Person(name: "John", age: 30)
john.introduce()

上述代码创建了一个Person类的实例john,并调用其introduce方法,输出Hello, my name is John and I'm 30 years old.

类的属性

存储属性

存储属性是类中用于存储值的变量或常量。在前面的Person类中,nameage就是存储属性。存储属性可以是变量属性(使用var关键字定义),也可以是常量属性(使用const关键字定义)。常量属性一旦初始化后就不能再被修改。

计算属性

计算属性不直接存储值,而是通过一个getter方法来计算并返回一个值,还可以通过setter方法来设置新的值(对于只读计算属性,只有getter方法)。例如,定义一个Rectangle类,包含宽度和高度属性,以及一个计算面积的计算属性:

class Rectangle {
    var width: Double
    var height: Double

    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double {
        get {
            return width * height
        }
    }
}

let rect = Rectangle(width: 5.0, height: 3.0)
print("The area of the rectangle is \(rect.area)")

在上述代码中,area是一个只读计算属性,通过getter方法返回矩形的面积。

属性观察器

属性观察器可以在属性值发生变化时执行特定的代码。有两种属性观察器:willSetdidSetwillSet在属性值即将被设置时调用,didSet在属性值已经被设置后调用。例如,在Person类中添加一个属性观察器来监测age属性的变化:

class Person {
    var name: String
    var age: Int {
        willSet(newAge) {
            print("The age is about to change to \(newAge)")
        }
        didSet {
            if age != oldValue {
                print("The age has changed from \(oldValue) to \(age)")
            }
        }
    }

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let tom = Person(name: "Tom", age: 25)
tom.age = 26

上述代码中,当tomage属性值发生变化时,willSetdidSet中的代码会被执行,打印相应的信息。

类的方法

实例方法

实例方法是属于类实例的方法,它可以访问和修改实例的属性。在前面的Person类中,introduce方法就是一个实例方法。实例方法可以带有参数,也可以返回值。例如,在Rectangle类中添加一个方法来判断矩形是否为正方形:

class Rectangle {
    var width: Double
    var height: Double

    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }

    func isSquare() -> Bool {
        return width == height
    }
}

let squareRect = Rectangle(width: 4.0, height: 4.0)
let nonSquareRect = Rectangle(width: 5.0, height: 3.0)
print(squareRect.isSquare()) // 输出 true
print(nonSquareRect.isSquare()) // 输出 false

在上述代码中,isSquare方法是一个实例方法,它返回一个布尔值,表示矩形是否为正方形。

类型方法

类型方法是属于类本身的方法,而不是属于类的实例。在方法定义前加上static关键字来定义类型方法。例如,定义一个MathUtils类,包含一个类型方法来计算两个数的和:

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

let result = MathUtils.add(3, 5)
print("The sum is \(result)")

在上述代码中,add方法是一个类型方法,通过类名直接调用,而不需要创建类的实例。

继承

继承是面向对象编程的重要特性之一,它允许一个类从另一个类继承属性和方法。被继承的类称为父类(superclass),继承的类称为子类(subclass)。子类可以在继承的基础上添加新的属性和方法,或者重写父类的方法。

继承的定义

定义一个子类时,在类名后面加上冒号(:),然后跟上父类的名称。例如,定义一个Student类继承自Person类:

class Student: Person {
    var studentID: String

    init(name: String, age: Int, studentID: String) {
        self.studentID = studentID
        super.init(name: name, age: age)
    }

    override func introduce() {
        print("Hello, I'm \(name), a student with ID \(studentID), and I'm \(age) years old.")
    }
}

在上述代码中,Student类继承自Person类,添加了一个新的属性studentID,并重写了introduce方法以包含学生ID信息。在init构造函数中,通过super.init调用父类的构造函数来初始化继承的属性。

重写方法

当子类需要提供与父类不同的实现时,可以重写父类的方法。在重写方法前需要加上override关键字,以表明这是一个重写的方法。如上面Student类中重写的introduce方法。如果子类中没有使用override关键字而定义了与父类方法同名的方法,编译器会报错。

访问父类成员

在子类中,可以通过super关键字访问父类的属性和方法。例如,在Student类的introduce方法中,如果想要保留父类introduce方法的部分功能,可以这样做:

class Student: Person {
    var studentID: String

    init(name: String, age: Int, studentID: String) {
        self.studentID = studentID
        super.init(name: name, age: age)
    }

    override func introduce() {
        super.introduce()
        print("My student ID is \(studentID)")
    }
}

let alice = Student(name: "Alice", age: 20, studentID: "S12345")
alice.introduce()

上述代码中,super.introduce()调用了父类Personintroduce方法,然后再打印学生ID信息。

访问控制

访问控制用于限制代码中不同部分对类、属性、方法等的访问权限。Swift提供了几种访问控制级别:publicinternalfileprivateprivate

访问控制级别

  • public:最高访问级别,允许在任何源文件中访问。通常用于暴露给其他模块使用的类、属性和方法。
  • internal:默认访问级别,允许在定义它们的模块内部访问。如果一个应用程序只有一个模块,internalpublic在效果上类似,但internal的成员不能被其他模块访问。
  • fileprivate:限制访问仅限于定义它们的源文件内部。同一模块中的其他源文件无法访问fileprivate成员。
  • private:最低访问级别,限制访问仅限于定义它们的作用域内。例如,在类中定义的private属性或方法,只能在该类的内部访问。

设置访问控制

在定义类、属性或方法时,可以在前面加上相应的访问控制关键字来设置访问级别。例如:

public class PublicClass {
    public var publicProperty: String
    internal var internalProperty: String
    fileprivate var fileprivateProperty: String
    private var privateProperty: String

    public init(publicProp: String, internalProp: String, fileprivateProp: String, privateProp: String) {
        self.publicProperty = publicProp
        self.internalProperty = internalProp
        self.fileprivateProperty = fileprivateProp
        self.privateProperty = privateProp
    }

    public func publicMethod() {
        print("This is a public method.")
    }

    internal func internalMethod() {
        print("This is an internal method.")
    }

    fileprivate func fileprivateMethod() {
        print("This is a file - private method.")
    }

    private func privateMethod() {
        print("This is a private method.")
    }
}

在上述代码中,PublicClass类及其成员分别设置了不同的访问控制级别。

构造过程与析构过程

构造函数

构造函数是类实例化时调用的特殊方法,用于初始化实例的属性。在Swift中,构造函数使用init关键字定义。一个类可以有多个构造函数,称为构造函数重载,只要它们的参数列表不同。例如,在Rectangle类中添加一个默认构造函数:

class Rectangle {
    var width: Double
    var height: Double

    // 带参数的构造函数
    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }

    // 默认构造函数
    init() {
        self.width = 0.0
        self.height = 0.0
    }
}

let rect1 = Rectangle(width: 5.0, height: 3.0)
let rect2 = Rectangle()

在上述代码中,Rectangle类有两个构造函数,一个接受宽度和高度参数,另一个是默认构造函数,将宽度和高度初始化为0。

可失败构造函数

可失败构造函数是一种特殊的构造函数,它可能会因为某些条件不满足而初始化失败。可失败构造函数使用init?关键字定义。例如,定义一个Circle类,其构造函数在半径为负数时初始化失败:

class Circle {
    var radius: Double

    init?(radius: Double) {
        if radius < 0 {
            return nil
        }
        self.radius = radius
    }
}

let validCircle = Circle(radius: 5.0)
let invalidCircle = Circle(radius: -2.0)
if let circle = validCircle {
    print("The radius of the circle is \(circle.radius)")
}
if invalidCircle == nil {
    print("Invalid circle creation.")
}

在上述代码中,当半径为负数时,Circle的构造函数返回nil,表示初始化失败。

析构函数

析构函数是在类实例被销毁之前调用的特殊方法,用于释放实例所占用的资源。在Swift中,析构函数使用deinit关键字定义。例如,定义一个FileManager类,在析构函数中关闭文件:

class FileManager {
    var file: FileHandle?

    init(filePath: String) {
        if let file = FileHandle(forWritingAtPath: filePath) {
            self.file = file
        }
    }

    deinit {
        file?.closeFile()
        file = nil
    }
}

let fileMgr = FileManager(filePath: "test.txt")
// 当fileMgr超出作用域被销毁时,deinit函数会被调用

在上述代码中,FileManager类的析构函数关闭打开的文件,并将file属性设置为nil

类的扩展

扩展(extension)允许在不修改原始类定义的情况下,为类添加新的属性、方法、构造函数等。扩展使用extension关键字定义。

扩展属性

可以为类扩展计算属性,但不能扩展存储属性。例如,为Int类型扩展一个计算属性,用于判断该整数是否为偶数:

extension Int {
    var isEven: Bool {
        return self % 2 == 0
    }
}

let number = 4
print(number.isEven) // 输出 true

在上述代码中,为Int类型扩展了一个isEven计算属性,通过这个属性可以方便地判断一个整数是否为偶数。

扩展方法

可以为类扩展实例方法和类型方法。例如,为String类型扩展一个方法,用于重复字符串指定次数:

extension String {
    func repeat(_ times: Int) -> String {
        var result = ""
        for _ in 0..<times {
            result += self
        }
        return result
    }
}

let str = "Hello"
let repeatedStr = str.repeat(3)
print(repeatedStr) // 输出 HelloHelloHello

在上述代码中,为String类型扩展了一个repeat方法,用于重复字符串。

扩展构造函数

可以为类扩展构造函数,但需要注意,扩展的构造函数不能重写类原有的构造函数。例如,为Rectangle类扩展一个构造函数,从字符串中解析宽度和高度:

extension Rectangle {
    init?(fromString: String) {
        let components = fromString.components(separatedBy: "x")
        if components.count != 2 {
            return nil
        }
        guard let width = Double(components[0]), let height = Double(components[1]) else {
            return nil
        }
        self.init(width: width, height: height)
    }
}

let rectFromString = Rectangle(fromString: "5.0x3.0")
if let rect = rectFromString {
    print("Width: \(rect.width), Height: \(rect.height)")
}

在上述代码中,为Rectangle类扩展了一个从字符串解析宽度和高度的构造函数,当解析失败时返回nil

协议

协议(protocol)定义了方法、属性等的蓝图,一个类可以遵循(conform to)协议,并实现协议中定义的要求。协议使用protocol关键字定义。

协议定义

例如,定义一个Drawable协议,要求遵循该协议的类必须实现draw方法:

protocol Drawable {
    func draw()
}

上述Drawable协议定义了一个draw方法,但没有实现,遵循该协议的类需要提供具体的实现。

类遵循协议

Rectangle类遵循Drawable协议,并实现draw方法:

class Rectangle: Drawable {
    var width: Double
    var height: Double

    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }

    func draw() {
        print("Drawing a rectangle with width \(width) and height \(height)")
    }
}

let rect = Rectangle(width: 4.0, height: 3.0)
rect.draw()

在上述代码中,Rectangle类遵循了Drawable协议,并实现了draw方法。

协议作为类型

协议可以作为一种类型来使用。例如,可以定义一个函数,接受一个遵循Drawable协议的对象:

func drawShape(shape: Drawable) {
    shape.draw()
}

let rectForDrawing = Rectangle(width: 5.0, height: 2.0)
drawShape(shape: rectForDrawing)

在上述代码中,drawShape函数接受一个遵循Drawable协议的对象,并调用其draw方法,这体现了多态性,不同的类只要遵循Drawable协议,都可以作为参数传递给该函数。

协议继承

协议可以继承其他协议,从而扩展其功能。例如,定义一个FillableDrawable协议,继承自Drawable协议,并添加一个fillColor属性和fill方法:

protocol FillableDrawable: Drawable {
    var fillColor: String { get set }
    func fill()
}

class FilledRectangle: Rectangle, FillableDrawable {
    var fillColor: String

    init(width: Double, height: Double, fillColor: String) {
        self.fillColor = fillColor
        super.init(width: width, height: height)
    }

    func fill() {
        print("Filling the rectangle with color \(fillColor)")
    }
}

let filledRect = FilledRectangle(width: 3.0, height: 2.0, fillColor: "Red")
filledRect.draw()
filledRect.fill()

在上述代码中,FilledRectangle类遵循了FillableDrawable协议,实现了fillColor属性和fill方法,同时继承自Rectangle类并实现了draw方法。

通过以上内容,我们全面地介绍了Swift面向对象编程的基础,包括类与对象、属性、方法、继承、访问控制、构造与析构、扩展以及协议等方面,这些知识为进一步深入学习Swift编程和开发复杂应用奠定了坚实的基础。在实际开发中,灵活运用这些面向对象的特性,可以编写出结构清晰、可维护性强且功能丰富的程序。继续深入学习和实践,你将能够充分发挥Swift语言在面向对象编程方面的强大能力。