Swift学习

Swift语法知识汇总(上)

2020-06-12  本文已影响0人  Amok校长

导语

历时5年发展, 从swift1.x发展到了swift5.x版本, 经历了多次重大改变, ABI终于稳定.

API(Application Programming Interface): 应用程序编程接口
    源代码和库之间的接口
ABI(Application Binary Interface): 应用程序二进制接口
    应用程序与操作系统之间的底层接口
    设计的内容有: 目标文件格式(maco格式)、数据类型大小/布局/对齐,函数调用约定等等

随着ABI的稳定, swift语法基本不会再有太大的变动, 此时正在学习swift的最佳时刻

编译器分前端和后端:

1.前端: 语法分析...
2.后端:生成对应平台的二进制代码

编译流程:
OC
c --> (编译前端)clang --> (编译后端)LLVM IR 通过 LLVM compiler --->x86/ARM/other
Swift
Swift --> (编译前端) swiftc --> (编译后端)LLVM IR 通过 LLVM compiler --->x86/ARM/other

Swift编译大概流程:
Swift Code(swift代码) --> Swift AST(根据swiftc生成语法树) --> Raw Swift IL(swift特有中间代码) --> Canonical Swift IL(swift特有中间代码更简洁) --> LLVM IR(编译后端生成LLVM中间代码) --> Assembly(汇编代码) --> Executable(二进制代码)

swiftc

swiftc存放在Xcode内部
Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

一些操作
1.生成.swift文件的语法树
  命令行输入:cd (.swift所在文件目录)
  swiftc -dump-ast main.swift
2.生成最简洁的SIL代码: swiftc -emit-sil main.swift
3.生成LLVM IR代码: swiftc -emit-ir main.swift -o main.ll
4.生成汇编代码: swiftc -emit-assembly main.swift -o main.s

对汇编代码进行分析, 可以真正掌握编程语言本质

断点后,转汇编代码查看: Debug >> Debug Workflow >> AlwaysShow Dissassembly(永远显示反汇编,就是把你平时写的代码转换成汇编代码)

第一章 基本运算、流程控制、函数**

1. Hello World

1.不用编写main函数, Swift将全局范围内的首句可执行代码作为程序入口
2.一句代码尾部可以省略分号(;), 多句代码写到同一行时必须用分号(;)隔开
3.用var定义变量, let定义常量, 编译器能自动推断出变量/常量的类型
4.Playground可以快速预览代码效果,是学习语法的好帮手

快捷键:
Command + Shift + Enter: 运行整个Playground

var a = 10
print("Hello Wrold! - \(a)")//拼接字符串

2. Playground

快捷键:
Command + 1 显示左测文件栏
Command + 0 隐藏左侧文件栏
Command + N 新建代码文件page页

import UIKit
import PlaygroundSupport

//使用Playground展示一个view
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
view.backgroundColor = UIColor.red
PlaygroundPage.current.liveView = view

//使用Playground展示一个图片
let imageView = UIImageView(image: UIImage(name: "logo"))
PlaygroundPage.current.liveView = imageView

//使用Playground展示一个视图控制器
let vc = UITableViewController()
vc.view.backgroundColor = UIColor.lightGray
PlaygroundPage.current.liveView = vc

//Playground--多Page
File >> New >> Playground Page

3. 注释

0. Swift支持注释嵌套

// 单行注释

/*
多行注释
*/

/*
1
// 多行注释嵌套单行注释
/* 多行注释嵌套多行注释 */
2
*/

1.Playground的注释支持markup语法(与markdown类似) //: 或 /*:*/
    单行markup:
    编写markup语法 //: # 一级标题

    多行markup:
    编写markup语法
    /*:
            # 学习Swift
            ## 基础语法
            - 变量
            - 常量
            ## 面向对象
            - 类
            - 属性
    */
    运行markup语法 Editor >> Show Rendered Markup

4. 常量

// 只能赋值1次
// 它的值不要求在编译时期确定, 在运行时确定也可以, 但使用之前必须赋值1次
let age1 = 10

let age2: Int //这种写法,必须说明类型
age2 = 20

func getAge() -> Int {
  return 30
}
let age3 = getAge()//在运行时确认值

// 常量、变量在初始化之前, 都不能被使用, 否则会报错
let age: Int
var height: Int
print(age)//报错
print(height)//报错

5. 标识符_数据类型

// 标识符(比如常量名、变量名、函数名)几乎可以使用任何字符
// 标识符不能以数字开头, 不能包含空白字符、制表符、箭头等特殊字符
常见的数据类型: Swift常见数据类型.png
整数类型: Int8、Int16、Int32、Int64、UInt8、UInt16、UInt32、UInt64
在32bit平台, Int等价于Int32, 在64bit平台, Int等价于Int64
整数的最值: UInt8.max、Int16.min
一般情况下, 都是直接使用Int即可

浮点类型:Float, 32位, 精度只有6位; Double, 64位, 精度至少15位.

6. 字面量

// 布尔
let bool = true // 取反是false

// 字符串
let string = "小哥哥"

// 字符 (可存储ASCII字符、Unicode字符)
let character: Character = "🐶"

// 整数
let intDecimal = 17 //十进制
let intBinary = 0b10001 //二进制
let intOctal = 0o21 // 八进制
let intHexadecimal = 0x11 //十六进制

// 浮点数
let doubleDecimal = 125.0 //十进制,等价于1.25e2,  0.0125等价于1.25e-2
let doubleHexadecimal1 = 0xFp2//十六进制, 意味着15x(2^2), 相当于十进制的60.0
let doubleHexadecimal2 = 0xFp-2//十六进制,意味着15x(2^-2),相当于十进制的3.75
//以下都是表示12.1875
//十进制:12.1875、1.21875e1
//十六进制: 0xC.3p0

// 整数和浮点数可以添加额外的零或者添加下划线来增强可读性
    比如: 100_0000、1_000_000.000_000_1、000123.456

// 数组
let array = [1,3,5,7,9]

// 字典
let dictionary = ["age" : 18, "height" : 165]

7. 类型转换

// 整数转换
let int1: UInt16 = 2_000
let int2: UInt8 = 1
let int3 = int1 + UInt16(int2)

// 整数、浮点数转换
let int = 3
let double = 0.14159
let pi = Double(int) + double
let intPi = Int(pi)

// 字面量可以直接相加, 因为数字字面量本身没有明确的类型
let result = 3 + 0.14159

8. 元组(tuple)

let http404Error = (404, "Not Found") //将多种类型组合在一起, 赋值给元祖对象
print("The status code is \(http404Error.0)")

let(statusCode, statusMessage) = http404Error //接收元祖
print("The status code is \(statusCode)")

let(justTheStatusCode, _) = http404Error //不想接收的值,用_占位

let http200Status = (statusCode: 200, description: "OK")
print("The status code is \(http200Status.statusCode)")

第二章 if-else和while、区间、Switch、函数

1、if-else 和 while

// 1.if-else
/*
  if后面的条件可以省略小括号;
  条件后面的大括号不可以省略;
  if后面的条件只能是Bool类型;
*/

// 2.while
/*
  repeat-while相当于C语言中的do-while;
  这里不用num--,是因为从Swift3开始,去除了自增(++)、自减(--)运算符
*/
var num = 5
while num > 0{
  print("num is \(num)")
  num -= 1
}//打印了5次

var num = -1
repeat {
  print("num is \(num)")
}while num > 0 //打印了1次

2-1、区间

// 闭区间运算符: a...b 其实就是 a <= 取值 <= b 
let names = ["a","b","c","d"]
for i in 0...3 {
  print(name[I])
}// a b c d

for name in names[0...3]{// for-区间运算符用在数组上
  print(name)
}// a b c d

let range = 1...3
for i in range {
  print(name[I])
}// b c d

let a = 1
let b = 2
for i in a...b{// 可以用变量来作为区间
  print(name[I])
}// b c
for i in a...3{
  print(name[I])
}// b c d

for var i in 1...3{// i 默认是let, 有需要时可以声明为var
  i += 5
  print(i)
}//6 7 8

for _ in 1...3 {// 如果用不到i, 用_占位
  print("for")
}//打印了3次

2-2、半开区间运算符: a..<b 其实就是 a <= 取值 < b

for i in 1..<5 {
  print(i)
}//1 2 3 4

2-3、单侧区间: 让区间朝一个方向尽可能的远

for name in names[2...] {
  print(name)
}// c d

for name in names[...2] {
  print(name)
}// a b c

for name in names[..<2] {
  print(name)
}// a b

let range = ...5
range.contains(7)//false
range.contains(4)//true
range.contains(-3)//true

2-4、区间类型

let range1: ClosedRange<Int> = 1...3
let range2: Range<Int> = 1..<3
let range3: PartialRangeThrough<Int> = ...5

//字符、字符串也能使用区间运算符, 但默认不能用在for-in中
let stringRange1 = "cc"..."ff" //CloseRange<String>
stringRange1.contains("cb")//false
stringRange1.contains("dz")//true
stringRange1.contains("fg")//false

let stringRange2 = "a"..."f"
stringRange2.contains("d")//true
stringRange2.contains("h")//false

// \0到~囊括了所有可能要用到的ASCII字符
let characterRange: CloseRange<Character> = "\0"..."~"
characterRange.contains("G")//true

2-5、带间隔的区间值

let hours = 11
let hourInterval = 2
// tickMark的取值: 从4开始, 累加2, 不超过11
for tickMark in stride(from: 4, through: hours, by: hourInterval){
  print(tickMark)
}// 4 6 8 10

3、switch

/*
    case、default后面不能写大括号{} ;
    默认可以不写break, 并不会贯穿到后面的条件;
    使用fallthrough可以实现贯穿效果;
    switch必须要保证能处理所有情况, 否则会报错;
    case、default后面至少要有一条语句, 如果不想做任何事, 加个break即可;
*/
var number = 1
switch number {
  case 1:
    print("number is 1")
    break
  case 2:
    print("number is 2")
    break
  default:
    print("number is other")
    break
}// number is 1

switch number {//默认可以不写break, 并不会贯穿到后面的条件
  case 1:
    print("number is 1")
    fallthrough//使用fallthrough可以实现贯穿效果
  case 2:
    print("number is 2")
  default:
    break//case、default后面至少要有一条语句;
}// number is 1  \n number is 2

/*
    switch注意点:
        如果能保证已处理所有情况, 也可以不必使用default;
*/
enum Answer {case right, wrong}
let answer = Answer.right
switch answer {
  case Answer.right:
    print("right")
  case Answer.wrong:
    print("wrong")
}

switch answer {//由于已确定answer是Answer类型, 因此可以省略Answer
  case .right:
    print("right")
  case .wrong:
    print("wrong")
}

3-1、复合条件

//switch也支持Character、String类型
let string = "Jack"
switch string {
  case "Jack", "Rouse"://满足其中一个条件就执行
    print("Right person")
  default:
    break
}//Right person

3-2、区间匹配、元组匹配

//可以使用下划线_忽略某个值
//关于case匹配问题, 属于模式匹配的范畴,以后会再次详细展开讲解
let count = 62
switch count {
  case 0:
    print("none")
  case 1..<5: //区间匹配
    print("a few")
  case 5..<12:
    print("several")
  case 12..<100:
    print("dozens of")
  case 100..<1000:
    print("hundreds of")
  default:
    print("many")
}// dozens of

let point = (1, 1)
switch point {
  case (0, 0)://元组匹配
    print("the origin")
  case (_,0):
    print("on the x-axis")
  case (0,_):
    print("on the y-axis")
  case (-2...2, -2...2):
    print("inside the box")
  default:
    print("outside of the box")
}// inside the box

3-3、值绑定

let point = (2, 0)
switch point {
  case(let x, 0):
    print("on the x-axis with an x value of \(x)")
  case(0, let y):
    print("on the y-axis with an y value of \(y)")
  case let(x, y):
    print("somewhere else at (\(x), \(y))")
}// on the x-axis with an x value of 2

3-4、where--过滤不满足条件的信息

let point = (1, -1)
switch point {
  case let (x,y) where x==y: //值绑定后,加条件
    print("on the line x==y")
  case let (x,y) where x==-y:
    print("on the line x==-y")
  case let (x,y):
    print("(\(x), \(y)) is just some arbitrary point")
}// on the line x == -y

