首页 > 代码库 > 从0开始学Swift笔记整理(三)

从0开始学Swift笔记整理(三)

这是跟在上一篇博文后续内容:

——Swift中相关的属性

存储属性

Swift中的属性分为存储属性和计算属性,存储属性就是Objective-C中的数据成员,计算属性不存储数据,但可以通过计算其他属性返回数据。存储属性可以存储数据,分为常量属性(用关键字let定义)和变量属性(用关键var定义)。
存储属性概念:
我们在前面曾用到过属性,Employee类和Department结构体。它们的类图如下,Employee 的部门属性dept与Department之间进行了关联。 

 

我们可以在定义存储属性时指定默认值,示例代码如下:
class Employee {
    let no: Int = 0
    var name: String = ""
    var job: String?
    var salary: Double = 0
    var dept: Department?
}
struct Department {
    let no: Int = 0
    var name: String = ""
}
let emp = Employee()
emp.no = 100 //编译错误:修改常量属性,程序会发生编译错误
let dept = Department()
dept.name = "SALES" //编译错误:dept是值类型,值类型不能修改,即便它的属性name是变量属性,也不能修改
let emp1 = Employee()
emp1.name =  "Tony"

计算属性

计算属性本身不存储数据,而是从其他存储属性中计算得到数据。

计算属性提供了一个getter(取值访问器)来获取值,以及一个可选的setter(设置访问器)来间接设置其他属性或变量的值。计算属性的语法格式如下:

面向对象类型 类型名 {

存储属性

......

var 计算属性名: 属性数据类型 {

get {

return 计算后属性值

}

set (新属性值) {

......

}

}

}

定义计算属性比较麻烦,要注意后面的几个大括号的对齐关系。

我们先看一个示例:

import Foundation

class Employee {

var no: Int = 0

var firstName: String = "Tony" //存储属性

var lastName: String = "Guan" //存储属性

var job: String?

var salary: Double = 0

lazy var dept: Department = Department()

var fullName: String { //计算属性

get {

return firstName + "." + lastName //返回拼接的结果

}

set (newFullName) { //存储传递进来的参数值

var name = newFullName.componentsSeparatedByString(".")

firstName = name[0]

lastName = name[1]

}

}

}

struct Department {

let no: Int = 0

var name: String = ""

}

var emp = Employee()

print(emp.fullName) //取出属性值

emp.fullName = "Tom.Guan" //给属性赋值

print(emp.fullName)

只读计算属性:

计算属性可以只有getter访问器,没有setter访问器,这就是只读计算属性。指定计算属性不仅不用写setter访问器,而且get{}代码也可以省略。与上一节相比,代码将大大减少。修改上一节示例为只读计算属性,代码如下:

class Employee {

var no: Int = 0

var firstName: String = "Tony"

var lastName: String = "Guan"

var job: String?

var salary: Double = 0

lazy var dept: Department = Department()

var fullName: String { //简洁的setter访问器

return firstName + "." + lastName

}

}

struct Department {

let no: Int = 0

var name: String = ""

}

var emp = Employee()

print(emp.fullName)

只读计算属性不能够赋值,下列语句是错误的。

emp.fullName = "Tom.Guan"

属性观察者

