Swift--Swift语言中的面向对象特性
-
Swift语言中的面向对象类型
-
枚举
-
结构体
-
类
-
可选链
-
访问限定
Swift语言中的面向对象类型
面向对象概念的基本特征
- 封装性。封装性是尽可能隐藏对象的内部细节,对外形成一个边界,只保留有限的对外接口使之与外部发生联系。
- 继承性。一些特殊类能够具有一般类的全部属性和方法,这称做特殊类对一般类的继承。例如客轮与轮船,客轮是特殊类,轮船是一般类。通常我们称一般类为父类(或基类),称特殊类为子类(或派生类)。
- 多态性。对象的多态性是指在父类中定义的属性或方法被子类继承之后,可以使同一个属性或方法在父类及其各个子类中中具有不同的含义,这称为多态性。例如动物都有吃的方法,但是老鼠的吃法和猫的吃法是截然不同的。
Swift中的面向对象类型
在Swift语言中类、结构体(struct)和枚举(enum)都是面向对象的数据类型,具有面向对象的特征。而在其他语言中,结构体和枚举是完全没有面向对象特性的,Swift语言赋予了它们面向对象特征。
注意: Swift语言中只有类具有面向对象的三个特征,而结构体和枚举只具有封装性,结构体和枚举是不具有继承性和多态性的。
-
在面向对象中类创建对象的过程称为“实例化”,实例化的结果称为“实例”,类的“实例”也称为“对象”。但是在 Swift中,结构体和枚举的实例一般不称为“对象”,这是因为结构体和枚举并不是彻底的面向对象类型,而只是包含了一些面向对象的特点。
-
例如,在Swift中继承和多态只发生在类上,结构体和枚举不能继承。
枚举
在C和Objective-C中,枚举用来管理一组相关常量集合,通过使用枚举可以提高程序的可读性,使代码更清晰,更易于维护。
而在Swift中,枚举的作用已经不仅仅是定义一组常量、提高程序的可读性了,它还具有了面向对象特性。
我们先来看Swift声明。Swift中也是使用enum关键词声明枚举类型,具体定义放在一对大括号内,枚举的语法格式如下:
enum 枚举名
{
枚举的定义
}
成员值
在Swift中,枚举的成员值默认情况下不是整数类型,以下代码是声明枚举示例:
enum WeekDays {
case Monday
case Tuesday
case Wednesday
case Thursday
case Friday
}
也可以将多个成员值放在一行,用逗号隔开,如下所示:
enum WeekDays {
case Monday, Tuesday, Wednesday, Thursday, Friday
}
enum WeekDays {
case Monday
case Tuesday
case Wednesday
case Thursday
case Friday
}
//var day: WeekDays = WeekDays.Friday
var day = WeekDays.Friday
day = WeekDays.Wednesday
day = .Monday //day经过前面的赋值之后,已经确定了它的数据类型,所以可以省略类型及枚举的名字,写成.成员
func writeGreeting(day: WeekDays) {
switch day {
case .Monday:
print("星期一好啊")
case .Tuesday:
print("星期二好啊")
case .Wednesday:
print("星期三好啊")
case .Thursday:
print("星期四好啊")
case .Friday:
print("星期五好啊")
// default:
// print("星期五好啊!")
}
}
writeGreeting(day: day)
writeGreeting(day: WeekDays.Friday)
writeGreeting(day: .Thursday)
注意:
1、使用枚举成员赋值时,我们可以采用完整的“枚举类型名.成员值”
的形式,也可以省略枚举类型而采用“.成员值”
的形式。
这种省略形式能够访问的前提是,Swift编译器能够根据上下文环境推断类型。
2、在switch中使用枚举类型时,switch语句中的case必须全面包含枚举中的所有成员,不能多也不能少,包括使用default的情况下,default也表示某个枚举成员。在上面的示例中,default表示的是Friday枚举成员,在这种情况下,Friday枚举成员的case分支不能再出现了。
原始值
我们可以为枚举类型提供原始值(raw values)声明,这些原始值类型可以是: 字符、字符串、整数和浮点数等。
原始值枚举的语法格式如下:
enum 枚举名:数据类型
{
case 成员名 = 默认值
......
}
在“枚举名”后面跟“:”和“数据类型”就可以声明原始值枚举的类型,然后在定义case成员的时候需要提供默认值。
enum WeekDays2: Int {
case Monday = 0
case Tuesday = 1
case Wednesday = 2
case Thursday = 3
case Friday = 4
}
当枚举类型为整数类型的时候,可以为第一个成员指定原始值,其他成员会自动累加,不用指定原始值,仅限于整数类型。如下:
enum WeekDays2: Int {
case Monday = 0, Tuesday, Wednesday, Thursday, Friday
}
枚举类型为String类型时,只指定第一个成员的原始值时,那其他成员的原始值就是它所在枚举中的成员值。如下:
enum WeekDays2: String {
case Monday = "A", Tuesday, Wednesday, Thursday, Friday
}
print(WeekDays2.Friday.rawValue) //输出:Friday,即为成员值
枚举类型为Double类型时,每个成员必须指定原始值,还必须和其他成员的原始值不能重复
enum WeekDays2: Double {
case Monday = 0.0, Tuesday = 1.0, Wednesday = 2.0, Thursday = 3.0, Friday = 4.0
}
通过成员值获得原始值
let friday = WeekDays2.Friday.rawValue
print("friday==", friday)//输出:friday== 4
通过原始值获得成员值
let thursday = WeekDays2(rawValue: 3)
print("thursday==", thursday)
通过枚举类型的构造函数,构造函数中有个参数rawValue,用原始值rawValue为3来构建一个枚举类型,得到的值就是原始值为3所对应的成员值thursday
完整代码:
enum WeekDays2: Int {
case Monday = 0, Tuesday, Wednesday, Thursday, Friday
}
print(WeekDays2.Friday.rawValue)//rawValue用来显示原始值
var day2 = WeekDays2.Friday
day2 = WeekDays2.Wednesday
func writeGreeting2(day2: WeekDays2) {
switch day {
case .Monday:
print("星期一好!")
case .Tuesday:
print("星期一好!")
case .Wednesday:
print("星期一好!")
case .Thursday:
print("星期一好!")
default:
print("星期五好啊!")
}
}
//通过成员值获得原始值
let friday = WeekDays2.Friday.rawValue
print("friday==", friday)//输出:friday== 4
//通过原始值获得成员值
let thursday = WeekDays2(rawValue: 3)
print("thursday==", thursday)
if (WeekDays2.Friday.rawValue == 4) {
print("今天是星期五")
}
注意: 原始值很多情况下不需要的,之所以使用原始值,实际上Swift是为迎合程序员的心理上的需要,因为之前的语言使很多程序员都认为枚举类型是一个整数类型,而且很多情况下是把枚举类型拿来判断的,现在如果只是出于判断的目的,就不需要原始值,直接使用枚举的成员值就好了。
类
类和结构体定义
class 类名{
定义类的成员
}
struct 结构体名 {
定义结构体的成员
}
类图