//将所有正数加起来
var numbers = [10,20,-10,-20,30,-30]
var sum = 0
for num in numbers where num > 0 {//使用where来过滤num
  sum += num
}
print(sum) //60

3-5、标签语句

outer: for i in 1...4 {// 如果现在内层嵌套的for循环里面写continue和break想控制外面的for循环,那么加上outer标签就好了
  for k in 1...4 {
    if k==3 {
      continue outer
    }
    if i==3 {
      break outer
    }
    print("i == \(i), k == \(k)")
  }
}

4、函数

func pi() --> Double {//不带参数, 有返回值
  return 3.14
}

func sum(v1: Int, v2: Int) -> Int {//带参数, 有返回值
  return v1 + v2
}
sum(v1: 10,v2: 20)
// 形参默认是let, 也只能是let

func sayHello() -> Void {//写法一:无返回值
  print("Hello")
}
func sayHello() -> () {//写法二:无返回值, 小括号代表空元组
  print("Hello")
}
func sayHello() {//写法三:无返回值
  print("Hello")
}

4-1、隐式返回(Implicit Return)

//如果整个函数体是一个单一表达式,那么函数会隐式返回这个表达式
  func sum(v1: Int, v2: Int) -> Int {
    v1 + v2
  }
  sum(v1: 10, v2: 20)// 30

4-2、返回元组: 实现多返回值

func calculate(v1: Int, v2: Int) -> (sum:Int, difference: Int, average: Int){
  let sum = v1 + v2
  return (sum, v1 - v2, sum >> 1)
}
let result = calculate(v1: 20, v2: 10)
result.sum //30
result.difference //10
result.average //15

4-3、函数的文档注释

/// 求和[概述]
///
/// 将两个整数相加[更详细的描述]
///
/// - Parameter v1: 第1个整数
/// - Parameter v2: 第2个整数
/// - Returns: 2个整数的和
///
/// - Note:传入两个整数即可[批注]
///
func sum(v1: Int, v2: Int) -> Int {
  v1 + v2
}
// option + command + / 快速注释快捷键
// 参考: https://swift.org/documentation/api-design-guidelines/

4-4、参数标签(Argument Label)

//可以修改参数标签
func goToWork(at time:String) {//调用时at替代time作为参数名
  print("this time is \(time)")
}
goToWork(at: "00:00")//this time is 00:00

//可以使用下划线_ 省略参数标签
func sum(_ v1: Int, _ v2: Int) -> Int {
  v1 + v2
}
sum(10, 20)

4-5、默认参数值(Default Parameter Value)

//参数可以有默认值
func check(name: String = "nobody", age: Int, job: String = "none"){
  print("name=\(name), age=\(age), job=\(job)")
}
check(name: "Jack")
check(age: 10)
//C++的默认参数值有个限制: 必须从右往左设置. 由于Swift拥有参数标签, 因此并没有此类限制
//但是在省略参数标签时, 需要特别注意, 避免出错

4-6、可变参数

fuc sum(_ numbers:Int...) -> Int {//...代表可变参数,意味着可以传很多个Int参数
  var total = 0
  for number in numbers {//numbers暂且认定为数组
    total += number
  }
  return total
}
sum(10, 20, 30, 40)//100
// 一个函数最多只能有1个可变参数
// 紧跟在可变参数后面的参数不能用省略参数标签
func test(_ numbers: Int..., string: String, _ other String){}
test(10, 20, 30, string: "Jack", "Rose")

4-7、输入输出参数(In-Out Parameter)

// 可以用inout定义一个输入输出参数: 可以在函数内部修改外部实参的值
func swapValues(_ v1: inout Int, _ v2: inout Int) {
  let tmp = v1
  v1 = v2
  v2 = tmp
}
var num1 = 10
var num2 = 20
swapValues(&num1, &num2)

func swapValues(_ v1: inout Int, _ v2: inout Int) {//交换两个外部变量的值
  (v1, v2) = (v2, v1)//用元组进行交换
}
// 可变参数不能标记为inout
// inout参数不能有默认值
// 示例代码中inout参数的本质是地址传递(引用传递), 如果传递给inout参数的是计算属性、有监听器的属性等内容,其本质并非引用传递,在[属性]章节再作详细讲解
// inout参数只能传入可以被多次赋值的,也就是说调用时传入v1和v2的,是可以被多次赋值的变量var

4-8、函数重载(Function Overload)

//规则: 函数名相同; 参数个数不同||参数类型不同||参数标签不同
func sum(v1: Int, v2: Int) -> Int {
  v1 + v2
}
func sum(v1: Int, v2: Int, v3: Int) -> Int {
  v1 + v2 + v3
}// 参数个数不同
func sum(v1: Double, v2: Int) -> Double {
  v1 + Double(v2)
}// 参数类型不同
func sum(a: Int, b: Int) -> Int {
  a + b
}//参数标签不同
sum(v1: 10, v2: 20)//30
sum(v1: 10, v2: 20, v3: 30)//60
sum(v1: 10.0, v2: 20)//30.0
sum(a: 10, b: 20)//30
/*函数重载注意点:
  1.返回值类型与函数重载无关;
  2.默认参数值和参数重载一起使用产生二义性时,编译器并不会报错(在C++中会报错);
  3.可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器有可能会报错
*/

4-9、内联函数(Inline Function)

//如果开启了编译器优化(Release模式默认会开启优化), 编译器会自动将某些函数变成内联函数.
//开启:Build Settings >> Swift Compiler - Code Generation >> Optimization Level >> Debug   Optimize for Speed[-O]
//内联函数实质: 将函数调用展开成函数体
//哪些函数不会被内联? 1.函数体比较长的时候 2.包含递归调用 3.包含动态派发(动态绑定)
@inline(never) func test(){//永远不会被内联(即使开启了编译器优化)
  print("test")
}
@inline(__always) func test() {//开启编译器优化后, 即使代码很长,也会被内联(递归调用很熟,动态派发的函数除外)
  print("test")
}
//在Release模式下,编译器已经开启优化,会自动决定哪些函数需要内联,因此没必要使用@inline

4-10、函数类型(Function Type)

//每一个函数都有类型的, 函数类型由形式参数类型、返回值类型组成
func test(){ }// () -> Void 或者 () -> ()

func sum(a:Int, b:Int) -> Int{
  a + b
}// (Int, Int) -> Int
//定义变量
var fn: (Int, Int) -> Int = sum
fn(2, 3) //5 调用时不需要参数标签

//函数类型作为函数参数
func sum(v1: Int, v2: Int) -> Int {
  v1 + v2
}
func difference(v1: Int, v2: Int) -> Int {
  v1 - v2
}
func printResult(_ mathFn: (Int, Int) -> Int, _ a: Int, _ b: Int) {
  print("Result:\(mathFn(a, b))")
}
printResult(sum, 5, 2) // Result:7
printResult(difference, 5, 2) // Result:3

//函数类型作为函数返回值
func next(_ input: Int) -> Int {
  input + 1
}
func previous(_ input: Int) -> Int {
  input - 1
}
func forward(_ forward: Bool) -> (Int) -> Int {
  forward? next : previous
}
forward(true)(3) //4
forward(false)(3)//2  //每一个函数都有类型的, 函数类型由形式参数类型、返回值类型组成
func test(){ }// () -> Void 或者 () -> ()

func sum(a:Int, b:Int) -> Int{
  a + b
}// (Int, Int) -> Int
//定义变量
var fn: (Int, Int) -> Int = sum
fn(2, 3) //5 调用时不需要参数标签

//函数类型作为函数参数
func sum(v1: Int, v2: Int) -> Int {
  v1 + v2
}
func difference(v1: Int, v2: Int) -> Int {
  v1 - v2
}
func printResult(_ mathFn: (Int, Int) -> Int, _ a: Int, _ b: Int) {
  print("Result:\(mathFn(a, b))")
}
printResult(sum, 5, 2) // Result:7
printResult(difference, 5, 2) // Result:3

//函数类型作为函数返回值
func next(_ input: Int) -> Int {
  input + 1
}
func previous(_ input: Int) -> Int {
  input - 1
}
func forward(_ forward: Bool) -> (Int) -> Int {
  forward? next : previous
}
forward(true)(3) //4
forward(false)(3)//2

4-11、typealias用来给类型起别名

typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64

typealias Date = (year: Int, month:Int, day:Int)//用Date代替这个元组
func test(_ date: Date) {
  print(date.0)
  print(date.year)
}
test((2011, 9, 10))

typealias IntFn = (Int, Int) -> Int //用IntFn代替这种函数类型
func difference(v1: Int, v2: Int) -> Int {
  v1 - v2
}
let fn: IntFn = difference
fn(20, 10)//10
func setFn(_ fn: IntFn){ }
setFn(difference)
func getFn() -> IntFn { difference }

//按照Swift标准库的定义, Void就是空元组()
public typealias Void = ()

4-12、嵌套函数(Nested Function)

//将函数定义在函数内部
func forward(_ forward: Bool) -> (Int) -> Int {
  func next(_ input: Int) -> Int {
    input + 1
  }
  func previous(_ input: Int) -> Int {
    input - 1
  }
  return forward ? next : previous
}
forward(true)(3)//4
forward(false)(3)//2

第三章 枚举

1-1、枚举的基本用法

enum Direction {//定义方式一
  case north
  case south
  case east
  case west
}
enum Direction {//定义方式二
  case north, south, east, west
}

var dir = Direction.west //使用方式一
dir = Direction.east
dir = .north
print(dir) // north

switch Direction {//使用方式二
  case .north
    print("north")
  case .south
    print("south")
  case .east
    print("east")
  case .west
    print("west")
}

1-2、关联值(Associated Values)

//有时会将枚举的成员值跟其他类型的关联存储在一起,会非常有用
enum Score { //示例一
  case points(Int)
  case grade(Character)
}
var score = Score.points(96)
score = .grade("A")
switch score {
  case let .points(i):
    print(i,"points")
  case let .grade(i):
    print("grade", i)
}// grade A

enum Date {//示例二
  case digit(year: Int, month: Int, day: Int)
  case string(String)
}
var date = Date.digit(year: 2011 month: 9, day:10)
date = .string("2011-09-10")
switch date {
  case .digit(let year, let month, let day):
    print(year, month, day)
  case let .string(value):
    print(value)
}

1-3、原始值(Raw Values)

// 枚举成员可以使用相同类型的默认值预先关联, 这个默认值叫做: 原始值
enum PokerSuit : Character {
  case spade = "♠"
  case heart = "♥"
  case diamond = "♦"
  case club = "♣"
}
var suit = PokerSuit.spade
print(suit) //spade
print(suit.rawValue)//♠
print(PokerSuit.club.rawValue)// ♣

enum Grade: String {//枚举Grade关联的原始值是String类型
        case perfect = "A"
    case great = "B"
    case good = "C"
    case bad = "D"
}
print(Grade.perfect.rawValue) // A
print(Grade.great.rawValue) // B
print(Grade.good.rawValue) // C
print(Grade.bad.rawValue) // D

1-4、隐式原始值(Implicitly Assigned Raw Values)

//如果枚举的原始值类型是Int、String, Swift会自动分配原始值
enum Direction : String {
  case north = "north"
  case south = "south"
  case east = "east"
  case west = "west"
}
enum Direction: String {//等价于
  case north, south, east, west
}
print(Direction.north) //north
print(Direction.north.rawValue) //north

enum Season : Int { //自动递增
  case spring, summer, autumn, winter
}
print(Season.spring.rawValue) //0
print(Season.summer.rawValue) //1
print(Season.autumn.rawValue) //2
print(Season.winter.rawValue) //3

enum Season : Int { //自动递增
  case spring = 1, summer, autumn = 4, winter
}
print(Season.spring.rawValue) //1
print(Season.summer.rawValue) //2
print(Season.autumn.rawValue) //4
print(Season.winter.rawValue) //5

1-5、递归枚举