为了监听属性的变化,Swift提供了属性观察者。属性观察者能够监听存储属性的变化,即便变化前后的值相同,它们也能监听到。
属性观察者主要有以下两个:
· willSet:观察者在修改之前调用。
· didSet:观察者在修改之后立刻调用。
属性观察者的语法格式如下:
面向对象类型 类型名 {     
    ...
    var 存储属性: 属性数据类型 = 初始化值 {
        willSet(新值) {   //定义willSet观察者。“新值”是传递给willSet观察者的参数,它保存了将要替换原来属性的新值
        ...
        }        
        didSet(旧值) {  //定义didSet观察者。“旧值”是传递给didSet观察者的参数,它保存了被新属性替换的旧值。
        ...
        }       
   }       

属性观察者的语法格式比计算属性要混乱。
属性观察者可以在类和结构体中使用,不能在枚举中使用。
示例代码如下:
class Employee { 
    var no: Int = 0
    var name: String = "Tony" { 
        willSet(newNameValue) { //定义name属性的willSet观察者,newNameValue是由我们分配的传递新值的参数名
        print("员工name新值:\(newNameValue)")  
        }
        didSet(oldNameValue) { //定义name属性的didSet观察者,oldNameValue是由我们分配的传递旧值的参数名
        print("员工name旧值:\(oldNameValue)")
        }
   }
   var job: String?
   var salary: Double = 0
   var dept: Department?
}

struct Department {  
    var no: Int = 10 {  
        willSet {    //定义no属性的willSet观察者,注意这里没有声明参数,但是我们可以在观察者内部使用newValue
        print("部门编号新值:\(newValue)")
        }
        didSet {    //定义no属性的didSet观察者,注意这里也没有声明参数,但是我们可以在观察者内部使用oldValue
        print("部门编号旧值:\(oldValue)")  
        }
   }
   var name: String = "RESEARCH"
}

var emp = Employee()
emp.no = 100
emp.name = "Smith"    

var dept = Department()
dept.no = 30     
上述代码运行结果如下:
员工name新值:Smith
员工name旧值:Tony
部门编号新值:30
部门编号旧值:10

静态属性

我先来设计一个类:有一个Account(银行账户)类,假设它有3个属性:amount(账户金额)、interestRate(利率)和owner(账户名)。

在这3个属性中,amount和owner会因人而异,不同的账户这些内容是不同的,而所有账户的interestRate都是相同的。

amount和owner属性与账户个体有关,称为实例属性。interestRate属性与个体无关,或者说是所有账户个体共享的,这种属性称为静态属性或类型属性。

面向对象类型(结构体、枚举和类)都可以定义静态属性,它们的语法格式分别如下所示:

struct 结构体名 {   //定义结构体, 结构体中可以定义静态存储属性和静态计算属性

static var(或let) 存储属性  = "xxx"

...

static var 计算属性名: 属性数据类型 {

get {

return 计算后属性值

}

set (新属性值) {

...

}

}

}

enum 枚举名 {  //定义枚举,枚举中不可以定义实例存储属性,但可以定义静态存储属性,也可以定义静态计算属性

static var(或let) 存储属性  = "xxx"

...

static var 计算属性名: 属性数据类型 {

get {

return 计算后属性值

}

set (新属性值) {

...

}

}

}

class 类名 {    //定义类,类中不仅可以定义实例存储属性,还可以定义静态存储属性

static var(或let) 存储属性  = "xxx"

...

class(或static)  var 计算属性名: 属性数据类型 {

get {

return 计算后属性值

}

set (新属性值) {

...

}

}

}

结构体静态计算属性也可以是只读的,语法如下:

static var 计算属性名: 属性数据类型 {

return 计算后属性值

}

看一个Account结构体静态属性示例:

struct Account {//定义Account结构体

var amount: Double = 0.0 //账户金额

var owner: String = "" //账户名

static var interestRate: Double = 0.0668  //定义静态存储属性interestRate利率

static var staticProp: Double { //定义静态计算属性staticProp

return interestRate * 1_000_000

}

var instanceProp: Double { //定义实例计算属性instanceProp

return Account.interestRate * amount

}

}

//访问静态属性

print(Account.staticProp)

var myAccount = Account()

//访问实例属性

myAccount.amount = 1_000_000

//访问静态属性

print(myAccount.instanceProp)

——Swift下标

var studentList: String[]  = ["张三","李四","王五"]
studentList[0] = "诸葛亮"

var studentDictionary = [102: "张三",105: "李四", 109: "王五"]
studentDictionary[110] = "赵六"

在访问数组和字典的时候,可以采用下标访问。其中数组的下标是整数类型索引,字典的下标是它的“键”。

Swift中的下标相当于Java中的索引属性和C#中的索引器。

下标访问的语法格式如下:

面向对象类型 类型名 {

其他属性

...

subscript(参数: 参数数据类型) -> 返回值数据类型 {

get {

return 返回值

}

set(新属性值) {

...

}

}

}

下标也有类似于计算属性的getter和setter访问器。

getter访问器是一个方法,在最后使用return语句将计算结果返回。

setter访问器“新属性值”是要赋值给属性值。参数的声明可以省略,系统会分配一个默认的参数newValue。

在Swift中没有提供二维数组,只有一维数组Array。可以自定义一个二维数组类型,然后通过两个下标参数访问它的元素,形式上类似于C语言的二维数组。

——默认构造函数

结构体和类的实例在构造过程中会调用一种特殊的init方法,称为构造函数。构造函数没有返回值,可以重载。在多个构造函数重载的情况下,运行环境可以根据它的外部参数名或参数列表调用合适的构造函数。

结构体和类在构造过程中会调用一个构造函数,即便是没有编写任何构造函数,编译器也会提供一个默认的构造函数。下面看示例代码:

class Rectangle {

var width: Double  = 0.0

var height: Double = 0.0

}

var rect = Rectangle() //创建实例,并调用默认构造函数init()
rect.width = 320.0
rect.height = 480.0
print("长方形:\(rect.width) x \(rect.height)")

Rectangle()表示调用了某个方法,这个方法就是默认构造函数init()。

事实上,在Rectangle的定义过程中省略了构造函数,相当于如下代码:

class Rectangle {

var width: Double  = 0.0

var height: Double = 0.0

init() {

}

}

如果Rectangle是结构体,则它的定义如下:

struct Rectangle {

var width: Double = 0.0

var height: Double = 0.0

}

而结构体Rectangle的默认构造函数与类Rectangle的默认构造函数是不同的,相当于如下代码:

struct Rectangle {

var width: Double = 0.0

var height: Double = 0.0

init() {

}

init(width: Double, height: Double) { //有参数的构造函数

self.width  = width

self.height  = height

}

}

要调用哪个构造函数是根据传递的参数名和参数类型决定的。

——构造函数与存储属性初始化

构造函数的主要作用是初始化实例,其中包括:初始化存储属性和其它的初始化。在Rectangle类或结构体中,如果在构造函数中初始化存储属性width和height后,那么在定义他们时就不需要初始化了。

Rectangle类代码如下:

class Rectangle {

var width: Double

var height: Double

init() {

width  = 0.0

height  = 0.0

}

}

如果存储属性在构造函数中没有初始化,在定义的时候也没有初始化,那么就会发生编译错误。
构造函数还可以初始化常量存储属性
构造函数中的局部参数名可以直接作为外部参数名使用

为了增强程序的可读性,Swift中的方法和函数可以使用外部参数名。在构造函数中也可以使用外部参数名。构造函数中的外部参数名要比一般的方法和函数更有意义,由于构造函数命名都是init,如果一个类型中有多个构造函数,我们可以通过不同的外部参数名区分调用不同的构造函数。

——构造函数重载

构造函数作为一种特殊方法,也可以重载。
Swift中构造函数可以多个,他们参数列表和返回值可以不同,这些构造函数构成重载。

为了减少多个构造函数间的代码重复,在定义构造函数时,可以通过调用其他构造函数来完成实例的部分构造过程,这个过程称为构造函数代理。构造函数代理在结构体和类中使用方式是不同,先介绍结构体中构造函数代理。
将上一节的示例修改如下:
struct Rectangle {
    var width: Double
    var height: Double   
    init(width: Double, height: Double) {
        self.width   = width
        self.height  = height
    }   
    init(W width: Double,H height: Double) { 
        self.width   = width
        self.height  = height
    }   
    init(length: Double) {     //调用了self.init语句
        self.init(W: length, H: length)
    }
    init() {         //调用了self.init语句
        self.init(width: 640.0, height: 940.0)
    }
}
var rectc1 = Rectangle(width: 320.0, height: 480.0) 
print("长方形:\(rectc1.width) x \(rectc1.height)")
var rectc2 = Rectangle(W: 320.0, H: 480.0)   
print("长方形:\(rectc2.width) x \(rectc2.height)")
var rectc3 = Rectangle(length: 500.0)    
print("长方形3:\(rectc3.width) x \(rectc3.height)")
var rectc4 = Rectangle()       
print("长方形4:\(rectc4.width) x \(rectc4.height)")
将Rectangle声明为结构体类型,其中也有4个构造函数重载。
这种在同一个类型中通过self.init语句进行调用当前类型其它构造函数,其它构造函数被称为构造函数代理。
类构造函数横向代理
由于类有继承关系,类构造函数代理比较复杂,分为横向代理和向上代理。
· 横向代理类似于结构体类型构造函数代理,发生在同一类内部,这种构造函数称为便利构造函数(convenience initializers)。
· 向上代理发生在继承情况下,在子类构造过程中要先调用父类构造函数,初始化父类的存储属性,这种构造函数称为指定构造函数(designated initializers)。

构造函数调用规则

在构造函数中可以使用构造函数代理帮助完成部分构造工作。类构造函数代理分为横向代理和向上代理,横向代理只能在发生在同一类内部,这种构造函数称为便利构造函数。向上代理发生在继承的情况下,在子类构造过程中,要先调用父类构造函数初始化父类的存储属性,这种构造函数称为指定构造函数。

Person和Student类示例:
class Person {
    var name: String
    var age: Int   
    func description() -> String {
        return "\(name) 年龄是: \(age)"
    }
    convenience init () {   //便利构造函数
        self.init(name: "Tony")
        self.age = 18
    }
    convenience init (name: String) { //便利构造函数
        self.init(name: name, age: 18)
    }
    init (name: String, age: Int) {  //指定构造函数
        self.name = name
        self.age  = age
    }
}
class Student: Person {
    var school: String
    init (name: String, age: Int, school: String) {  //指定构造函数
        self.school = school
        super.init(name: name, age: age)
    }
    convenience override init (name: String, age: Int) {//便利构造函数
        self.init(name: name, age: age, school: "清华大学")
    }
}
let student = Student()
print("学生: \(student.description())")
构造函数之间的调用形成了构造函数链,如图所示。
Swift限制构造函数之间的代理调用的规则有3条,如下所示。
1、指定构造函数必须调用其直接父类的的指定构造函数。从图可见,Student中的④号指定构造函数调用Person中的③号指定构造函数。
2、便利构造函数必须调用同一类中定义的其他构造函数。从图可见,Student中的⑤号便利构造函数调用同一类中的④号便利构造函数,Person中的①号便利构造函数调用同一类中的②号便利构造函数。
3、便利构造函数必须最终以调用一个指定构造函数结束。从图可见,Student中的⑤号便利构造函数调用同一类中的④号指定构造函数,Person中的②号便利构造函数调用同一类中的③号指定构造函数。

——类的继承

Swift中的继承只能发生在类上,不能发生在枚举和结构体上。一个类可以继承另一个类的方法、属性、下标等特征,当一个类继承其他类时,继承类叫子类,被继承类叫父类(或超类)。子类继承父类后,可以重写父类的方法、属性、下标等特征。
为了了解继承性,看这样一个场景:一位面向对象的程序员小赵,在编程过程中需要描述和处理个人信息,于是他定义了类Person,如下所示:
class Person {
    var name: String
    var age: Int   
    func description() -> String {
        return "\(name) 年龄是: \(age)"
    }
    init () {
        name = ""
        age  = 1
    }
}
一周以后,小赵又遇到了新的需求,需要描述和处理学生信息,于是他又定义了一个新的类Student,如下所示:
class Student {
    var name: String
    var age: Int
   var school: String   
    func description() -> String {
        return "\(name) 年龄是: \(age)"
    }
    init() {
        school = ""
        name = ""
        age  = 8
    }
}
很多人会认为小赵的做法能够理解并相信这是可行的,但是问题在于Student和Person两个类的结构太接近了,后者只比前者多了一个属性school,却要重复定义其他所有的内容,实在让人“不甘心”。Swift提供了解决类似问题的机制,那就是类的继承,代码如下所示:
class Student: Person {
    var school: String
    override init() {
        school = ""
        super.init()
        age  = 8
    }
}
Student类继承了Person类中的所有特征,“:”之后的Person类是父类。Swift中的类可以没有父类,例如Person类,定义的时候后面没有“:”,这种没有父类的就是基类。
此外override init()是子类重写父类构造函数。
一般情况下,一个子类只能继承一个父类,这称为单继承,但有的情况下一个子类可以有多个不同的父类,这称为多重继承。在Swift中,类的继承只能是单继承。多重继承可以通过遵从多个协议实现。也就是说,在Swift中,一个类只能继承一个父类,但是可以遵循多个协议。

——构造函数继承

Swift中的子类构造函数的来源有两种:自己编写和从父类继承。并不是父类的所有的构造函数都能继承下来,能够从父类继承下来的构造函数是有条件的,如下所示。
· 条件1:如果子类没有定义任何指定构造函数,它将自动继承所有父类的指定构造函数。
· 条件2:如果子类提供了所有父类指定构造函数的实现,无论是通过条件1继承过来的,还是通过自己编写实现的,它都将自动继承所有父类的便利构造函数。(示例忽略)

——重写属性

重写实例属性。我们可以在子类中重写从父类继承来的属性,属性有实例属性和静态属性之分,他们在具体实现也是不同的。
实例属性的重写一方面可以重写getter和setter访问器,另一方面可以重写属性观察者。
计算静态属性需要使用getter和setter访问器,而存储属性不需要。子类在继承父类后,也可以通过getter和setter访问器重写父类的存储属性和计算属性。
从属性重写可见,子类本身并不存储数据,数据是存储在父类的存储属性中的。

重写静态属性。

在类中静态属性定义使用class或static关键字,但是使用哪一个要看子类中是否重写该属性。

class修饰的属性可以被重写,static关键字就不能被重写。(示例忽略)

 

 

这是我在学Swift整理的基础笔记,希望给更多刚学IOS开发者带来帮助,在这里博主非常感谢大家的支持!

更多的请到参考我下一篇博文。之后还在持续更新中。。。

从0开始学Swift笔记整理(三)