结构体和类的区别:如果不考虑到面向对象的继承和多态的时候,结构体和类并没有什么区别,但如果考虑到未来的继承和多态,那一定要把类型声明成类的类型,而不是结构体。
//类和结构体
class Employee {
var no: Int = 0
var name: String = ""
var job: String?
var salary: Double = 0
var dept: Department?
}
struct Department {
var no: Int = 0
var name: String = ""
var employees: [Employee]?
}
var emp = Employee()
var department = Department()
可选链
可选链就是可选类型的具体的应用
可选链概念

注意: 1、加感叹号(!
) 为强制拆包方式
,也叫显示拆包方式
,这种方式有个弊端,如果中间有个链条是为空的话,例如emp.dept为空的话,那么员工引用部门的这块就不会再往下走了,如果用感叹号(!)进行强制拆包的话就会出现异常,程序就会崩溃。我们在可选类型中学过,如果我们试图对一个空值进行强制拆包的话,就会出现异常,出现错误,程序就会崩溃。
2、问号(?
)是一种比较温柔的方式,我们叫做可选链
是允许为空值的,如果emp.dept为空的话,没有关系,最后返回的结果就是空值,只是不会往公司这一层级找,程序不会崩溃。
3、可选链的最后结果还是可选的
,我们把问号的这种方式所形成的链条称为可选链
。
使用问号(?)和感叹号(!)
1、可选类型中的问号(?)
声明这个类型是可选类型,访问这种类型的变量或常量时要使用感叹号(!),下列代码是显示拆包:
let result1: Double? = 100 //声明可选类型
print(result1!) //显示拆包取值(强制拆包取值), 输出结果为100.0
2、可选类型中的感叹号(!)
声明这个类型是可选类型,但是访问这种类型的变量或常量时可以不使用感叹号,下列代码是隐式拆包:
let result2: Double? = 200 //声明可选类型
print(result2) //隐式拆包取值,输出结果为Optional(100.0)
3、可选链中的感叹号(!)
多个实例具有关联关系,当从一个实例引用另外实例的方法、属性和下标等成员时就会形成引用链,由于这个”链条“某些环节可能有值,也可能没有值,因此需要采用如下方式访问:
emp.dept!.comp!.name
4、可选链中的问号(?)
在可选链中使用感叹号(!)访问时,一旦”链条“某些环节没有值,程序就会发生异常,于是我们把感叹号(!)改为问号(?),代码如下所示:
emp.dept?.comp?.name
访问限定
访问范围
- 模块。模块是指一个应用程序包或一个框架。
- 源文件。源文件指的是Swift中的.swift文件。
访问级别
Swift提供了3种不同访问级别,对于访问修饰符:public、internal和private。 这些访问修饰符可以修饰类、结构体、枚举等面向对象的类型,还可以修饰:变量、常量、下标、元组、函数、属性等内容。
-
public。 可以访问自己模块中的模块的任何public实体,如果使用import语句引入其他模块,可以访问其它模块中的public实体。
-
internal。 缺省访问限定。只能访问自己模块的任何internal实体,不能访问其它模块的internal实体。
-
private。只能在当前源文件中使用的实体,称为私有实体。使用private修饰,可以用作隐藏某些功能的实现细节。

