目录
一、结构体
二、类
三、属性
四、方法
一、结构体
1、定义结构体
// 定义结构体
struct Point {
var x: Int
var y: Int
}
// 用结构体的构造方法,创建结构体变量
var point = Point(x: 11, y: 12)
切记:定义结构体时,必须保证结构体的每个存储属性都有初始值,当然你可以在定义存储属性时就直接给它们赋初始值,也可以在稍后的构造方法里给它们赋初始值。
2、结构体的构造方法(支持重载)
其实我们每定义一个结构体,编译器就会自动帮我们生成相应的构造方法,至于会生成什么样的构造方法,那要看我们定义的结构体长什么样,但是你只需要记住一个宗旨就行了:结构体的构造方法,必须得保证结构体的每个存储属性都能被赋初始值。
比如我们定义了下面这样一个结构体:
struct Point {
var x: Int
var y: Int
}
编译器其实自动帮我们生成了这个结构体的构造方法:
init(x: Int, y: Int) {
self.x = x
self.y = y
}
当我们调用Point()
函数创建结构体变量时,本质上就是在调用相应的构造方法。
var point = Point(x: 11, y: 11)
又比如我们定义了下面这样一个结构体:
struct Point {
var x: Int = 11
var y: Int = 11
}
编译器其实自动帮我们生成了这个结构体的构造方法:
init(x: Int, y: Int) {
self.x = x
self.y = y
}
init(x: Int) {
self.x = x
}
init(y: Int) {
self.y = y
}
init() {
}
当我们调用Point()
函数创建结构体变量时,本质上就是在调用相应的构造方法。
var point1 = Point(x: 11, y: 12)
var point2 = Point(x: 13)
var point3 = Point(y: 14)
var point4 = Point()
当然我们也可以自定义结构体的构造方法,但是需要注意的是:一旦我们自定义了结构体的构造方法,编译器就不会自动帮我们生成构造方法了,一个也不会。
比如我们定义下面这样一个结构体,并且自定义了构造方法:
struct Point {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
那么我们就只能调用如下Point()
函数创建结构体变量,因为其它的构造方法都没有了。
var point = Point(x: 11, y: 11)
3、结构体变量的内存大小、内存布局
结构体变量的内存大小,和我们平常那样分析是一致的:成员的绝对大小、内存对齐导致的实际大小、分配时对齐参数导致的分配大小。
struct Point {
var x: Int
var y: Int
}
func test() {
var point = Point(x: 11, y: 11) // 栈区变量
}
print(MemoryLayout<Point>.size) // 16
print(MemoryLayout<Point>.stride) // 16
print(MemoryLayout<Point>.alignment) // 8
结构体变量在不同的地方创建就对应着不同内存区域的一块连续内存。比如结构体变量是在函数内部创建的,它就对应着栈区的一块连续内存;在函数外部创建的,它就对应着全局区的一块连续内存;作为对象的一个属性创建的,它就对应着堆区的一块连续内存。上面的例子中,我们是定义了一个栈区的结构体变量,因此它的内部布局大概如下:
二、类
1、定义类
// 定义类
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
// 用类的构造方法,创建引用变量和对象
var size = Size(width: 11, height: 11)
切记:定义类时,必须保证类的每个存储属性都有初始值,当然你可以在定义存储属性时就直接给它们赋初始值,也可以在稍后的构造方法里给它们赋初始值。
2、类的构造方法(支持重载)
和结构体不同的是,编译器不会轻易给类自动生成相应的构造方法,只有一种情况会生成,那就是我们在定义类的时候就给每个存储属性赋了初始值,编译器才会为我们自动生成一个不带参数的构造方法,像下面这样:
class Size {
var width: Int = 11
var height: Int = 11
}
var size = Size()
所以大多数情况下都是我们自定义类的构造方法,自定义构造方法的核心宗旨和结构体还是一样的:类的构造方法,必须得保证类的每个存储属性都能被赋初始值。同样的,一旦我们自定义了类的构造方法,编译器就不会自动帮我们生成构造方法了,一个也不会。
class Size {
var width: Int
var height: Int
// 主要构造方法(又叫全能构造方法),要确保每个存储属性都能被赋初始值
init(width: Int, height: Int) {
self.width = width
self.height = height
}
// 便捷构造方法(其实便捷构造方法可以通过给全能构造方法的参数设置默认值来替代)
// 1、提供多个更加灵活的构造方法,供外界选择
// 2、之所以选择定义便捷构造方法,而不是定义多个主要构造方法,是因为多个主要构造方法里可能有重复的代码,这样写更优雅
// 3、强制你调用一下当前类的主要构造方法,复用主要构造方法里代码的同时,也确保了每个存储属性都能被赋初始值
// 便利构造方法通常用在对系统的类进行构造方法的扩展这种场景下,也就是一般都出现在extension里,便利构造方法需要在init前面加个convenience关键字,并且方法里需要显性地调用一下self.init
convenience init(width: Int) {
// 内部必须得调用一下当前类的主要构造方法
self.init(width: width, height: 0)
}
convenience init(height: Int) {
self.init(width: 0, height: height)
}
convenience init() {
self.init(width: 0, height: 0)
}
}
var size = Size(width: 11, height: 11)
如果存在继承关系时,子类的构造方法里必须手动调用一下父类的构造方法,以确保从父类继承而来的存储属性能被正常赋初始值。
class Animal {
var age: Int
init(age: Int) {
self.age = age
}
}
class Dog: Animal {
var weight: Int
init(age: Int, weight: Int) {
self.weight = weight
super.init(age: age) // 手动调用一下父类的构造方法,不过要先初始化完自己的,再调用
}
}
当对象销毁时,会触发类的deinit
方法,我们称之为反构造方法,类似于OC的dealloc
方法、C++的析构函数。
class Size {
deinit {
print("Size对象销毁了")
}
}
3、对象的内存大小、内存布局
像结构体那样正常分析没有错,只不过对象的话,还得在前面加上类型信息的8个字节和引用计数的8个字节。
然后macOS、iOS下对象的内存大小总是16的倍数,不仅OC对象是这样哦,Swift对象也是这样,因为对象都是通过malloc
函数去分配内存的,那个函数里就做了这样的强制规定。
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
func test() {
var size = Size(width: 11, height: 11) // 栈区变量,堆区对象
print("Size对象:", Mems.size(ofRef: size)) // Size对象: 32
}
test()
print("size变量:", MemoryLayout<Size>.size) // size变量: 8
print("size变量:", MemoryLayout<Size>.stride) // size变量: 8
print("size变量:", MemoryLayout<Size>.alignment) // size变量: 8
因此上面例子中,Size
对象的内存布局大概如下:
三、属性
1、对象属性
- 存储属性
存储属性类似于成员变量的概念,它的值存储在对象的内存中。结构体和类可以定义存储属性,枚举不可以定义存储属性。
- 计算属性
计算属性的本质其实就是一对set
、get
方法(我们可以只实现get
方法——只读,也可以同时实现set
、get
方法——读写),它的值不存储在对象的内存中。枚举、结构体和类都可以定义计算属性。
举个例子:
struct Circle {
// 存储属性,定义属性时必须声明数据类型
var radius: Double
// 计算属性,定义属性时必须声明数据类型
var diameter: Double {
set(diameter) { // set方法
radius = diameter / 2
}
get { // get方法
return radius * 2
}
}
}
var circle = Circle(radius: 11)
print(circle.radius) // 11.0
print(circle.diameter) // 22.0
circle.diameter = 12
print(circle.radius) // 6.0
print(circle.diameter) // 12.0
上面我们说“计算属性的本质其实就是一对set
、get
方法”,所以计算属性diameter
其实就等价于我们在结构体里面自己定义了这么两个方法:
func setDiameter(diameter: Double) {
radius = diameter / 2
}
func getDiameter() -> Double {
return radius * 2
}
2、类属性
Swift除了可以定义对象属性外,还可以通过static
关键字定义类属性,即通过类来调用的属性。
- 存储属性
类存储属性的本质其实就是一个全局变量,存储在全局区,程序运行过程中只有1份内存,只不过普通全局变量直接通过变量名就可以访问,而它则需要通过特定的类来访问,所以可以看作是加了一个访问权限。枚举、结构体和类都可以定义类存储属性。
- 计算属性
类计算属性的本质也是一对set
、get
方法。枚举、结构体和类都可以定义类计算属性。
举个例子:
struct Circle {
static var radius: Double = 11
static var diameter: Double {
set(diameter) {
radius = diameter / 2
}
get {
return radius * 2
}
}
}
print(Circle.radius) // 11.0
print(Circle.diameter) // 22.0
Circle.diameter = 12
print(Circle.radius) // 6.0
print(Circle.diameter) // 12.0
3、lazy
+ 闭包和属性观察器
-
lazy
+ 闭包
lazy
+ 闭包类似于OC里的getter
方法(懒加载),用它们定义的属性只有在第一次被用到时才会初始化。lazy
+ 闭包只能应用在var
的、存储属性身上。
不使用lazy
时,即便我们仅仅是用到Person
对象,而没有用到它的属性——Car
对象,那在创建Person
对象时,Car
对象也会被初始化。因为Swift要求在创建结构体或类的实例时,它的每个存储属性都必须得到初始化。
class Car {
var name: String?
var price: Double?
init() {
print("Car init")
}
func run() {
print("Car run")
}
}
class Person {
// 不使用lazy
var car: Car = {
let car = Car()
car.name = "奔驰"
car.price = 35
return car
}()
init() {
print("Person init")
}
func travel() {
self.car.run()
}
}
// 创建Person对象
var person = Person()
//// 控制台打印:
Car init
Person init
而如果我们使用了lazy
,Car
对象在没有用到时就不会被初始化。
class Car {
var name: String?
var price: Double?
init() {
print("Car init")
}
func run() {
print("Car run")
}
}
class Person {
// 使用lazy
lazy var car: Car = {
let car = Car()
car.name = "奔驰"
car.price = 35
return car
}()
init() {
print("Person init")
}
func travel() {
self.car.run()
}
}
// 创建Person对象
var person = Person()
//// 控制台打印:
Person init
只有它真正被用到时才会初始化。
var person = Person()
person.travel()
// 控制台打印:
Person init
Car init
Car run
- 属性观察器
属性观察器类似于OC里的setter
方法,用来监听一个属性被修改掉,我们用新的数据去刷新UI这样的场景。属性观察器只能应用在非lazy
的、var
的、存储属性身上,这三个条件之外的其它属性都不能使用。
struct Circle {
var radius: Double = 11 { // 这里radius还是个存储属性啊,只不过设置了属性观察器
willSet {
print("即将发生变化", "当前是\(radius)", "即将是\(newValue)") // 即将发生变化 当前是11.0 即将是12.0
}
// 实际中主要用didSet方法
didSet {
print("已经发生变化", "当前是\(radius)", "曾经是\(oldValue)") // 已经发生变化 当前是12.0 曾经是11.0
}
}
}
var circle = Circle()
circle.radius = 12
tips:
1️⃣计算属性、
lazy
+ 闭包和属性观察器的样子都是在属性后面有个大括号{}
,那怎么区分它们呢?其实在实际开发中,计算属性总是在属性后面直接就跟上{}
了,而lazy
+ 闭包和属性观察器一般都会在属性后面有个=
,然后才跟上{}
。2️⃣Swift里定义单例非常简单,就是一个类属性,只需要一句话:
class SingletonUtil { // 一句话定义单例,并且是线程安全的 static let shared = SingletonUtil() }
四、方法
枚举、结构体和类里面都可以定义函数,我们把定义在它们内部的函数称为方法。
enum Direction {
case east
case west
case south
case north
func show() {
print("direction is \(self)")
}
}
var direction = Direction.east
direction.show() // direction is east
struct Point {
var x: Int
var y: Int
func show() {
print("x = \(self.x), y = \(self.y)")
}
}
var point = Point(x: 11, y: 12)
point.show() // x = 11, y = 12
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
func show() {
print("width = \(self.width), height = \(self.height)")
}
}
var size = Size(width: 11, height: 12)
size.show() // width = 11, height = 12
方法当然也分为实例方法和类方法:
class Car {
static var count = 0
init() {
Car.count += 1
}
// 实例方法
func run() {
print("Car run")
}
// 类方法
static func getCount() {
print("Car count:", self.count)
}
}
var car1 = Car()
var car2 = Car()
var car3 = Car()
car1.run() // Car run
Car.getCount() // Car count: 3
枚举和结构体的实例方法内部,不允许修改它们自己的属性,如果想要修改需要加上mutating
关键字。
enum StateSwitch {
case low
case middle
case high
mutating func next() {
switch self {
case .low:
self = .middle
case .middle:
self = .high
case .high:
self = .low
}
}
}
struct Point {
var x: Double = 0
var y: Double = 0
mutating func moveBy(deltaX: Double, deltaY: Double) {
x += deltaX
y += deltaY
}
}
补充
Swift里其实只有两种数据类型:值类型和引用类型,值类型的传递肯定就是深拷贝,引用类型的传递肯定就是浅拷贝。
把值类型的变量赋值给另一个let
常量、var
变量或函数的参数,是值传递、深拷贝。
struct Point {
var x: Int
var y: Int
}
var point1 = Point(x: 11, y: 12)
var point2 = point1 // 值拷贝、深拷贝
point2.x = 13
point2.y = 14
print(point1) // Point(x: 11, y: 12)
print(point2) // Point(x: 13, y: 14)
把引用类型的变量赋值给另一个let
常量、var
变量或函数的参数,是引用传递、浅拷贝。
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
var size1 = Size(width: 11, height: 12)
var size2 = size1 // 引用拷贝、浅拷贝
size2.width = 13
size2.height = 14
print(size1.width, size1.height) // 13 14
print(size2.width, size2.height) // 13 14