//定义一个枚举类型,枚举的成员里面的关联值也用到了这个枚举类型,就是递归枚举.必须加关键字indirect
indirect enum ArithExpr {//递归枚举写法一(多用)
  case number(Int)
  case sum(ArithExpr, ArithExpr)
  case difference(ArithExpr, ArithExpr)
}
enum ArithExpr {//递归枚举写法二
  case number(Int)
  indirect case sum(ArithExpr, ArithExpr)
  indirect case difference(ArithExpr, ArithExpr)
}

let five = ArithExpr.number(5)//获取枚举成员变量
let four = ArithExpr.number(4)
let two = ArithExpr.number(2)
let sum = ArithExpr.sum(five, four)
let difference = ArithExpr.difference(sum, two)

func calculate(_ expr: ArithExpr) -> Int {
  switch expr {
    case let .number(value):
        return value
    case let .sum(left, right):
        return calculate(left) + calculate(right)
    case let .difference(left, right):
        return calculate(left) - calculate(right)
  }
}
calculate(difference)//使用递归枚举

2、MemoryLayout

可以使用MemoryLayout获取数据类型所占用的内存大小
var age = 10
//使用方法一: MemoryLayout支持泛型
MemoryLayout<Int>.size //占8个字节数(分配占用的空间大小)
MemoryLayout<Int>.stride //占8个字节(实际用到的空间大小)
MemoryLayout<Int>.alignment //(alignment:内存对齐参数),占用8个字节

//使用方法二:
MemoryLayout.size(ofValue: age)
MemoryLayout.stride(ofValue: age)
MemoryLayout.alignment(ofValue: age)


enum Password {
  case number(Int, Int, Int, Int)//此处是关联值,关联值会存储到枚举中去,占用32个字节
  case other//1
}
var pwd = Password.number(5,6,4,7) //编译器分配给它多少内存? 32
pwd = .other //因为是同一个变量,所以还是占用32个字节
MemoryLayout<Password>.size //占33个字节数,(实际用到的空间大小)
MemoryLayout<Password>.stride //占40个字节,(分配占用的空间大小)
MemoryLayout<Password>.alignmen //占8个字节,(对齐参数,此时要求分配空间要是8的倍数)

enum Season {
  case spring = 1, summer, autumn, winter //此处是原始值,它是不会存储到枚举变量里面去的,底层用1个字节存储就可以了
}
MemoryLayout<Int>.size //1
MemoryLayout<Int>.stride //1
MemoryLayout<Int>.alignment//1
//为什么是1个字节? 因为是关联值与原始值的区别: 原始值(定义成员的时候就给它一个默认值),原始值会永远跟你的成员保存在一起,原始值是固定死的,不允许传值; 关联值的特点是允许自己传入不同的值进来.
总结: 如果枚举变量是关联值,到时候可以传入一个具体的值和枚举进行关联,那么传进去的这些值是直接存储到枚举变量内存里面; 如果枚举类型写个:Int之类的什么类型的,叫做原始值,原始值是跟每一个成员是固定绑在一起的, 但是这些原始值是不会占用你的枚举变量的内存的, 相当于我们的原始值不是存储在枚举变量的内存里面的.

3、可选项(Optional)

//可选项, 一般也叫可选类型, 它允许将值设置为nil
//在类型名称后面加个?来定义一个可选项
var name: String? = "Jack"//默认情况下是不允许你给空值的,只有设置为可选项才可以设置为nil
name = nil

var age: Int? //默认就是nil
age = 10
age = nil

var array = [1, 15, 40, 29]
func get(_ index: Int) -> Int? {//表明可以返回nil或Int类型
  if(index < 0 || index >= array.count){
    return nil
  }
  return array[index]
}

print(get(1)) // Optional(15)
print(get(-1))// nil
print(get(4))// nil

3-1、强制解包(Forced Unwrapping)

/*
    可选项是对其他类型的一层包装, 可以将它理解为一个盒子,
    如果为nil, 那么它是个空盒子,
    如果不为nil, 那么盒子里装的是: 被包装类型的数据;
    如果要从可选项中取出被包装的数据(将盒子里装的东西取出来), 需要使用感叹号!进行强制解包.
*/
var age: Int? = 10
let ageInt: Int = age!
ageInt += 10
//如果对值nil的可选项(空盒子)进行解包, 将会产生运行时错误. 所以强制解包时一定要确认它不是nil

3-2、判断可选值是否包含值

let number = Int("123")
if number != nil {
   print("字符串转换整数成功:\(number!)")
}else{
  print("字符串转换整数失败")
}// 字符串转换整数成功:123

3-3、可选项绑定(Optional Binding)

// 可以使用可选项绑定来判断可选项是否包含值
// 如果包含就自动解包,把值赋给一个临时的常量(let)或者变量(var),并返回true,否则返回false
if let number = Int("123") {
  print("字符串转换成功:\(number)")
  // number是强制解包之后的Int值
  // number作用域仅限于这个大括号
}else{
  print("字符串转换整数失败")
}// 字符串转换整数成功:123

enum Season : Int {
  case spring = 1, summer, autumn, winter
}
if let season = Season(rawValue: 6) {
  switch season {
    case.spring:
        print("the season is spring")
    default:
        print("the season is other")
  }
} else {
  print("no such season")
}// no such season

// 等价写法:
if let first  = Int("4") {
  if let second = Int("42"){
    if first < second && second < 100 {
      print("\(first) < \(second) < 100")
    }
  }
}// 4 < 42 < 100

if let first  = Int("4"),
        let second = Int("42"),
        first < second && second < 100 {
    print("\(first) < \(second) < 100")
}// 4 < 42 < 100

3-4、while循环中使用可选项绑定

// 遍历数组, 将遇到的正数都加起来, 如果遇到负数或者非数字, 停止遍历
var strs = ["10", "20", "abc", "-20", "30"]

var index = 0
var sum = 0
while let num = Int(strs[index]), num > 0 {
  sum += num
  index += 1
}
print(sum)//30

3-5、空合并运算符??(Nil-Coalescing Operator)

public func ?? <T>(optional:T?,defaultValue: @autoclosure() throws -> T?) rethrows -> T?
public func ?? <T>(optional:T?,defaultValue: @autoclosure() throws -> T) rethrows -> T

a ?? b //a是可选项,??左边必须放可选项; b是可选项或者不是可选项; b跟a的存储类型必须相同; 作用:如果a不为nil,就返回a,此时如果b不是可选项,返回a时会自动解包;如果a为nil,就返回b,

let a: Int? = 1
let b: Int? = 2
let c = a ?? b // c是Int? ,Optional(1)

let a: Int? = nil
let b: Int? = 2
let c = a ?? b // c是Int? ,Optional(2)

let a: Int? = nil
let b: Int? = nil
let c = a ?? b // c是Int? ,nil

let a: Int? = 1
let b: Int = 2
let c = a ?? b // c是Int ,1

let a: Int? = nil
let b: Int = 2
let c = a ?? b // c是Int ,2

let a: Int? = nil
let b: Int = 2
//如果不使用??运算符
let c : Int
if let tmp = a {
  c = tmp
}else {
  c = b
}

// 多个 ?? 一起使用
let a: Int? = 1
let b: Int? = 2
let c = a ?? b ?? 3 // c是Int , 1 , c是什么类型要看最后一个运算符是什么类型.

let a: Int? = nil
let b: Int? = 2
let c = a ?? b ?? 3 // c是Int , 2

let a: Int? = nil
let b: Int? = nil
let c = a ?? b ?? 3 // c是Int , 3

??跟if let配合使用
let a: Int? = nil
let b: Int? = 2
if let c = a ?? b {
  print(c)
}//类似于 if a != nil || b != nil

if let c = a, let d = b{
  print(c)
  print(d)
}//类似于if a != nil && b != nil

3-6、if语句实现登录

func login(_ info:[String: String]) {
  let username: String
  if let tmp = info["username"] {
    username = tmp
  } else {
    print("请输入用户名")
    return
  }
  let password: String
  if let tmp = info["password"] {
      password = tmp
  } else {
    print("请输入密码")
    return
  }
  // if username ....
  // if password ....
  print("用户名:\(username)","密码:\(password)","登录ing")
}

login(["username" : "jack","password":"123456"])//用户名:jack 密码:123456 登录ing
login(["password":"123456"])// 请输入用户名
login(["username" : "jack"])// 请输入密码

3-7、字典和数组的元素的返回值类型

var dict = ["age":10]
var age = dict["abc"]// Int?  字典返回可选类型

var array = [1,2,3]
var num = array[-1]// Int 数组直接返回值, 需要自己检查数组越界

3-8、guard语句

guard 条件 else {
    //do something...
  退出当前作用域
  //return、break、continue、throw、error
}

//当guard语句的条件为false时, 就会执行大括号里的代码;
//当guard语句的条件为true时, 就会跳过guard语句
//guard语句特别适合用来"提前退出"
//当使用guard语句进行可选项绑定时,绑定的常量(let)、变量(var)也能在外层作用域中使用

//用guard简写3-7的登录逻辑:
func login(_ info:[String: String]){
    guard let username = info["username"] else{
        print("请输入用户名")
        return
    }
    guard let password = info["password"] else {
        print("请输入密码")
        return
    }
    //if username ....
    //if password ....
    print("用户名:\(username)","密码:\(password)","登录ing")
}
login(["username" : "jack","password":"123456"])//用户名:jack 密码:123456 登录ing
login(["password":"123456"])// 请输入用户名
login(["username" : "jack"])// 请输入密码

3-9、隐式解包(Implicitly Unwrapped Optional)

// 在某些情况下, 可选项一旦被设定值之后, 就会一直拥有值
// 在这种情况下, 可以去掉检查, 也不必每次访问的时候都进行解包, 因为它能够确定每次访问的时候都有值
// 可以在类型后面加个感叹号 ! 定义一个隐式解包的可选项
let num1: Int! = 10 // 隐式解包的可选项(自动解包), 等价于:let num2: Int = num1!
let num2: Int = num1
if num1 != nil {
    print(num1 + 6)// 16
}
if let num3 = num1 {
    print(num3)// 10
}

/// 如果可选项是空值那么会报错
let num1: Int! = nil
let num2: Int = num1// Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file MyPlayground.playground, line 

3-10、字符串插值

// 字符串插值
// 可选项在字符串插值或者直接打印时, 编译器会发出警告
var age: Int? = 10
print("My age is \(age)")//My age is Optional(10)

// 至少有3种方法消除警告
print("My age is \(age!)")//My age is 10
print("My age is \(String(describing: age))")//My age is Optional(10)
print("My age is \(age ?? 0)")// My age is 10

3-11、多重可选项

var num1: Int? = 10 //包装了一个Int类型的可选类型盒子
var num2: Int?? = num1 // 包装了一个可选类型的可选类型盒子
var num3: Int?? = 10
print(num2 == num3) // true

var aum1: Int? = nil
var aum2: Int?? = aum1
var aum3: Int?? = nil
print(aum2 == aum3) // false

(aum2 ?? 1) ?? 2 // 2
(aum3 ?? 1) ?? 2 // 1

/*
可以使用lldb指令: help frame 查看frame有哪些指令
查看内存布局:
frame variable -R 或者 fr v -R 查看内存布局的区别
比如:fr v -R aum1
*/ 

3-12、思考下面枚举变量的内存布局

enum TestEnum {
    case test1, test2, test3
}
// 1个字节存储成员值
// N个字节存储关联值(N取占用内存最大的关联值), 任何一个case的关联值都共用这N个字节
// 共用体

// 有时候会将枚举的成员值跟其他类型的值关联存储在一起, 会非常有用

//枚举总共能存放256个字节 0x00~0xFF

var t = TestEnum.test1 //0
t = .test2 //1
t = .test3 //2

print(MemoryLayout<TestEnum>.size)
print(MemoryLayout<TestEnum>.stride)
print(MemoryLayout<TestEnum>.alignment)

// 查看变量内存存储情况
// 打断点: Debug >> Debug Workflow >>View Memory  或者右键选择 View Memory of "xxx"

第四章 汇编分析枚举的内存布局

1-1、程序的本质

软件/程序的执行过程:
    (硬盘)程序/软件-->(装载)内存--->(读写)--->CPU-->(控制)计算机

CPU中分为:
    寄存器 --> 用来做:信息存储    
    运算器 --> 用来做:信息处理
    控制器 