如上图:
1、在模块1中import模块2后,模块1可以访问模块2中的源程序文件1的public实体。
2、在模块1中import模块2后,模块1不可以访问模块2中的源程序文件2的internal实体。
3、在模块2中源程序文件1和源程序文件2可以相互访问。
4、在模块2中import模块1后,模块2不可以访问模块1中的源程序文件2中的private实体。
5、在模块1中,源程序文件1也不可以访问源程序文件2中的private实体。
使用访问级别最佳实践
1、统一性原则
-
原则1。 如果一个类型(类、结构体、枚举)定义为internal或private,那么类型声明的变量或常量不能使用public访问级别。
-
原则2。函数的访问级别不能高于它的参数和返回类型(类、结构体、枚举)的访问级别。
//访问限定
private class Employee2 {
var no: Int = 0
var name: String = "Tony"
var job: String?
var salary: Double = 0
var dept: Department2?
}
internal struct Department2 {
var no: Int = 0
var name: String = ""
}
//违背统一性原则1
//public let emp2 = Employee2() //编译错误
//internal let emp2 = Employee2() //编译错误
private let emp2 = Employee2()
//违背统一性原则1
//public var dept2 = Department2() //编译错误
internal var dept2 = Department2()
//违背统一性原则2
//public func getEmpDept(emp: Employee2) ->Department2? {
// return emp.dept
//}
private func getEmpDept(emp: Employee2) ->Department2? {
return emp.dept
}
2、设计原则
- 如果我们编写的是应用程序,应用程序包中的所有Swift文件和其中定义的实体都是供本应用使用的,而不是提供给其他模块使用,那么我们就不用设置访问级别了,即使用默认的访问级别。
- 如果我们开发的是框架,框架编译的文件不能独立运行,因此它天生给别人使用的,这种情况下要详细设计其中的Swift文件和实体的访问级别,让别人使用的可以设定为public,不想让别人看到的可以设定为internal或private。
3、元组类型的访问级别
元组类型的访问级别遵循元组中字段最低级的访问级别,例如下面的代码:
private class Employee2 {
var no: Int = 0
var name: String = "Tony"
var job: String?
var salary: Double = 0
var dept: Department2?
}
struct Department2 {
var no: Int = 0
var name: String = ""
}
private let emp2 = Employee2()
var dept2 = Department2()
private var student1 = (dept2, emp2)//元组类型的访问级别遵循元组中字段最低级的访问级别
4、枚举类型的访问级别
枚举中成员的访问级别继承自该枚举,因此我们不能为枚举中的成员指定访问级别。例如下面的的代码:
public enum WeekDays {
case Monday
case Tuesday
case Wednesday
case Thursday
case Friday
}