导语
历时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 0x10, %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)
/*总结:
共同点: 都能去访问类型属性和类型方法
区别: 一个叫元类类型, 类型不同
*/
网友评论