通常, CPU会先将内存中的数据存储到寄存器中, 然后再对寄存器中的数据进行运算

假设内存中有块红色内存空间的值是3,现在想把它的值加1, 并将结果存储到蓝色内存空间
    1.CPU首先会将红色内存空间的值放到rax寄存器中: movq 红色内存空间, %rax  //movq是将左边的值送给到右边
    2.然后让rax寄存器与1相加: addq $0x1, %rax
    3.最后将值赋值给内存空间: movq %rax, 蓝色内存空间

CPU规定:
    1.不允许内存间直接交互数据,必须要通过CPU的寄存器 
    2.想做运算或计算也要拉倒CPU的寄存器中进行运算, 运算结束后再送回内存

1-2、汇编语言的发展

机器语言
    由0-1组成
汇编语言(Assembly Language)
    用符号代替0和1, 比机器语言便于阅读和记忆
高级语言
    C/C++/JavaScript/Python等, 更接近人类自然语言

操作: 将寄存器BX的内容送入寄存器AX
    机器语言: 1000100111011000
    汇编语言: movm %bx, %ax
    高级语言: ax = bx; //这里只是举例,高级语言不能直接操作寄存器.

高级语言-->(编译)汇编语言<-->(编译/反编译)机器语言-->(运行)计算机

汇编语言与机器语言一一对应, 每一条机器指令都有与之对应的汇编指令;
汇编语言可以通过编译得到机器语言, 机器语言可以通过反汇编得到汇编语言;
高级语言可以通过编译得到汇编语言/机器语言, 但汇编语言/机器语言几乎不可能

1-3、汇编语言的种类

汇编语言严重依赖于电脑硬件设备, 相当于CPU的架构不同用的汇编就不同, 种类有:
    8086汇编(16bit) //2个字节
    x86汇编(32bit) //4个字节
    x64汇编(64bit) //8个字节
    ARM汇编(嵌入式、移动设备)
    ...

x86、x64汇编根据编译器的不同, 有2种书写格式:
    Intel: Windows派系
    AT&T:   Unix派系

作为iOS开发工程师, 最主要的汇编语言是
    AT&T汇编 --> iOS模拟器
    ARM汇编   --> iOS真机设备

1-4、常见的汇编指令

项目 AT&T Intel 说明
寄存器命名 %rax rax
操作数顺序 movq %rax, %rdx mov rdx, rax 将rax的值赋值给rdx
常数/立即数 movq 3 %rax movq0x10, %rax mov rax 3 mov rax, 0x10 将3赋值给rax 将0x10赋值给rax
内存赋值 movq $0xa, 0x1ff7(%rip) mov qword ptr [rip+0x1ff7], 0xa 将0xa赋值给地址为rip + 0x1ff7的内存空间
取内存地址 leaq -0x18(%rbp), %rax lea rax, [rbp - 0x18] 将rbp - 0x18这个地址赋值给rax
jmp指令 jmp *%rdx jmp 0x4001002 jmp *(%rax) jmp rdx jmp 0x4001002 jmp [rax] call和jmp写法类似,区别: jmp是跳转到某个地址去执行代码,一直到结束; 而call是跳到这个地址(call后面跟的一般是函数地址)去执行代码,然后配合ret(return)来配合使用
操作数长度 movl %eax, %edx movb $0x10, %al leaw 0x10(%dx), %ax mov edx, eax mov al, 0x10 lea ax, [dx + 0x10] b = byte(8-bit) s = short(16-bit integer or 32-bit floating point) w = word(16-bit) i = long(32-bit integer or 64-bit floating point) q = quad(64 bit) t = ten bytes(80-bit flating point)

备注: rip储存的是指令地址: 即CPU要执行的下一条指令地址就存储在rip中. rip += 正在执行指令的长度

1-5、寄存器

有16个常用的寄存器
    %rax、%rbx、%rcx、%rdx、%rsi、%rbp、%rsp
    %r8、%r9、%r10、%r11、%r12、%r13、%r14、%r15

寄存器的具体用途
    %rax、rdx常用作为函数返回值使用
    %rdi、%rsi、%rdx、%rcx、%r8、%r9等寄存器常用于存放函数参数
    %rsp、%rbp用于栈操作
    rip作为指令指针
        存储着CPU下一条要执行的指令的地址
        一旦CPU读取一条指令,rip会自动指向下一条指令(存储下一套指令的地址)

r开头: 64bit的寄存器, 只有8个字节
e开头: 32bit的寄存器, 占4个字节
ax,bx,cx : 16bit的寄存器, 占2个字节
ah al bh bl: 占1个字节

问: 64位的结构体怎么存储大于8个字节的数据呢,比如结构体对象?
答: 对象存在内存中,结构体不能塞到寄存器里面去的,它就是放在内存中

1-6、lldb常用指令

读取寄存器的值:
    register read/格式
        register read/x

修改寄存器的值:
    register write 寄存器名称 数值
        register write  rax 0

读取内存中的值:
    x/数量-格式-字节大小 内存地址
        x/3xw 0x0000010

修改内存中的值
    memory write 内存地址 数值
        memory write 0x0000010 10

格式
    x是16进制, f是浮点型, d是十进制

字节大小
    b - byte 1字节
    h - half word 2字节
    w - word 4字节
    g - giant word 8字节

express表达式
    可以简写: expr表达式
    expression $rax
    expression $rax = 1

po 表达式
point 表达式
    po/x(格式) $rax

thread step-over、next、n
    单步运行, 把子函数当做整体一步执行(源码级别)
thread step-in、step、s
    单步运行, 遇到子函数会进入子函数(源码级别)
thread step-inst-over、next、ni
    单步运行,把子函数当做整体一步执行(汇编级别)
thread step-inst、stepi、si
    单步运行,遇到子函数会进入子函数(汇编级别)
thread step-out、finish
    直接执行完当前函数所有代码, 返回到上一个函数(遇到断点会卡住)

补充知识点:

指令的内存地址 第几个字节           汇编指令
 0x10000d40    <+1>:    movq %rsp, %rbp
 
imm立即数, 也就是字面量

第五章 汇编分析结构体、类的内存布局

1-1、结构体

//在Swift标准库中, 绝大多数的公开类型都是结构体, 而枚举和类只占很小一部分
    //比如Bool、Int、Double、String、Array、Dictionary等常见类型都是结构体.
①struct Date {
②    var year: Int = 0
③  var month: Int
④  var day: Int
⑤}
⑥var date = Date(year: 2019, month: 6, day: 23)
//所有的结构体都有一个编译器自动生成的初始化器(Initializer, 初始化方法、构造器、构造方法)
//在第⑥行调用的, 可以传入所有成员值,用以初始化所有成员(存储属性, Stored Property)

1-2、结构体的初始化器

//编译器会根据情况, 可能会为结构体生成多个初始化器(比如给定成员初始值), 宗旨是: 保证所有成员都有初始值.
struct Point {
  var x: Int?
  var y: Int?
}
var p1 = Point(x:10, y:10)
var p2 = Point(y:10)
var p3 = Point(x:10)
var p4 = Point()
//可选项都有个默认值nil, 因此上面代码可以编译通过!!

1-3、自定义初始化器

//一旦在自定义结构体时定义了初始化器, 编译器就不会再帮它自动生成其他初始化器.
struct Point {
    var x: Int = 0
  var y: Int = 0
  init(x: Int, y: Int) {
    self.x = x
    self.y = y
  }
}
var p1 = Point(x: 10, y: 10)

1-4、窥探初始化器的本质

// 以下2端代码完全等效
struct Point {
    var x: Int = 0
  var y: Int = 0
}
var p = Point()

struct Point{
  var x: Int
  var y: Int
  init() {
    x = 0
    y = 0
  }
}
var p = Point()

2-1、类

//类的定义和结构体类似, 但编译器并没有为类自动生成可以传入成员值的初始化器
class Point {
  var x: Int = 0
  var y: Int = 0
}
let p1 = Point()
//如果类的所有成员都在定义的时候指定了初始值, 编译器会为类生成无参的初始化器
//成员的初始化是在这个初始化器中完成的

2-2、结构体与类的本质区别

//结构体是值类型(枚举也是值类型), 类是引用类型(指针类型)
class Size {
  var width = 1
  var height = 2
}
struct Point {
  var x = 3
  var y = 4
}
func test() {
  var size = Size()
  print("size变量的地址",Mems.ptr(ofVal: &size))
  print("size变量的内存",Mems.memStr(ofVal: &size))
  
  print("size所指向内存的地址",Mems.ptr(ofRef: size))
  print("size所指向内存的内容",Mems.memStr(ofRef: size))
  
  var point = Point() 
  print("point变量的地址",Mems.ptr(ofVal: &point))
  print("point变量的内存",Mems.memStr(ofVal: &point))
}
// 值类型的特点: 如果值类型是在函数里面创建的,那么它的内存肯定在栈空间里面
// 指针变量在64bit栈空间内存中占几个字节? 8个字节,存放的是Size对象的内存地址.Size对象在堆空间. Size对象在堆空间占用32个字节内存,分别是引用计数(8个字节)、指针类型信息(8个字节).
// 上面都是针对64bit环境
// 汇编中有alloc、malloc相关的信息, 说明系统分配在堆空间.

2-3、对象的堆空间申请过程

//在Swift中, 创建类的实例对象, 要向堆空间申请内存, 大概流程如下:
    Class._allocating_init()
    libswiftCore.dylib:_swift_allocObject_
    libswiftCore.dylib:swift_slowAlloc
    libsystem_malloc.dylib:malloc

//在Mac、iOS中malloc函数分配的内存大小总是16的倍数
var ptr = malloc(1) //申请1个字节的堆空间
print(malloc_size(ptr))// 16, 所指向的堆空间是16个字节
var ptr = malloc(17) //申请17个字节的堆空间
print(malloc_size(ptr))// 32, 分配给你的堆空间是32个字节

class Size {
  var width = 1
  var height = 2
}
var size = Size()
print(malloc_size(Mems.ptr(ofRef: size)))//size这个指针变量,它指向堆空间是32个字节

//通过class_getInstanceSize可以得知: 类的对象至少需要占用多少内存
class Point {
  var x = 11//8
  var text = true//1
  var y = 22//8
}//实际利用的字节为33个
var p = Point() // 要经过堆空间malloc检查, 是否为16的倍数
class_getInstanceSize(type(of:p))//40 内存对齐后,对象至少要占多少内存
class_getInstanceSize(Point.self)//40 对象占用多少内存,内存对齐是8
print(Mems.size(ofRef:p))//48 堆空间真实分配内存空间的大小,堆空间分配空间的时候,内存对齐是16

问:结构体都存在栈里?
答: 结构体内存在哪里取决于你在哪里定义的. 如果你的结构体变量是在函数里面定义的,那么它的内存肯定就在栈空间; 如果你的结构体变量在外部定义的,它的内存就在数据段,也就是全局区,因为它是个全局变量; 假如结构体是在对象里面,那么这个结构体肯定跟随对象在堆空间. 同理枚举.

2-4、值类型

值类型赋值给var、let或者给函数传参, 是直接将所有内容拷贝一份.
    类似于对文件进行copy、paste操作,产生了全新的文件副本.属于深拷贝(deep copy)
struct Point{
    var x: Int
  var y: Int
}
func test(){
  var p1 = Point(x: 10, y: 20)
  var p2 = p1
  p2.x = 11
  p2.y = 22
  //请问p1.x和p1.y是多少? 答: 10 20
}

/*规律: 
    内存地址格式为: 0x4bdc(%rip), 一般是全局变量, 全局区(数据段)
    内存地址格式为: -0x78(%rbp), 一般是局部变量, 栈空间
    内存地址格式为: 0x10(%rax), 一般是堆空间
*/

//值类型赋值操作
var s1 = "Jack"
var s2 = s1
s2.append("_Rose")
print(s1)// Jack
print(s2)// Jack_Rose

/* 
在Swift标注库中, 为了提升性能, String、Array、Dictionary、Set采用了Copy On Write的技术(也就是说当我们修改内存的时候,它才会进行深度拷贝操作,否则是浅拷贝).
        比如仅当有"写"操作时, 才会真正执行拷贝操作
        对于标准库值类型的赋值操作, Swift能确保最佳性能, 所以没必要为了保证最佳性能来避免赋值.
*/

