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

时间:2021-12-27 23:01:35

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

——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开发者带来帮助,在这里博主非常感谢大家的支持!

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