16 内存布局
| 区域 | 存储内容 | 管理方式 | 生命周期 | 一句话总结 |
|---|---|---|---|---|
| 栈 | 局部变量、函数参数 | 自动 | 函数调用期间 | 快、小、自动 |
| 堆 | 类实例、闭包捕获变量 | ARC | 无强引用时销毁 | 慢、大、ARC管理 |
| 全局区 | 全局变量、静态变量 | 自动 | 程序启动到结束 | 全程存活 |
| 常量区 | 字符串字面量、只读数据 | 自动 | 程序启动到结束 | 全程存活 |
| 代码段 | 函数指令、方法代码 | 自动 | 程序启动到结束 | 全程存活 |
17 属性
口 存储属性(Stored Property )
- 存储在实例的内存中, 需要设置初始值
- 结构体、类可以定义存储属性, 枚举
没有
口计算属性(Computed Property)
- 本质就是方法(函数)
- 不占用实例的内存, 所以
不能设置初始值 - 枚举、结构体、类都可以定义计算属性
17.1 存储属性
存储属性的核心价值是存储数据, 关于存储属性,Swift有个明确的规定
口 在创建类或结构体的实例时,必须为所有的存储属性设置一个合适的初始值
口 可以在初始化器里为存储属性设置一个初始值
口 可以分配一个默认的属性值作为属性定义的一部
enum Season : Int {
case spring = 2, summer = 5 // 这不是存储属性
}
var s = Season.spring
// 枚举没有存储属性, 结构设计如此, 枚举占1个字节,区分case及关联值
17.2 计算属性
计算属性的核心价值是动态计算值 口 大括号内 只能写 getter setter代码段 口 声明计算属性 只能用var 不能用let
// 存储属性
var radius: Int = 0
// 计算属性
var diameter: Int {
set {
radius = newValue / 2
}
get {
radius * 2
}
}
// 计算属性 省略 get{}的
var getDiameter: Int {
return diameter
}
// 枚举的rawValue本质上属于计算属性
enum TestEnum : Int {
case test1 = 1, test2 = 2, test3 = 3
var rawValue: Int {
switch self {
case .test1:
return 10
case .test2:
return 11
case .test3:
return 12
}
}
}
print(TestEnum.test3.rawValue) // 12
17.3 延迟存储属性
class Car {
init() {
print("car--init")
}
func run() {
print("车开动了")
}
}
class Man {
lazy var car: Car = Car()
init() {
print("man--init")
}
}
let m = Man()
// 原始打印
// man -- init
// car -- init
// ---
// 车开动了
// 添加lazy打印
// man -- init
// ---
// car -- init
// 车开动了
17.4 属性观察器 (Property Observer)
可以为非lazy 的 var 存储属性设置属性观察器
// 存储属性才能设置属性观察器 ( 虽然和计算属性长的很像 )
// so 存储属性设置默认值很正常了
struct Circle {
var radius: Double = 0 {
willSet {
print(newValue)
}
didSet {
print(oldValue,radius)
}
}
}
var cir = Circle()
cir.radius = 10.0
// 10.0
// 0.0 10.0
17.5 inout输入输出函数
struct Shape {
var width: Int // 存储属性
var side: Int { // 存储属性
willSet {
print("willsetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
var girth: Int { // 计算属性
set {
width = newValue / side
print ("setGirth", newValue)
}
get {
print("getGirth")
return width * side
}
}
func show() {
print("width=\(width), side=\(side), girth=\(girth)")
}
}
// 错误写法
func test(_ number: Int) {
number = 20 // Cannot assign to value: 'number' is a 'let' constant
}
// 正确写法
func test(_ number: inout Int) {
number = 20
}
var s = Shape(width: 10, side: 4) // 初始化不调用属性观察器
test (&s.width)
s.show()
print("---")
// getGirth -- ps: s.show()打印的
// width=20, side=4, girth=80
// ---
test(&s.side)
s.show()
print("---")
// willsetSide 20
// didSetSide 4 20
// getGirth
// width=20, side=20, girth=400
// ---
test (&s.girth)
s.show()
// getGirth -- ps: test (&s.girth)
// setGirth 20 -- ps: ps: test (&s.girth)
// getGirth -- ps: s.show()
// width=1, side=20, -- girth=20 ps: s.show()
17.6 类型属性 static class
严格来说,属性可以分为: 实例属性, 类型属性
-
实例属性(Instance Property):只能通过实例去访问, 包含: 存储实例属性, 计算实例属性
-
类型属性(Type Property):只能通过类型去访问, 包含: 存储类型属性, 计算类型属性
-
可以通过static定义类型属性, 如果是类,可用Class定义可重写的类型属性
static var age: Int = 18 // 存储属性
static var name: String { // 注意这是个计算属性
"jack"
}
override func viewDidLoad() {
super.viewDidLoad()
let age = FTXibViewController.age
let name = FTXibViewController.name
}
不同于存储实例属性,你必须给存储类型属性设定初始值
- 因为类型没有像实例那样的 init 初始化器来初始化存储属性
存储类型属性默认就是lazy,会在第一次使用的时候才初始化
- 就算被多个线程同时访问,保证只会初始化一次口
- 存储类型属性可以是let
枚举类型也可以定义类型属性(存储类型属性、计算类型属性)
class Animal {
// 使用 class 定义可重写的类型计算属性
class var species: String {
return "动物"
}
// 使用 static 定义不可重写的类型计算属性
static var kingdom: String {
return "动物界"
}
}
class Dog: Animal {
// ✅ 可以重写 class 属性
override class var species: String {
return "犬科动物"
}
// ❌ 不能重写 static 属性
// override static var kingdom: String { ... } // 错误!
}
// 枚举的类型属性
enum Shape {
static var width: Int = 0
var height: Int { // 这个看着没啥用,但是计算属性rowValue还是有用的
0
}
case s1, s2, s3, s4
}
var s = Shape.s1 // 通过枚举名, 访问枚举实例
var s1 = Shape.width // 通过枚举名, 访问枚类型属性
var s2 = Shape.s2.height // 通过实例访问普通计算属性
18 单例
public class FileManager { // 公开的类,可被其他模块访问
public static let shared = FileManager() // 公开的静态常量,持有唯一实例
private init() {} // 私有构造器,外部无法创建新实例
}
// ✅ 获取单例
let manager = FileManager.shared
// ❌ 无法创建新实例
let another = FileManager() // 编译错误:init is private
19 方法
枚举、结构体、类都可以定义实例方法、类型方法
-
实例方法(Instance Method):通过实例对象调用
-
类型方法(Type Method):通过类型调用,用static或者class关键字定义
class Car {
static var cout = 0
static func getCount() -> Int {
cout
}
init() {
Car.cout += 1
}
}
let c0 = Car()
let c1 = Car()
let c2 = Car()
print(Car.getCount()) // 3
20 mutating
作用: 添加mutating关键字, 实现值类型内部实例方法修改自身属性值
mutating: 转换/转变
// 结构体实例方法 修改自身属性
struct Point {
var x = 0.0
var y = 0.0
mutating func moveBy(deltaX: Double, deltaY: Double) {
x += deltaX
y += deltaY
// self = Point(x: × + deltaX, y: y + deltaY)
}
}
// 直接修改方法传入的参数值
func test(num1: inout Int) {
num1 = 10
}
21 discardableResult ( 可丢弃的结果 )
- 作用: 消除函数调用后返回值未使用的警告 (消除警告)
@discardableResult func get() -> Int {
return 10
}
func get1() -> Int {
return 10
}
get()
get1() // ⚠️ Result of call to 'get1()' is unused
22 下标 subscript
作用: 本质是函数, 可以像数组一样调用函数
- 使用subscript可以给任意类型(枚举、结构体、类)增加下标功能,有些地方也翻译为:下标脚本
- subscript的语法类似于实例方法、计算属性,本质就是方法(函数)
// 实例下标
// 给类 Point 添加下标, 使其可以类似数组一样 丝滑获取及修改属性x,y
class Point {
var x = 0.0
var y = 0.0
subscript(index: Int) -> Double {
set {
if index == 0 {
x = newValue
} else if index == 1 {
y = newValue
}
}
get {
if index == 0{
return x
} else if index == 1 {
return y
}
return 0
}
}
}
let p = Point()
p[0] = 11.1
p[1] = 22.2
print(p.x) // 11.1
print (p.y) // 22.2
print(p[0]) // 11.1
print(p[1]) // 22.2
// 类型下标
class Sum {
static subscript(v1: Int, v2: Int) -> Int {
return v1 + v2
}
}
print(Sum[10,20]) // 30
23 继承 ( Inheritance )
值类型(枚举、结构体)不支持继承,只有类支持继承
- 没有父类的类,称为:基类
- Swift并没有像OC、Java那样的规定:任何类最终都要继承自某个基类
子类可以重写父类的下标、方法、属性,重写必须加上 override 关键字
23.2 重写实例方法及下标 overrride
- 想实现父类方法, 调用super
- 不想实现父类方法, 不调用super
- swift 重写父类方法, 强制要求加 overrride, 这点不同于oc 的直接覆盖
class Animal {
func speak() {
print("Animal speak")
}
subscript(index: Int) -> Int {
print("---\(index)")
return index
}
}
class Cat: Animal {
override func speak() {
super.speak() //不想实现父类方法, 可以不调用super
print("Cat speak")
}
override subscript(index: Int) -> Int {
super[index]
print("---\(index + 1)")
return index + 1
}
}
let a = Animal()
a.speak()
a[6]
// Animal speak
// ---6
let c = Cat()
c.speak()
c[6]
// Animal speak
// Cat speak
// ---6
// ---7
24.2 重写类型方法及下标 class
-
被class修饰的类型方法、下标,
允许被子类重写 -
被static修饰的类型方法、下标,
不允许被子类重写
class Animal {
class func speak() {
print("Animal speak")
}
class subscript(index: Int) -> Int {
print("---\(index)")
return index
}
}
class Cat: Animal {
class override func speak() {
super.speak()
print("Cat speak")
}
class override subscript(index: Int) -> Int {
super[index]
print("---\(index + 1)")
return index + 1
}
}
Animal.speak()
Animal[6]
// Animal speak
// ---6
Cat.speak()
Cat[6]
// Animal speak
// Cat speak
// ---6
// ---7
24.3 重写属性 override
子类可以将父类的属性(存储、计算)重写为计算属性
子类不可以将父类属性重写为存储属性
只能重写var属性,不能重写let属性
重写时,属性名、类型要一致
子类重写后的属性权限 不能小于 父类属性的权限
- 如果父类属性是只读的,那么子类重写后的属性可以是只读的、也可以是可读写的
- 如果父类属性是可读写的,那么子类重写后的属性也必须是可读写的
class Circle {
// 存储属性 添加属性观察器
var radius: Int = 0 {
willSet {
print("--circle--will- \(newValue)")
}
didSet {
print("--circle--did- \(oldValue)--\(radius)")
}
}
var diameter: Int = 0 {
willSet {
print("--circle--will- \(newValue)")
}
didSet {
print("--circle--did- \(oldValue)--\(diameter)")
}
}
}
class SubCircle: Circle {
// 计算属性
override var radius: Int {
set {
print("SubCircle setRadius")
super.radius = newValue > 0 ? newValue : 0
}
get {
print("Sub(ircle getRadius")
return super.radius
}
}
override var diameter: Int {
set {
print("'SubCircle setDiameter")
super.diameter = newValue > 0 ? newValue : 0
}
get {
print("SubCircle getDiameter")
return super.diameter
}
}
}
var circle = SubCircle()
circle.radius = 6 // SubCircle setRadius, --circle--will- 6, --circle--did- 0--6
print(circle.diameter) // SubCircle getDiameter , 0
24.4 属性监听 willSet didSet
- 计算属性不可以和属性观察器一起写, 但是通过子类可以实现对父类的计算属性实现监听
- 计算属性的特殊情况也可以直接监听了
class Rectangle {
// 实例属性
var width: Double = 0 {
willSet {
print("width 将要变成: (newValue)")
}
didSet {
print("width 从 (oldValue) 变成了 (width)")
// 注意:area 是计算属性,改变 width/height 会自动重算
}
}
// 类型属性
static var height: Double = 0 {
willSet {
print("--Rectangle--will- \(newValue)")
}
didSet {
print("--Rectangle--did- \(oldValue)--\(height)")
}
}
//
```
// 计算属性
var area: Double {
get {
print("正在读取 area 的值") // 获取当前值
return width * height
}
set {
print("正在设置 area 为: (newValue)") // 获取新值
width = sqrt(newValue)
height = sqrt(newValue)
}
}
}
class SubRectangle: Rectangle {
// 监听父类的计算属性 area
override var area: Double {
willSet {
print("SubRectangle----\(newValue)")
}
didSet {
print("SubRectangle----\(oldValue)")
}
}
}
24 final 函数
- 被final修饰的方法、下标、属性,禁止被重写
- 被final修饰的类,禁止被继承
ps: final: 终结
// 方法禁止被重写
class Rectangle {
final func test(){}
}
class SubRectangle: Rectangle {
override func test(){} // ❌ Instance method overrides a 'final' instance method
}
// 类禁止被继承
final class Rectangle {
final func test(){}
}
class SubRectangle: Rectangle { // ❌ Inheritance from a final class 'Rectangle'
}
25 初始化器
类、结构体、枚举, 都可以定义初始化器
指定初始化器(designated initializer ): 确保所有属性都被初始化
便捷初始化器(convenience initializer ): 确保所有属性都被初始化 ( 代码层面书写更简单 )
- 指定初始化器, 和便捷初始化器 都可一设置多个
// 指定初始化器 vs 便捷初始化器
class Person {
var name: String
var age: Int
// 指定初始化器:确保所有属性都被赋值
init(name: String, age: Int) {
self.name = name // ✅ name 被初始化
self.age = age // ✅ age 被初始化
}
// 便捷初始化器:只是叫指定初始化器干活
convenience init(name: String) {
self.init(name: name, age: 0) // 调用指定初始化器
}
}
let p = Person(name: "张三", age: 20) // 对象完整可用
let p1 = Person(name: "张三") // 调用简单了些
class VipUser: User {
var level: Int
// 子类的指定初始化器
init(name: String, age: Int, level: Int) {
self.level = level
super.init(name: name, age: age) // 不写会报错!, 确保子类的所有属性都初始化
}
}
25.1 初始化器继承
- 指定初始化器子类也能继承
- 如果父类自定义了指定初始化器, 子类的默认指定初始化器会被覆盖
class Animal {
var name: String
init(name: String) {
self.name = name
}
}
// 子类没有定义任何初始化器
class Dog: Animal {
}
let dog = Dog(name: "旺财")
// let dog1 = dog() // ❌ 默认初始器被覆盖
26 require 强制重写关键字
作用:强制所有子类必须实现该初始化器(要么继承,要么重写)
- 如果子类重写了
required初始化器,也必须加上required, 不用加overrid
class Animal {
var name: String
// required 修饰:所有子类都必须有这个初始化器(继承 或 重写)
required init(name: String) {
self.name = name
}
}
// 继承
class Pig: Animal {
}
// 重写
class Dog: Animal {
var breed: String
// 必须实现 required 初始化器
required init(name: String) {
self.breed = "Unknown"
super.init(name: name)
}
}
// 重写
class Cat: Animal {
var color: String
// 自己的指定初始化器
init(name: String, color: String) {
self.color = color
super.init(name: name)
}
// 因为有了自己的指定初始化器,必须重写 required
required init(name: String) {
self.color = "White"
super.init(name: name)
}
}
27 可失败初始化器 init?
- 可以创建失败 nil
// 类
class Person {
var name: String
init?(name: String) {
if name.isEmpty {
self.name = name
}
return nil
}
}
// ⚠️ !!! 正常初始化器 是不能返回的
// 枚举
enum Temperature {
case hot, cold
init?(celsius: Double) {
if celsius > 30 {
self = .hot
} else if celsius < 10 {
self = .cold
} else {
return nil
}
}
}
// 结构体
struct Size {
var width: Double
init?(width: Double) {
if width < 0 {
return nil
}
self.width = width
}
}
28 反初始化器 deinit
- 类似 oc 的 dealloc
- deinit 不接受任何参数,不能写小括号,不能自行调用
- 父类的 deinit 能被子类继承
- 子类的 deinit 实现执行完毕后会调用父类的deinit
// 父类
class Animal {
var age: Int = 0
init(age: Int) {
self.age = age
}
deinit {
print("animal is die")
}
}
// 子类
class Dog: Animal {
deinit {
print("dog is die")
}
}
Dog(age: 5)
// dog is die
// animal is die
29 协议
作用: 继承了协议, 就要遵守协议的规则(方法, 属性, 下标等)
- 协议中定义方法时不能有默认参数值
- 默认情况下,协议中定义的内容必须全部都实现
- 定义属性必须var
29.1 遵守多个协议
// 声明协议
protocol Test1 {}
protocol Test2 {}
protocol Test3 {}
// 同时遵守多个协议
class TestClass : Test1, Test2, Test3 {}
// 声明协议
protocol Drawable {
func draw()
var x: Int { get set } // 读/写 属性
var y: Int { get } // 只读属性
subscript(index: Int) -> Int { get set}
}
29.2 遵守 & 实现协议
属性 方法 类型方法 下标
// 遵守协议
class Woman : Drawable {
var x: Int = 0 // var 实现读写属性
let y: Int = 0 // let 实现仅读属性
func draw() {
print ("Woman draw")
}
static func run() { // static不可重写
print ("Woman run")
}
subscript(index: Int) -> Int {
set {}
get { index }
}
}
// 遵守协议
class Man : Drawable {
var x: Int { // 计算属性实现
set {
print(newValue)
}
get {
0
}
}
var y: Int { // 计算属性实现
get {
return 0
}
}
func draw() {
print ("Man draw")
}
class func run() { // class 可以重写
print ("Man run")
}
subscript(index: Int) -> Int {
set {}
get { index }
}
}
29.3 协议中 mutating
// mutating作用: 结构体, 枚举自身实例方法,能修改自身存储属性
protocol Drawable {
mutating func draw() // 如果值类型遵守协议,k可以在这方法中修改自身存储属性
}
// 我是类 可以不加
class Size : Drawable {
var width: Int = 0
func draw() {
width = 10
}
}
// 我是值类型 加上了, 可以修改自身属性值
struct Point1 :Drawable {
var x: Int = 0
mutating func draw() {
x = 10
}
}
29.4 协议中 init init? init!
协议中定义的init?、init!,可以用init、init?、init!去实现
protocol Livable {
init ()
init?(age: Int)
init!(no: Int)
}
class Person : Livable {
required init() {}
required init?(age: Int) {}
required init! (no: Int) {}
}
29.5 协议继承
- 协议可以继承
protocol Runnable {
func run()
}
protocol Livable: Runnable {
func breath()
}
class Person: Livable {
func breath(){}
func run(){}
}
29.6 协议组合
protocol Livable {}
protocol Runnable {}
class Person {}
// 接收Person或者其子类的实例
func fn0(obj: Person) {}
// 接收遵守Livable协议的实例
func fn1(obj: Livable) {}
// 接收同时遵守Livable、Runnable协议的实例
func fn2(obj: Livable & Runnable) {}
// 接收同时遵守Livable、Runnable协议、并且是Person或者其子类的实例
func fn3(obj: Person & Livable & Runnable) {}
// 接收同时遵守Livable、Runnable协议、并且是Person或者其子类的实例
typealias RealPerson = Person & Livable & Runnable
func fn4 (obj: RealPerson){}
29.6 遵守协议日常
enum Season : CaseIterable {
case spring, summer, autumn, winter
}
// 枚举遵守了协议可以使用 Season.allCases 类方法
let seasons = Season.allCases
print(seasons.count) // 4
for season in seasons {
print (season)
}
// spring summer autumn winter
// 打印协议 CustomstringConvertible、 CustomDebugStringConvertible 协议
30 类型 Any AnyObject
Any: 存放任何类型
AnyObject: class的实例, (Objective‑C 里 id 对应 AnyObject)
// Any: 存放任何类型
var a: Any = 10
a = "hi"
a = [1,2,3]
a = { print(1) }
a = test()
a = TestViewController.test1()
a = {(v1:Int, v2:Int) in v1 + v2}
// AnyObject: 类的实例
struct mModel {
var name: String
var age: Int
}
var b: AnyObject = mModel(name: "小华", age: 10) // ❌ 报错
var c: AnyObject = Person()
// 定义协议 (仅仅能被类遵守)
protocol XXProtocol: AnyObject {
var num: Int{get set}
}
struct model: XXProtocol { // ❌ Non-class type 'model' cannot conform to class protocol 'XXProtocol' 非类类型的model 不能遵守协议
var num: Int = 0
var name: String
var age: Int
}
class TestViewController: UIViewController, XXProtocol {
var num: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
}
}