2-5、引用类型

/*
引用赋值给var、let或者给函数传参, 是将内存地址拷贝一份.
        类似于制作一个文件的替身(快捷方式、链接),指向的是同一个文件.属于浅拷贝(shallow copy)
*/
class Size {
    var width: Int
    var height: Int
    init(width:Int, height:Int) {
      self.width = width
      self.height = height
    }
}
func test() {
    var s1 = Size(width: 10, height: 20)
    var s2 = s1
    s2.width = 11
    s2.height = 22
    //请问s1.width和s2.height是多少? 答:11 22
}

第六、七章 汇编分析闭包本质

1-1、嵌套类型

struct Poker {
  enum Suit : Character {
    case spades = "♠", hearts = "♥", diamonds="♦", clubs = "♣"
  }
  enum Rank : Int {
    case two = 2, three, four, five, six, seven, eight, nine, ten
    case jack, queen, king, ace
  }
}

print(Poker.Suit.hearts.rawValue)

var suit = Poker.Suit.spades
suit = .diamonds

var rank = Poker.Rank.five
rank = .king

1-2、枚举、结构体、类都可以定义方法

一般把定义在枚举、结构体、类内部的函数, 叫做方法.
struct Point {
    var x = 10
  var y = 10
  func show(){//方法是不占用实例对象内存的, 方法的本质就是函数, 函数、方法都存放在代码段
    var a = 10//a局部变量
    print("局部变量(栈空间)",Mems.ptr(ofVal: &a))
    print("x=\(x),y=\(y)")
  }
}
let p = Point()//Point()在堆空间; p全局变量,在数据段的地方;
p.show() // x=10, y=10

print("全局变量",Mems.ptr(ofVal:&p))
print("堆空间",Mems.ptr(ofVal:p))

2-1、闭包表达式( Closure Expression)

/// 在Swift中, 可以通过func定义一个函数, 也可以通过闭包表达式定义一个函数
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2}
// 简写一:
var fn = {
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
}
fn(10, 20)
// 简写二:
{
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
}(10, 20)

/*闭包的格式写法:
{
  (参数列表) -> 返回值类型 in
  函数体代码
}
*/

2-2、闭包表达式的简写

func exec(v1: Int, v2: Int, fn:(Int, Int) -> Int) {
    print(fn(v1, v2))
}
// 简写一:
exec(v1: 10, v2: 20, fn:{
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
})
// 简写二: (省略参数类型)
exec(v1: 10, v2: 20, fn: {
    v1, v2 in return v1 + v2
})
// 简写三: 
exec(v1: 10, v2: 20, fn: {
    v1, v2 in v1 + v2
})
// 简写四:
exec(v1: 10, v2: 20, fn: {  $0 + $1 })
// 简写五:
exec(v1: 10, v2: 20, fn: + )

2-3、尾随闭包

/*如果将一个很长的闭包表达式作为函数的最后一个实参, 使用尾随闭包可以增加函数的可读性
    尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式
*/
func exec(v1: Int, v2: Int, fn:(Int, Int) -> Int) {
    print(fn(v1, v2))
}
//尾随闭包写法:
exec(v1: 10, v2: 20){
  $0 + $1
}

/*如果闭包表达式是函数的唯一实参, 而且使用了尾随闭包的语法, 那就不需要在函数名后边写圆括号
*/
func exec(fn:(Int, Int) -> Int) {
    print(fn(v1, v2))
}
exec(fn: { $0 + $1 })// 普通调用
exec() { $0 + $1 }// 尾随闭包写法
exec { $0 + $1 }// 尾随闭包简写

2-4、示例 - 数组的排序

func testSort(){
  var arr = [10, 1, 4, 20, 99]
  arr.sort()
  //arr.sort(by:(Int, Int) throws -> Bool)
  print(arr)
}

/*解析:
    func sort(by areInIncreasingOrder: (Element, Element) -> Bool)
*/
/// 返回true: i1排在i2前面
/// 发挥false: i1排在i2后面
func cmp(i1: Int, i2: Int) -> Bool {
  // 大的排在前面
  return i1 > i2
}
var nums = [11, 2, 18, 6]
nums.sort(by: cmp)// [18, 11, 6, 2]
// 简写一:
nums.sort(by: {
    (i1: Int, i2: Int) -> Bool in
    return i1 < i2
})
// 简写二: 
nums.sort(by: {i1, i2 in return i1 < i2})
// 简写三: 
nums.sort(by: {i1, i2 in i1 < i2})
// 简写四: 
nums.sort(by: { $0 < $1 })
// 简写五: 
nums.sort(by: <)
// 简写六: 
nums.sort(){ $0 < $1 }
// 简写七: 
nums.sort { $0 < $1 }

3-1、闭包(Closure)

/*网上有各种关于闭包的定义, 个人觉得比较严谨的定义是:
        一个函数和它所捕获的变量/常量环境组合起来, 称为闭包
    一般指的是定义在函数内部的函数;
    一般它捕获的是外层函数的局部变量/常量.
*/
typealias Fn = (Int) -> Int //定义类型
/// 闭包写法一: (通过func)
func getFn() -> Fn{
    var num = 0
    func plus(_ i: Int) -> Int {
        num += i
        return num
    }
    return plus
}// 返回的plus和num形成了闭包
/// 闭包写法二: (通过闭包表达式)
func getFn() -> Fn{
  var num = 0
  return {
    num += $0
    return num
  }
}
var fn1 = getFn()
var fn2 = getFn()
print(fn1(1)) // 1
print(fn2(2)) // 2
print(fn1(3)) // 4
print(fn2(4)) // 6

/*可以把闭包想象成一个类的实例对象
        内存在堆空间
        捕获的局部变量/常量就是对象的成员(存储属性)
        组成闭包的函数就是类内部定义的方法
*/
class Closure {
    var num = 0
  func plus(_ i: Int) -> Int {
    num += i
    return num
  }
}
var cs1 = Closure()
var cs2 = Closure()
cs1.plus(1)//1
cs2.plus(2)//2
cs1.plus(3)//4
cs2.plus(4)//6

3-2、练习

typealias Fn = (Int) -> (Int, Int)
func getFns() -> (Fn, Fn) {
  var num1 = 0
  var num2 = 0
  func plus (_ i: Int) -> (Int, Int) {
    num1 += i
    num2 += i << 1 // <<1 等于 *2
    return (num1, num2)
  }
  func minus(_ i: Int) -> (Int, Int) {
    num1 -= i
    num2 -= i << 1
    return (num1, num2)
  }
  return (plus, minus)
}
let (p, m) = getFns()
p(5) //(5,10)
m(4) //(1,2)
p(3) //(4,8)
m(2) //(2,4)

3-3、注意

//如果返回值是函数类型, 那么参数的修饰要保持统一
func add(_ num: Int) -> (inout Int) -> Void {
  func plus(v: inout Int) {//inout(输入输出参数), 与上面要保持统一
    v += num
  }
  return plus
}
var num = 5
add(20)(&num)
print(num)

3-4、自动闭包

// 如果第1个数大于0, 返回第一个数, 否则返回第2个数
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
    return v1 > 0 ? v1 : v2
}
getFirstPositive(10, 20) //10
getFirstPositive(-2, 20) //20
getFirstPositive(0, -4) //-4

// 改成函数类型的参数, 可以让v2延迟加载
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int? {
    return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4) { 20 }

func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? {
    return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, 20)
getFirstPositive(-4, {30})
// @autoclosure 会自动将20封装成闭包{ 20 }
// @autoclosure 只支持() -> T 格式的参数
// @autoclosure 并非只支持最后一个参数
// 空合并运算符 ?? 使用了 @autoclosure 技术
// 有@autoclosure、无@autoclosure, 构成了函数重载
// 为了避免与期望冲突, 使用了@autoclosure的地方最好明确注释清楚, 这个值会被推迟执行

第八章 属性、汇编分析inout本质

1-1、属性

/*Swift中跟实例相关的属性可以分为2大类
    存储属性( Stored Property)
            >类似于成员变量这个概念
            >存储在实例的内存中
            >结构体、类可以定义存储属性
            >枚举不可以定义存储属性
    计算属性( Computed Property)
            >本质就是方法(函数)
            >不占用实例的内存
            >枚举、结构体、类都可以定义计算属性
*/
struct Circle {
    //存储属性
    var radius: Double
    //计算属性
    var diameter: Double {
        set {
          radius = newValue / 2
        }
        get {
          radius * 2
        }
    }
}
var c = Circle(radius: 10)
c.radius = 11
c.diameter = 40
print(c.radius)// 20.0
print(MemoryLayout<Circle>.stride)// 8

1-2、存储属性

/*关于存储属性, Swift有个明确的规定:
    在创建类或结构体的实例时, 必须为所有的存储属性设置一个合适的初始值
        >可以在初始化器里面为属性设置一个初始值
        >可以分配一个默认的属性值作为属性定义的一部分
*/

1-3、计算属性

/// set传入的新值默认叫做newValue, 也可以自定义
struct Circle {
    var radius: Double
  var diameter: Double {
    set(newDiameter){
      radius = newDiameter / 2
    }
    get {
      radius * 2
    }
  }
}

/// 只读计算属性: 只有get, 没有set
struct Circle {
    var radius: Double
  var diameter: Double {
    get {
      radius * 2
    }
  }
}
//简写:
struct Circle {
    var radius: Double
  var diameter: Double { radius * 2 }
}

/* 定义计算属性只能用var, 不能用let. 
            >let代表常量.值是一成不变的
            >计算属性的值是可能发生变化的(即使是只读计算属性)
*/

1-4、枚举rawValue原理

/*枚举原始值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

1-5、延迟存储属性( Lazy Stored Property)

/*使用lazy可以定义一个延迟存储属性, 在第一次用到属性的时候才会进行初始化*/
class Car {
  init(){
    print("car init!")
  }
  func run(){
    print("car is running!")
  }
}

class Person{
  lazy var car = Car()
  init(){
    print("Persion init!")
  }
  func goOut(){
    car.run()
  }
}
let p = Persion()
print("---------")
p.goOut()


class PhotoView {
  lazy var image: Image = {
    let url = "https://www.520it.com/xx.png"
    let data = Data(url: url)
    return Image(data: data)
  }()//闭包
}

/*
lazy属性必须是var, 不能是let
        let必须在实例的初始化方法完成之前就拥有值
如果多条线程同时第一次访问lazy属性
        无法保证属性只被初始化1次
*/
/*延迟存储属性注意点:
当结构体包含一个延迟属性时, 只有var才能访问延迟存储属性
        因为延迟属性初始化时需要改变结构体的内存
*/
struct Point {
    var x = 0
  var y = 0
  lazy var z = 0
}
let p = Point()//此时必须用var修饰
print(p.z)// 报错

1-6、属性观察器(Property Observer)

/*可以为非lazy的var存储属性设置属性观察器*/
struct Circle {
    var radius: Double {//存储属性
    willSet {
      print("willSet", newValue)
    }
    didSet {
      print("didSet", oldValue, radius)
    }
  }
  init(){
    self.radius = 1.0
    print("Circle init!")
  }
}
var circle = Circle() // Circle init
circle.radius = 10.5 //willSet 10.5  \n didSet 1.0 10.5
print(circle.radius)// 10.5
/*
  willSet会传递新增, 默认叫newValue
  didSet会传递旧值, 默认叫oldValue
  在初始化器中设置属性值不会触发willSet和didSet
        >在属性定义的时设置初始值也不会触发willSet和didSet
*/

1-7、全局变量、局部变量

/*属性观察器、计算属性的功能, 同样也可以应用在全局变量、局部变量身上*/
var num: Int {
    get {
    return 10
  }
  set {
    print("setNum",newValue)
  }
}
num = 11 // setNum 11
print(num)// 10


func test() {
  var age = 10 {
    willSet {
      print("willSet", newValue)
    }
    didSet {
      Print("didSet", oldValue, age)
    }
  }
  age = 11
  // willSet 11
  // didSet 10 11
}
test()

2-1、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(_ num: inout Int) { //接收一个地址值,输入输出参数inout本质是引用传递
  print("test")
  num = 20
}

