代码改变世界

Swift学习笔记(10):类和结构体

2017-05-20 01:43  杨平  阅读(265)  评论(0编辑  收藏  举报

目录:

  • 基本
  • 属性
  • 方法
  • 下标
  • 继承

基本

使用class和struct关键字定义类和结构体。

・类是引用类型,结构体和枚举是值类型
・值类型被赋予给一个变量、常量或被传递给一个函数时,已值拷贝方式传递
・可以使用 === 或 !== 判断两个类实例引用是否指向同一个类实例
class SomeClass {
    // 在这里定义类
}
struct SomeStructure {
    // 在这里定义结构体
}

使用类或结构体名称加 ()类创建类或结构体实例,使用 . 操作符引用属性名。

结构体都带有自动生成的成员逐一构造器,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中。类没有默认的成员逐一构造器。

let vga = Resolution(width:640, height: 480)

 

属性

属性将值跟特定的类、结构或枚举关联。存储属性存储常量或变量作为实例的一部分,而计算属性计算一个值。计算属性可以用于类、结构体和枚举,存储属性只能用于类和结构体。还可以定义属性观察器来监控属性值的变化。

・存储属性

存储属性可以是变量存储属性(用关键字 var 定义),也可以是常量存储属性(用关键字 let 定义)。可以在定义存储属性的时候指定默认值,也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值。

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)

由于结构体属于值类型,当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。属于引用类型的类则不同,把一个引用类型的实例赋给一个常量后,仍然可以修改该实例的变量属性。

延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 lazy 来标示一个延迟存 储属性。必须将延迟存储属性声明成变量(使用 var 关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

// DataImporter 是一个负责将外部文件中的数据导入的类。 这个类的初始化会消耗不少时间。
class DataImporter {
    var fileName = "data.txt"
    // 这里会提供数据导入功能
}
class DataManager {
    lazy var importer = DataImporter() var data = [String]()
    // 这里会提供数据管理功能
}
let manager = DataManager() manager.data.append("Some data") manager.data.append("Some more data")
// DataImporter 实例的 importer 属性还没有被创建

注意:如果一个被标记为 lazy 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。

・计算属性

计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    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)
        }

        // 如果计算属性的 setter 没有定义表示新值的参数名,
        // 则可以使用默认名称 newValue 
        // set {
        //     origin.x = newValue.x - (size.width / 2)
        //     origin.y = newValue.y - (size.height / 2)
        // }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}

只有 getter 没有 setter 的计算属性就是只读计算属性。只读计算属性的声明可以去掉get关键字和花括号:

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}
・属性观察器

属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。

属性观察器包含:willSet 在新的值被设置之前调用; didSet 在新的值被设置之后立即调用。

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            // 默认值oldValue表示旧值的参数名
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}
・类型属性

使用关键字 static 来定义类型属性。类型属性通过类型名加.操作符直接访问。在为类定义计算型类型属性时,可以改用关键字 class 来支持子类对父类的实现进行重写。

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 6
    }
}
class SomeClass {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 27
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}

 

方法

实例方法和函数完全一致,定义在类中。每个实例都有一个隐含的 self 属性指向自己本身。

结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。此时你可以使用 mutating 关键子修饰某个方法使其可以修改结构体或者枚举的属性;并且该方法对属性的修改都会反应到原始属性值上(即修改的是原始属性值),还可以给它隐含的 self 属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveByX(deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))") // 打印 "The point is now at (3.0, 4.0)"

在可变方法中给 self 赋值。

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY)
    }
}

枚举的可变方法可以把 self 设置为同一枚举类型中不同的成员:

enum TriStateSwitch {
    case Off, Low, High
    mutating func next() {
        switch self {
        case .Off:
            self = .Low
        case .Low:
            self = .High
        case .High:
            self = .Off }
        }
}
var ovenLight = TriStateSwitch.Low ovenLight.next()
// ovenLight 现在等于 .High ovenLight.next()
// ovenLight 现在等于 .Off

在类方法的 func 关键字之前加上关键字 static ,来指定类型方法,该类型方法通过类本身进行调用;类还可以用关键字 class 来允许子类重写父类的方法。

class SomeClass {
    class func someTypeMethod() {
    // 在这里实现类型方法 
    }
}
SomeClass.someTypeMethod()

在类型方法的方法体中, self 指向这个类型本身,而不是类型的某个实例。这意味着你可以用 self 来消除类型属性和类型方法参数之间的歧义。

 

下标

标可以定义在类、结构体和枚举中,是访问 合,列表或序列中元素的快捷方式。

定义下标使用 subscript 关键字,指定一个或多个输入参数和返回类型;与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,有点类似计算型属性。

subscript(index: Int) -> Int {
    get {
        // 返回一个适当的 Int 类型的值
    }
    set(newValue) {
        // 执行适当的赋值操作
    }
}

下标可以接受任意数量任意类型的无默认值非输入输出参数,可以使用变量参数和可变参数;其返回值也可以是任意类型的。

一个类或结构体可以根据自身需要提供多个下标实现,使用下标时将通过入参的数量和类型进行区分,自动匹配合适的下标,这就是下标的重载。

struct Matrix {
    let rows: Int, columns: Int
    var grid: [Double]
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(count: rows * columns, repeatedValue: 0.0)
    }
    func indexIsValidForRow(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}

 

继承

一个类可以继承另一个类的方法,属性和其它特性。继承是类独有的特性,被继承的类叫超类,继承的类叫子类。

为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔:bsp;

class SomeClass: SomeSuperclass {
    // 这里是子类的定义
}

可以通过关键字 override 重写子类继承来的实例方法,类方法,实例属性,或下标,定制自己的功能。

 class Train: Vehicle {
    // 重写父类Vehicle中的makeNoise方法
     override func makeNoise() {
         print("Choo Choo")
     }
}

 

可以通过 super 关键字应用超类对象。

你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter,或添加属性观察器使重写的属性可以观察属性值什么时候发生变化。 

你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。

你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会被 通知到,无论那个属性原本是如何实现的。

・ 你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值不可以被设置 的,所以,为它们提供 willSet 或 didSet 实现是不恰当
・ 你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并你已 经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了

你可以通过把方法,属性或下标标记为 final 来防止它们被重写 

 

声明:该系列内容均来自网络或电子书籍,只做学习总结!