var s = Shape(width: 10, side: 4)
test(&s.width)
s.show()
print("-----------")
test(&s.side)
s.show()
print("-----------")
test(&s.girth)
s.show()
print("-----------")
/*inout的本质总结:
如果实参有物理内存地址, 且没有设置属性观察器
        直接将实参的内存地址传入函数(实参进行引用传递)
如果实参是计算属性 或者 设置了属性的观察者
        采用了Copy In Copy Out的做法:
                调用该函数时, 先复制实参的值, 产生副本[get]
                将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
                函数返回后, 再将副本的值覆盖实参的值[set]
总结: inout的本质就是引用传递(地址传递)
*/

3-1、类型属性(Type Property)

/*严格来说, 属性可以分为:

    实例属性(Instance Property): 只能通过实例去访问
            >存储实例属性(Stored Instance Property): 存储在实例内存中, 每个实例都有1份
            >计算实例属性(Computed Instance Property)

    类型属性(Type Property):只能通过类型去访问
            >存储类型属性(Stored Type Property): 这个程序运行过程中, 就只有1份内存(类似于全局变量)
            >计算类型属性(Computed Type Property)

可以通过static定义类型属性
    如果是类, 可以通过关键字class. 不过关键字class不能修饰类中的存储属性
*/
class Shape {
    var width: Int = 0 //实例属性
        static var count: Int = 0 // static关键字声明类型属性
}
var s = Shape()
s.count = 10//报错
Shape.count = 10

struct Car {
  static var count: Int = 0
  init(){
    Car.count += 1
  }
}
let c1 = Car()
let c2 = Car()
let c3 = Car()
print(Car.count)//3

3-2、类型属性细节

/*不同存储实例属性, 你必须给存储类型属性设定初始值
        >因为类型没有像实例那样的init初始化器来初始化存储属性
存储类型属性默认就是lazy, 会在第一次使用的时候才初始化
        >就算被多个线程访问, 保证只会初始化一次
        >存储类型属性可以是let

枚举类型也可以定义类型属性(存储类型属性、计算类型属性)
*/
enum Shape {
    static var width: Int = 0 //可以定义类型属性, 不可以定义实例存储属性
  case s1, s2, s3, s4
}
var s = Shap.s1

/*单例模式*/
public class FileManager {
  public static let shared = FileManager()
  private init(){ 
   
  }
  func open() {
    
    }
    func close() {
      
    } 
}
FileManager.shared.open()
FileManager.shared.close()

第九章 汇编分析类型属性、方法、下标、继承

1-1、方法(Method)

/*枚举、结构体、类都可以定义实例方法、类型方法
        实例方法(Instance Method ) : 通过实例调用
        类型方法(Type Method): 通过类型调用, 用static或者class关键字定义
*/
class Car {
  static var count = 0
  init() {
    Car.count += 1
  }
  static func getCount() -> Int { count }
}
let c0 = Car()
let c1 = Car()
let c2 = Car()
print(Car.getCount()) //3
/*
self
        在实例方法中代表实例
        在类型方法中代表类型

在类型方法中 static func getCount中
        count等价于self.count、Car.self.count、Car.count
*/

1-2、mutating

/*结构体和枚举是值类型, 默认情况下, 值类型的属性不能被自身的实例方法修改
    在func关键字前面加mutating可以允许这种修改行为
*/
struct Point{
    var x = 0.0, y = 0.0
  mutating func moveBy(deltaX: Double, deltaY: Double) {
    x += deltaX
    y += deltaY
  }
}

enum StateSwitch {//状态开关
  case low, middle, high
  mutating func next() {
    switch self {
      case .low:
        self = .middle
      case .middle:
        self = .high
      case .high:
        self = .low
    }
  }
}

1-3、@discardableResult

// 在func前面加个@discardableResult, 可以消除: 函数调用返回值未被使用的警告⚠️
struct Point{
    var x = 0.0, y = 0.0
  @discardableRusult mutating func moveX(deltaX: Double) -> Double {
    x += deltaX
    return x
  }
}
var p = Point()
p.moveX(deltaX: 10)

@discardableResult func get() -> Int {
  return 10
}
get()

2-1、下标(subscribe)

/// 使用subscribe可以给任意类型(枚举、结构体、类)增加下标功能, 有些地方也翻译为: 下标脚本
// subscript的语法类似于实例方法、计算属性,本质就是方法(函数)
class Point{
  var x = 0.0, 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
    }
  }
}

var p = Point()
p[0] = 11.1 //下标set方法
p[1] = 22.2
print(p.x)//11.1
print(p.y)//22.2
print(p[0])//11.1  下标get方法
print(p[1])//22.2

/*subscript中定义的返回值类型决定了:
    get方法的返回值类型
    set方法中newValue的类型

subscript可以接受多个参数,并且类型任意
*/

2-2、下标的细节

/// subscript可以没有set方法, 但必须要有get方法
class Point {
  var x = 0.0, y = 0.0
  subscript(index: Int) -> Double {
    get {
      if index == 0 {
        return x
      } else if index == 1 {
        return y
      }
      return 0
    }
  }
}
/// 如果只有get方法, 可以省略get
class Point {
    var x = 0.0, y = 0.0
  subscript(index: Int) -> Double {
    if index == 0{
      return x
    } else if index == 1{
      return y
    }
    return 0
  }
}

/// 可以设置参数标签
class Point {
  var x = 0.0, y = 0.0
  subscript(index i: Int) -> Double {
    if i == 0 {
      return x
    } else if i == 1{
      return y
    }
    return 0
  }
}
var p = Point()
p.y = 22.2
print(p[index: 1]) // 22.2

/// 下标可以是类型方法
class  Sum {
  static subscript(v1: Int, v2: Int) -> Int {
    return v1 + v2
  }
}
print(Sum[10, 20]) //30

2-3、结构体、类作为返回值对比

class Point {
  var x = 0, y = 0
}
class PointManager {
  var point = Point()
  subscript(index: Int) -> Point {
    get { point }//只有get方法, 给x/y赋值时会报错
  }
}


struct Point {//结构体是值类型, 下标不写set方法会报错
  var x = 0, y = 0
}
class PointManager {
  var point = Point()
  subscript(index: Int) -> Point{
    set { point = newValue } 
    get { point }
  }
}

var pm = PointManager()
pm[0].x = 11//本质: pm[0] = Point(x: 11, y: pm[0].y)
pm[0].y = 22//本质: pm[0] = Point(x: pm[0].x, y: 22)
// Point(x: 11, y:22)
print(pm[0])
//Point(x:11, y:22)
print(pm.point)


class Point {//类是引用类型, 下标不写set方法不会报错
  var x = 0, y = 0
}
class PointManager {
  var point = Point()
  subscript(index: Int) -> Point {
    get { point }//只有get方法, 给x/y赋值时不会报错
  }
}

2-4、接收多个参数的下标

class Grid {
  var data = [
    [0,1,2],
    [3,4,5],
    [6,7,8]
  ]
  subscript(row: Int, column: Int) -> Int {
    set {
      guard row >= 0 && row < 3 && column >= 0 && column < 3 else{
        return
      }
      data[row][column] = newValue
    }
    get {
      guard row >= 0 && row < 3 && column >=0 && column < 3 else{
        return 0
      } 
      return data[row][column]
    }
  }
}

var grid = Grid()
grid[0 ,1] = 77
grid[1, 2] = 88
grid[2, 0] = 99
print(grid.data)

3-1、继承(Inheritance)

/// 值类型(枚举、结构体)不支持继承, 只有类支持继承

/// 没有父类的类, 称为基类
    //swift并没有像OC、Java那样的规定: 任何类最终都要继承自某个基类

/// 子类可以重写父类的下标、方法、属性, 重写必须加上override关键字

3-2、内存结构

class Animal {
  var age = 0
}
class Dog : Animal {
  var weight = 0
}
class ErHa : Dog {
  var iq = 0
}

let a = Animal() //堆空间的对象, 堆空间的内存是16的倍数. 
a.age = 10
// 32
print(Mems.size(ofRef: a))
/*
0x00000001000073e0 //这8个字节存放类型信息
0x0000000000000002 //这8个字节存放引用计数相关信息
0x000000000000000a //这8个字节存储age
0x0000000000000000
*/
print(Mems.meStr(ofRef: a))

let d = Dog()
d.age = 10
d.weight = 20
// 32
print(Mems.size(ofRef: d))
/*
0x0000000100007490 //这8个字节存放类型信息
0x0000000000000002 //这8个字节存放引用计数相关信息
0x000000000000000a //这8个字节存储age
0x0000000000000014 //这8个字节存放weight
*/
print(Mems.memStr(ofRef: d))

3-3、重写实例方法、下标

class Animal {
  func speak() {
    print("Animal speak")
  }
  subscript(index: Int) -> Int {
    return index
  }
}

var anim: Animal
anim = Animal()
// Animal speak
anim.speak()
// 6
print(anim[6])

class Cat : Animal {
  override func speak(){
    super.speak()
    print("Cat speak")
  }
  override subscript(index: Int) -> Int {
    return super[index] + 1
  }
}

anim = Cat()
// Animal speak
// Cat speak
anim.speak()
// 7
print(anim[6])

3-4、重写类型方法、下标

/*
被class修饰的类型方法、下标, 允许被子类重写
被static修饰的类型方法、下标, 不允许被子类重写
*/
class Animal {
  class func speak(){
    print("Animal speak")
  }
  class subscript(index: Int) -> Int{
    return index
  }
}

class Cat : Animal {
  override func speak(){
    super.speak()
    print("Cat speak")
  }
  override subscript(index: Int) -> Int {
    return super[index] + 1
  }
}

3-5、重写属性

/* 
子类可以将父类的属性(存储、计算)重写为计算属性
子类不可以将父类属性重写为存储属性
只能重写var属性, 不能重写let属性
重写时, 属性名、类型要一致
子类重写后的属性权限, 不能小于父类属性的权限
    如果父类属性是只读的, 那么子类重写的属性可以是只读的、也可以是可读写的
    如果父类属性是可读写的, 那么子类重写后的属性也必须是可读写的
*/
class Aimal {
  var age = 0 //存储属性
}
class Dog : Animal {
  override var age = 0 //报错, 不允许
  var weight = 0
} 

/// 重写实例属性
class Circle {
  var radius: Int = 0.0 //直径 存储属性
  var diameter: Int { //半径 计算属性
    set {
      print("Circle setDiameter")
      radius = newValue / 2
    }
    get {
      pirnt("Cricle getDiameter")
      return = radius * 2
    }
  }
}

var circle: Circle
circle = Circle()
circle.radius = 6
// Circle getDiameter
// 12
print(circle.diameter)
// Circle setDiameter
circle.diameter = 20
// 10
print(circle.radius)

class SunCircle : Circle {
  override var radius: Int {//从父类继承过来的存储属性都是有存储空间的
    set {
      print("SubCircle setRadius")
      super.radius = newValue > 0 ? newVaule : 0
    }
    get {
      print("SubCricle getRadius")
      return super.radius
    }
  }
  override var diameter: Int {
    set {
      print("SubCircle setDiameter")
      super.diameter = newVlue > 0 ? newValue : 0
    }
    get {
      print("SubCircle getDiameter")
      return super.diameter
    }
  }
}

var circle = SubCircle()

// SubCircle setRadius
circle.radius = 6

// SubCircle getDiameter
// Circle getDiameter
// SubCircle getRaius
// 12
print(circle.diameter)

// SubCircle setDiameter
// Circle setDiameter
// SubCircle setRadius
circle.diameter = 20

// SubCircle getRadius
// 10
print(circle.radius)

3-6、重写类型属性

/*被class修饰的计算类型属性, 可以被子类重写; 存储属性不可以.
被static修饰的类型属性(计算、存储),不可以被子类重写
*/
class Circle {
  static var radius: Int = 0
  class var diameter: Int {
    set {
      print("Circle setDiameter")
      radius = newValue / 2
    }
    get {
        print("Circle getDiameter") 
      return radius * 2
    }
  }
}

class SubCircle : Circle {
  override static var diameter: Int {
    set {
      print("SubCircle setDiameter")
      super.diameter = newValue > 0 ? newValue : 0
    }
    get {
      print("SubCircle getDiameter")
      return super.diameter
    }
  }
}

3-7、属性观察器

/// 可以在子类中为父类属性(除了只读计算属性、let属性)增加属性观察器
class Circle {
  var radius: Int = 1
}
class SubCircle: Circle {
  override var radius: Int {//此处不是重写计算属性, 只是添加属性观察器
    willSet {
      print("SubCircle willSetRadius", newValue)
    }
    didSet {
      print("SubCircle didSetRadius", oldValue, radius)//访问radius的存储属性
    }
  }
}
var circle = SubCircle()
// SubCircle willSetRadius 10
// SubCircle didSetRadius 1 10
circle.radius = 10


class Circle {
  var radius: Int = 1 {//存储属性
    willSet {
      print("Circle willSetRadius", newValue)
    }
    didSet {
      print("Circle didSetRadius", oldValue, radius)
    }
  }
}
class SubCircle: Circle {
  override var radius: Int {
    willSet {
      print("SubCircle willSetRadius", newValue)
    }
    didSet {
      print("SubCircle didSetRadius", oldValue, radius)
    }
  }
}
var circle = SubCircle()
// SubCricle willSetRadius 10
// Circle willSetRadius 10
// Circle didSetRadius 1 10
// SubCricle didSetRadius 1 10
circle.radius = 10


class Circle {
  var radius: Int {// 实例计算属性
    set {
      print("Circle setRadius", newValue)
    }
    get {
      print("Circle getRadius")
      return 20
    }
  }
}
class SubCircle: Circle {
  override var radius: Int {
    willSet {
      print("SubCircle willSetRadius", newValue)
    }
    didSet {
      print("SubCircle didSetRadius", oldValue, radius)
    }
  }
}


class Circle {
  class var radius: Int {// 类型计算属性
    set {
      print("Circle setRadius", newValue)
    }
    get {
      print("Circle getRadius")
      return 20
    }
  }
}
class SubCircle: Circle {
  override static var radius: Int {
    willSet {
      print("SubCircle willSetRadius", newValue)
    }
    didSet {
      print("SubCircle didSetRadius", oldValue, radius)
    }
  }
}
// Circle getRadius
// SubCircle willSetRadius 10
// Circle setRadius 10
// Circle getRadius
// SubCircle didSetRadius 20 20
SubCircle.radius = 10

第十章 汇编分析多态原理、初始化、可选链

1-1、多态的实现原理

/*多态的实现原理: 
1. OC: Runtime
2. C++: 虚表(虚函数表)

Swift中没有runtime, Swift的实现跟虚表有点像, Swift中多态的实现原理
父类指针指向子类对象
*/
class Animal {
  func speak() {
    print("Animal speak")
  }
  func eat() {
    print("Animal eat")
  }
  func sleep() {
    print("Animal sleep")
  }
}
class Dog : Animal {
  func speak() {
    print("Dog speak")
  }
  func eat() {
    print("Dog eat")
  }
  func run() {
    print("Dog run")
  }
}

var anim: Animal
anim = Animal()
animal.speak()
animal.eat()
animal.sleep()

anim = Dog()//父类指针指向子类对象
anim.speak()
anim.eat()
anim.sleep()
//anim.run()

1-2、多态的实现原理

/*Swift中多态的使用原理:
类似于C++的虚表,直接将这个对象将来要调用的函数内存地址,提前放到类型信息那里, 这些类型信息肯定是编译完就可以确定的调用谁,程序运行过程中就去那块内存中找就可以了
*/

2-1、初始化

/*
类、结构体、枚举都可以定义初始化器.
类有2种初始化器: 指定初始化器(designated Initializer)、便捷初始化器(convenience Initializer).

每个类至少有一个指定初始化器, 指定初始化器是类的主要初始化器.
默认初始化器总是类的指定初始化器.
类偏向于少量指定初始化器, 一个类通常只有一个指定初始化器.

初始化器的相互调用规则:
    指定初始化器必须从它的直系父类调用指定初始化器;
    便捷初始化器必须从相同的类里调用另一个初始化器;
    便捷初始化器最终必须调用一个指定初始化器.
*/

/// 指定初始化器
init(parameters) {
    statements
}

/// 便捷初始化器
convenience init(parameters) {
    statements
}

class Size {
  // init(){ } 默认初始化器
  var width: Int = 0
  var height: Int = 0
  init(width: Int, height: Int){// 指定初始化器(主要初始化器)
    self.width = width
    self.height = height
  }
  convenience init(width: Int, height: Int){// 便捷初始化器
    self.init()
    self.width = width
    self.height = height
  }
}
var s = Size(width: 10, height: 20)//调用初始化器

2-2、便捷初始化器最终必须调用一个指定初始化器

class Size {
  var width: Int
  var height: Int
  init(width: Int, height: Int) {// 指定初始化器(主要初始化器)
    self.width = width
    self.height = height
  }
  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 s1 = Size(width: 10, height: 20)
var s2 = Size(width: 10)
var s3 = Size(height: 10)
var s4 = Size()

2-3、指定初始化器必须从它的直系父类调用指定初始化器

class Person {
  var age: Int
  init(age: Int) {
    self.age = age
  }
  convenience init() {
    self.init(age: 0)
  }
}
class Student : Person {
  var score: Int
  init(age: Int, score: Int) {//自己的指定初始化器, 必须指定父类的指定初始化器
    // 第一阶段: 初始化所有存储属性
    self.score = score//必须放在调用父类指定初始化器之前
    super.init(age: age)
    // 第二阶段:实例个性化定制
    self.age = age
  }
  convenience init() {//自己的便捷初始化器, 必须指定自己的指定初始化器
    self.init(age: 0, score: 0)
  }
  convenience init(score: Int) {//自己的便捷初始化器, 必须指定自己的指定初始化器
    self.init(age: 0, score: score)
  }
}

2-4、两段式初始化

/*
Swift在编码安全方面是煞费苦心, 为了保证初始化过程的安全, 设定了两段式初始化、安全检查

两段式初始化:
    第1阶段: 初始化所有存储属性
        ① 外层调用指定/便捷初始化器
        ② 分配内存给实例, 但未初始化
        ③ 指定初始化器确保当前类定义的存储属性都初始化
        ④ 指定初始化器调用父类的初始化器, 不断向上调用, 形成初始化器链

    第2阶段: 设置新的存储属性值
        ① 从顶部初始化器往下, 链中的每一个指定初始化器都有机会进一步定制实例
        ② 初始化器现在能够使用self(访问、修改它属性, 调用它的实例方法等等)
        ③ 最终,链中任何便捷初始化器都有机会定制实例以及使用self

安全检查: 
    指定初始化器必须保证在调用父类初始化器之前, 其所在类定义的所有存储属性都要初始化完成;
    指定初始化器必须调用父类初始化器, 然后才能成为继承的属性设置新值;
    便捷初始化器必须先调用同类中的其它初始化器, 然后再为任意属性设置新值;
    初始化器在第1阶段初始化完成之前, 不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self;
    直到第1阶段结束, 实例才算完全合法.
*/

2-5、重写父类的指定初始化器

/// 当重写父类的指定初始化器时,必须加上override(即使子类的实现是便捷初始化器)
class Person {
  var age: Int
  init(age: Int) {
    self.age = age
  }
}
class Student : Person {
  var score: Int
  init(age: Int, score: Int) {
    self.score = score
    super.init(age: age)
    self.age = age
  }
 // override init(age: Int) { //重写为指定初始化器
 //   self.score = 0 //把自己的初始化完成,再调用父类指定初始化器
 //   self.init(age: age)
 // }
  override convenience init(age: Int) {//重写为便捷初始化器
    self.init(age: age, score:0)//调用自己的指定初始化器
    score = 15
  }
}

/// 如果子类写了一个匹配父类便捷初始化器的初始化器, 不用加上override
    // 因为父类的便捷初始化器永远不会通过子类直接调用, 因此, 严格来说, 子类无法重写父类的便捷初始化器
class Person {
  var age: Int
  init(age: Int) {
    self.age = age
  }
  convenience init() {
    self.init(age: 0)
  }
}
class Student : Person {
  var score: Int
  init(age: Int, score: Int) {
    self.score = score
    super.init(age: age)
    self.age = age
  }
    init() {//此处不算重写, 不用加override
    self.score = 0
    super.init(age: 0)
  }
  convenience init() {
    self.init(age: 0, score: 0)
  }
}

2-5、自动继承

/*
① 如果子类没有自定义任何指定初始化器, 它会自动继承父类所有的指定初始化器

② 如果子类提供了父类所有指定初始化器的实现(要么通过方式①继承,要么重写)
        子类自动继承所有的父类便捷初始化器

③ 就算子类添加了更多的便捷初始化器, 这些规则仍然适用

④ 子类以便捷初始化器的形式重写父类的指定初始化器, 也可以作为满足规则②的一部分
*/

class Person {
  var age: Int
  var name: String
  init(age: Int, name: String) {
    self.age = age
    self.name = name
  }
  init() {
    self.age = 0
    self.name = 0
  }
  convenience init(age: Int) {
    self.init(age: age, name: "")
  }
  convenience init(name: String) {
    self.init(age: 0, name: name)
  }
}

class Student : Person {
  var no: Int = 0
  convenience init(no: Int) {
    self.init()
  }
}
// 只要我们自定义了指定初始化器, 那么父类的指定初始化器就不能继承下来.

第十一章 init、deinit、可选链、协议、元类型

1-1、required

/*
        用required修饰指定初始化器, 表明其所有子类都必须实现该初始化器(通过继承或重写实现)
        如果子类重写required初始化器时也必须加上required, 不用加override
*/
class Preson {
  required init() { }
  init(age: Int)
}
class Student: Preson {
  required init() {
    super.init()
  }
}

1-2、属性观察器

/*
    父类的属性在它自己的初始化器中赋值不会触发属性观察器,但在子类的初始化器中赋值会触发属性观察器
*/
class Preson {
  var age: Int {
    willSet {
      print("willSet", newValue)
    }
    didSet {
      print("didSet", oldValue, age)
    }
  }
  init() {
    self.age = 0
  }
}

class Student: Person {
  override init() {
    super.init()
    self.age = 1
  }
}

// willSet 1
// didSet 0 1
var stu = Student()

1-3、可失败初始化器

// 类、结构体、枚举都可以使用init?定义可失败初始化器
class Person {
    var name: String
  init?(name: String){
    if name.isEmpty {
      return nil
    }
    self.name = name
  }
}

//之前接触过的可失败初始化器
var num = Int("123")
public init?(_description: String)// Int类型初始化源码

enum Answer : Int {
    vase wrong, right
}
var an = Answer(rawValue: 1) //枚举的初始化器

/*
    不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器和非可失败初始化器
    可以用init!定义隐式解包的可失败初始化器
    可失败初始化器可调用非可失败初始化器, 非可失败初始化器调用可失败初始化器需要进行解包
    如果初始化器调用一个可失败初始化器导致初始化失败, 那么整个初始化过程都失败, 并且之后的代码都停止执行
    可以用一个非可失败的初始化器重写一个可失败初始化器, 但反过来是不行的
*/
class Person {
    var name: String
  /*
  init!(name: String){//定义隐式解包的可失败初始化器
    if name.isEmpty {
      return nil
    }
    self.name = name
  }
  convenience init() {
    self.init(name: "")
  }
  */
  /*
  init?(name: String){
    if name.isEmpty {
      return nil
    }
    self.name = name
  }
  convenience init() {
    self.init(name: "")!//需强制解包
  }
  */
  init?(name: String){
    if name.isEmpty {
      return nil
    }
    self.name = name
  }
  convenience init?() {
    self.init(name: "")
    self.name = "Jack"
    // ....
  }
}
class Student : Person {
  override init(name: String){
    // ....
  }
}

var p1 = Person(name: "")
print(p1)
var p2 = Person(name: "Jack")
print(p2)

2-1、反初始化器(deinit)

/*
deinit叫做反初始化器, 类似于C++的析构函数、OC中的dealloc方法
    当类的实例对象被释放内存时,就会调用实例对象的deinit方法
*/
class Person {
  deinit {
    print("Preson对象销毁了")
  }
}
class Student : Person {
    deinit {
    print("Student对象销毁了")
  }
}
var stu: Student? = Student() //ARC
stu = nil
/*deinit不接受任何参数, 不能写小括号, 不能自行调用
    父类的deinit能被子类继承
    子类的deinit实现执行完毕后会调用父类的deinit
*/

3-1、可选链 ( Optional Chaining )

class Car { var price = 0 }
class Dog { var weight = 0 }
class Person {
  var name: String = ""
  var dog: Dog = Dog()
  var car: Car? = Car()
  func age() -> Int { 18 }
  func eat() { print("Person eat") }
  subscript(index: Int) -> Int { index }
}

var person: Person? = Person()
var age1 = person!.age() // Int
var age2 = person?.age() // Int?
var name = person?.name // String?
var index = person?[6] //Int?

func getName() -> String { "jack" }
// 如果person是nil, 不会调用getName()
person?.name = getName()

/*
如果可选项为nil, 调用方法、下标、属性失败, 结果为nil
如果可选项不为nil, 调用方法、下标、属性成功, 结果会被包装成可选项
    如果结果本来就是可选项, 不会进行再包装
*/
if let _ = person?.eat() {// ()?
  print("eat调用成功")
} else {
    print("eat调用失败")
}

var dog = person?.dog //Dog?
var weigth =  person?.dog.weight // Int?
var price = person?.car?.price //Int?
/*多个? 可以链接在一起
    如果链中任何一个节点是nil, 那么整个链就会调用失败
*/

3-2、可选链

var scores = ["Jack": [86, 82, 84], "Rose": [79, 94, 81]]
scorces["Jack"]?[0] = 100
scorces["Rose"]?[2] += 10
scorces["Kate"]?[0] = 88

var num1: Int? = 5
num1? = 10 // Optional(10)

var num2: Int? = nil
num2? = 10 // nil

var dict: [String : (Int, Int) -> Int] = [//字符串作为key,函数作为value
  "sum" : (+), // (+)这是语法糖,表示两个Int相加,返回Int类型
  "difference" : (-)// (-)这是语法糖,表示两个Int相减,返回Int类型
]
var result = dict["sum"]?(10, 20) // Optional(30), Int?

4-1、协议 ( Protocol )

/*协议可以用来定义方法、属性、下标的声明, 协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)*/
Protocol Drawable {
  func draw()
  var x: Int { get set }
  var y: Int { get }
  subscript(index: Int) -> Int { get set}
}

protocol Test1 { }
protocol Test2 { }
protocol Test3 { }
class TestClass : Test1, Test2, Test3 { }

/*
    协议中定义方法时不能有默认参数值
    默认情况下, 协议中定义的内容必须全部实现
        也有办法办到只实现部分内容, 后面会谈到
*/

4-2、协议中的属性

/*
    协议中定义属性必须用var关键字
    实现协议的属性权限要不小于协议中定义的属性权限
        协议定义get、set, 用var存储属性或get、set计算属性去实现
        协议定义get, 用任何属性都可以实现
*/
protocol Drawable {
  func draw()
  var x: Int { get set }
  var y: Int { get }
  subscript(index: Int) -> Int { get set }
}

class Person : Drawable{
  var x: Int = 0 // 通过存储属性实现协议
  let y: Int = 0
  func draw() {
    print("Person draw")
  }
  subscript(index: Int) -> Int {
    set { }
    get { index }
  }
}

class Person : Drawable{
  var x: Int {// 通过计算属性实现协议
    get { 0 }
    set { }
  }
  var y: Int { 0 }
  func draw() { print("Person draw") }
  subscript(index: Int) -> Int {
    set { }
    get { index }
  }
}

4-3、static、class

/*为了保证通用, 协议中必须用static定义类型方法、类型属性、类型下标*/
protocol Drawable {
  static func draw()
}

class Person1: Drawable {
  class func draw() {
    print("Person1 draw")
  }
}

class Person2: Drawable {
  static func draw() {
    print("Person2 draw")
  }
}

4-4、mutating

/*只有将协议中的实例方法标记为mutating
        才允许结构体、枚举的具体实现修改自身内存
        类在实现方法时不用加mutating, 枚举、结构体才需要加mutating
*/
protocol Drawable {
    mutating func draw()
}

class Size : Drawable {
  var width: Int = 0
  func draw() {
    width = 10
  }
}

struct Point : Drawable {
  var x: Int = 0
  mutating func draw() {
    x = 10
  }
}

4-5、init

/*协议中还可以定义初始化器init
    非final类实现时必须加上required
*/
protocol Drawable {
    init(x: Int, y: Int) //定义初始化器
}

class Point : Drawable {// 非final类. 将来可能有子类
  required init(x: Int, y: Int) { } //遵守协议实现方法, required表示它的所有子类都必须遵守这个协议
}

final class Size : Drawable {// final类, 规定将来没有子类
    init(x: Int, y: Int) { }
}

/*如果从协议实现的初始化器, 刚好是重写了父类的指定初始化器
    那么这个初始化必须同时加required、override
*/
protocol Livable {
    init(age: Int)
}

class Person {
  init (age: Int) { }
}

class Student : Person, Livable {
  required override init(age: Int) {
    super.init(age: age)
  }
} 

4-6、required

/*
    用required修饰指定初始化器, 表明其所有子类都必须实现该初始化器(通过继承或重写实现)
    如果子类重写了required初始化器, 也必须加上required, 不用加override
*/
class Person {
  required init() { }
  init (age: Int) { }
}

class Student : Person {
  required init() {
    super.init()
  }
} 

4-7、init、init?、init!

/*
协议中定义的init?、init!, 可以用init、init?、init!去实现
协议中定义的init, 可以用init、init!去实现
*/
protocol Livable {
    init()
  init?(age: Int)
  init!(no: Int)
}

class Person : Livable {
  required init() { }
  // reuqired init!() { }
  
  required init?(age: Int) { }
  // required init!(age: Int) { }
  // required init(age: Int) { }
  
  required init!(no: Int) { }
  // required init!(no: Int) { }
  // required init(no: Int) { }
}

4-8、协议的继承

/*一个协议可以继承其他协议*/
protocol Runnable {
    func run()
}

protocol Livable : Runnable {
  func breath()
}

class Person : Livable {
  func breath() { }
  func run() { }
}

4-9、协议组合

/*协议组合, 可以包含1个类类型(最多1个), 枚举和结构体则不能存在其中*/
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) { }

typealias RealPerson = Person & Livable & Runnable
// 接收同时遵守Livable、Runnable协议、并且是Person或者其子类的实例
func fn4(obj: RealPerson) { }

4-10、CaseIterable

/*让枚举遵守CaseIterable协议, 可以实现遍历枚举值*/
enum Season : CaseIterable {
  case spring, summer, autumn, winter
} 
let seasons = Season.allCases
print(seasons.count) // 4
for season in seasons {
  print(season)
}// spring summer autumn winter

4-11、CustomStringConvertible

/*遵守CustomStringConvertible协议, 可以自定义实例的打印字符串*/
class Person : CustomStringConvertible {
  var age: Int
  var name: String
  init(age: Int, name: String){
    self.age = age
    self.name = name
  }
  var description: String { //只读的计算属性
    "age=\(age), name=\(name)"
  }
}
var p = Person(age: 10, name: "Jack")
print(p) //age = 10, name=Jack

5-1、Any、AnyObject

/*Swift提供了2种特殊的类型: Any、AnyObject
    Any : 可以代表任意类型 (枚举、结构体、类、也包括函数类型)
    AnyObject: 可以代表任意类类型 (在协议后面写上: AnyObject代表只有类能遵守这个协议)
*/
protocol Runnable: AnyObject { }//AnyObject代表类类型的实例, 这样写代表只有类才能遵守这个协议
class Person : Runnable { }
struct Size : Runnable { }// 报错

var stu: Any = 10
stu = "Jack"
stu = Student()

//创建1个能存放任意类型的数组
// var data = Array<Any>()
var data = [Any]()
data.append(1)
data.append(3.14)
data.append(Student())
data.append("Jack")
data.append({ 10 })

6-1、is、as?、as!、as

/*is用来判断是否为某种类型, as用来做强制类型转换*/
protocol Runnable { func run() }
class Person { }
class Student : Person, Runnable {
  func run() {
    print("Student run")
  }
  func study() {
    print("Student study")
  }
}

var stu: Any = 10
print(stu is Int) // true
stu = "Jack"
print(stu is String) //true
stu = Student()
print(stu is Person) //true
print(stu is Student) //true
print(stu is Runnable) //true

var stu: Any = 10
(stu as? Student)?.study() //没有调用study
stu = Student()
(stu as? Student)?.study() // Student study
(stu as! Student).study() // Student study
(stu as? Runnable)?.run() // Student run

var data = [Any]()
data.append(Int("123") as Any)
print(data.count)

var d = 10 as Double
print(d) // 10.0

7-1、 X.self、 X.Type、AnyClass

/*
    X.self是一个元类型(metadata)指针, metadata存放着类型相关信息;
    X.self属性X.Type类型;
*/
class Person{
  
}
var p:Person = Person()
var pType: Person.Type = Person.self // 元类型指针(取出Person堆空间的前8个字节出来, 指向Person元类型的地址值)

var anyType: AnyObject.Type = Person.self
anyType = Student.self

public typealias AnyClass = AnyObject.Type
var anyType2: AnyClass = Person.self
anyType2 = Student.self

var per = Person()
//type(of: x) 接收一个实例,能告诉你这个实例的类型是什么
var perType = type(of: per) // Person.self
print(Person.self == type(of: per)) // true

7-2、元类型的应用

class Animal { required init() { } }// 必须使用 required 修饰,确保子类一定有init
class Cat : Animal { }
class Dog : Animal { }
class Pig : Animal { }

func create(_ clses: [Animal.Type]) -> [Animal] {
  var arr = [Animal]()
  for cls in clses {
    arr.append(cls.init()) //cls.init()调用初始化方法,返回实例
  }
  return arr
}
print(create([Cat.self, Dog.self, Pig.self]))

/*比如:
TabBarContorller 
1: HomeVIewController
2: AboutViewController

// oc
NSArray *array = @[HomeViewController.class, AboutViewController.class];
for(cls in array){
    [[cls alloc]init];
}
//swift
var array = @[HomeViewController.self, AboutViewController.self];
....
*/

import Foundation 
class Person {
    var age: Int = 0
}
class Student : Person {
    var no: Int = 0
}
//runtime: class_getInstanceSize获取类创建出实例对象需要多少内存
print(class_getInstanceSize(Student.self)) // 32
print(class_getSuperclass(Student.self)!) //Person
print(class_getSuperclass(Person.self)!) //Swift._SwiftObject
/*从结果可以看得出,Swift还有个隐藏的基类: Swift._SwiftObject
    可以参考Swift源码: https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftObject.h
*/
/*有些runtime的函数还是可以用在纯Swift中*/

7-3、Self

/*Self一般用作返回值类型, 限定返回值跟方法调用者必须是同一类型(也可以作为参数类型)*/
protocol Runnable {
  func test () -> self
}
class Person : Runnable {
    required init() { }
  func test() -> Self { type(of: self).init() }
}
class Student : Person { }

/*如果Self用在类中, 要求返回时调用的初始化器是required的*/

var p = Person()
// Person
print(p.test())

var stu = Student()
// Student
print(stu.test())

7-4、Person和Person.self

class Person {
  static var age = 0 //类型属性类型方法, 用类去调用
  static func run() {}
}
Person.age = 10 //Person代表一个类名
Person.run()

Person.self.age = 10//等同于上面
Person.self.run()// Person.self代表元类型指针

/// 下面的实例方法效果都是一样的
var p0 = Person()//init()
var p01 = type(of: p0).init()
var p1 = Person.self()//init()
var p2 = Person.init()//init()
var p3 = Person.self.init()//init()

// var pType0 = Person
var pType1 = Person.self

/*AnyClass相当于AnyObject.Type*/
func test(_ cls: AnyClass) {
  
}
test(Person.self)

/*总结:
共同点: 都能去访问类型属性和类型方法
区别: 一个叫元类类型, 类型不同
*/
上一篇下一篇

猜你喜欢

热点阅读