/– 课时22)标准库源码分析、项目实战 –/
要点:
- RxSwift有点重,一旦用了,整个项目就很难脱离它了。
标准库源码分析
Swift源码简介
- Swift与2015年正式开源,github地址:https://github.com/apple/swift
- 几个可能会经常看的目录
- docs:一些文档
- stdlib:Swift源码
- lib:C++源码
- include:C++头文件
- 标准库源码位置
- https://github.com/apple/swift/tree/main/stdlib/public/core
Array分析
- map、filter
- flatMap、compactMap、reduce
Substring分析
- apped、lowercased、uppercased
Optional分析
- map、flatMap、==、??
Metadata分析
反射
- 反射是编程语言中一项强大的能力,比如Java语言的反射机制
- 对于任意一个类型,都能够动态获取这个类的所有属性和方法信息
- 对于任意一个实例,都能够动态调用它的任意方法和属性
- Swift的反射机制目前比较弱,通过Mirror类型来提供简单的反射功能
常用Swift第三方库
- 网络请求:https://github.com/Alamofire/Alamofire
- 图片下载:https://github.com/onevcat/Kingfisher
- JSON访问:https://github.com/SwiftyJSON/SwiftyJSON
- JSON-Mode转换:https://github.com/kakaopensource/KaKaJSON
- HandyJSON:https://www.jianshu.com/p/939d33a626a0
Kingfisher注意点
- Kingfisher默认不支持WebP格式的图片,需要额外安装KingfisherWebP
- pod ‘KingfisherWebP’
详细了解Xcode单元测试
/—/
/– 课时21)面向协议编程、响应式编程 –/
协议用途:
利用协议实现前缀效果
extension MJ where Base == Person {} // Base只能是Person类型才能得到这个扩展
extension MJ where Base : Person {} // Person及其子类都能得到这个扩展
// 前缀类型
struct MJ<Base> {
var base: Base
init(_ base: Base) {
self.base = base
}
}
// 利用协议扩展前缀属性
protocol MJCompatible {}
extension MJCompatible {
var mj: MJ<Self> {
set {} // 此处加上set是为了mutating的语法能编译通过
get { MJ(self) }
}
static var mj: MJ<Self>.Type {
set {} // 此处加上set是为了mutating的语法能编译通过
get { MJ<Self>.self }
}
}
// 给字符串扩展功能,让String拥有mj前缀属性
extension String: MJCompatible {}
// 给 String.mj 或者 String().mj 前缀扩展功能
extension MJ where Base == String {
var numberCount: Int {
var count = 0
for c in base where ("0"..."9").contains(c) {
count += 1
}
return count
}
mutating func run() {}
static func test() {}
}
var str = "test_str"
str.mj.run() // 此处不能"test_str".mj.run(),因为"test_str"是一个常量,无法调用mutating修饰的函数
class Person {}
extension Person: MJCompatible {}
class Dog {}
extension Dog: MJCompatible {}
利用协议实现类型判断
响应式编程
- 响应式编程(Reactive Programming,简称RP)
- 也是一种编程范式,于1997年提出,可以简化异步编程,提供更优雅的数据绑定
- 一般与函数式融合在一起,所以也会叫做:函数响应式编程(Functional Reactive Programming,简称FRP)
- 比较著名的、成熟的响应式框架
- ReactiveCocoa
- 简称RAC,有Objective-C、Swift版本
- 官网:http://reactivecocoa.io/
- GitHub:https://github.com/ReactiveCocoa
- ReactiveX(建议使用这个)
- 简称Rx,有众多编程语言的版本,比如RxJava、RxKotlin、RxJS、RxCpp、RxPHP、RxGo、RxSwift等等
- 官网:http://reactivex.io/
- GitHub:https://github.com/ReactiveX
- ReactiveCocoa
RxSwift
- RxSwift(ReactiveX for Swift),是ReactiveX的Swift版本
- 源码:https://github.com/ReactiveX/RxSwift
- 中文文档:https://beeth0ven.github.io/RxSwift-Chinese-Documentation/
- RxSwift的github上已经有详细的安装教程,这里只演示CocoaPods方式的安装
// ① Podfile
pod 'RxSwift', '~>5'
pod 'RxCocoa', '~>5'
// ② 命令行
pod repo update
pod install
// ③ 导入模块
import RxSwift
import RxCocoa
- 模块说明
- RxSwift:Rx标准API的Swift实现,不包括任何iOS相关的内容
- RxCocoa:基于RxSwift,给iOS UI控件扩展了很多Rx特性
RxSwift的核心角色
- Observable:负责发送事件(Event)
- Observer:负责订阅Observable,监听Observable发送的事件(Event)
public enum Event<Element> {
/// Next element is produced.
case next(Element)
/// Sequence terminated with an error.
case error(Swift.Error)
/// Sequence completed successfully
case completed
}
- Event有3种
- next:携带具体数据
- error:携带错误信息,表明Observable终止,不会再发出事件
- completed:表明Observable终止,不会再发出事件
创建、订阅Observable1
创建、订阅Observable2
Disposable
- 每当Observable被订阅时,都会返回一个Disposable实例,当调用Disposable的dispose,就相当于取消订阅
- 在不需要再接收事件时,建议取消订阅,释放资源,有3种常见方式取消订阅
创建Observer的两种方式(video 6373s)
/—/
/– 课时20)函数式编程、面向协议编程 –/
函数式编程(Funtional Programming)
- 函数式编程(Funtional Programming,简称FP)是一种编程范式,也就是如何编写程序的方法论
- 主要思想:把计算过程尽量分解成一系列可复用函数的调用
- 主要特征:函数是“第一等公民”
- 函数与其他数据类型一样的地位,可以赋值给其他变量,也可以作为函数参数、函数返回值
- 函数式编程最早出现在LISP语言,绝大部分的现代编程语言也对函数式编程做了不同程度的支持,比如
- Haskell、JavaScript、Python、Swift、Kotlin、Scala等
- 函数式编程中几个常用的概念
- Higher-Order Function、Function Currying
- Functor、Applicative Functor、Monad
- 参考资料
- https://adit.io/posts/2013-04-17-functors,_appclatives,_and_monads_in_pictures.html
- http://www.mokacoding.com/blog/functor-applicative-monads-in-pictures
FP实践 - 传统写法
// 假设要实现一下功能: [(num + 3) * 5 - 1] % 10 / 2
let num = 1
func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
func sub(_ v1: Int, _ v2: Int) -> Int { v1 - v2 }
func multiple(_ v1: Int, _ v2: Int) -> Int { v1 * v2 }
func divide(_ v1: Int, _ v2: Int) -> Int { v1 / v2 }
func mod(_ v1: Int, _ v2: Int) -> Int { v1 % v2 }
divide(mod(sub(multiple(add(num, 3), 5), 1), 10), 2)
FP实践 - 函数式写法
let num = 1
func add(_ v: Int) -> (Int) -> Int { { $0 + v } }
func sub(_ v: Int) -> (Int) -> Int { { $0 - v } }
func multiple(_ v: Int) -> (Int) -> Int { { $0 * v } }
func divide(_ v: Int) -> (Int) -> Int { { $0 / v } }
func mod(_ v: Int) -> (Int) -> Int { { $0 % v } }
// 函数合成
infix operator >>> : AdditionPrecedence
func >>><A, B, C>(_ f1: @escaping (A) -> B,
_ f2: @escaping (B) -> C) -> (A) -> C { { f2(f1($0)) } }
var fn = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2)
fn(num)
高阶函数(Higher-Oder Function)
- 高阶函数是至少满足下列一个条件的函数
- 接受一个或多个函数作为输入(mpa、filter、reduce等)
- 返回一个函数
- FP中到处都是高阶函数
func add(_ v: Int) -> (Int) -> Int { { $0 + v } }
柯里化(Currying)
- 什么是柯里化
- 将一个接受多参数的函数变换为一系列只接受单个参数的函数
- Array、Optional的map方法接收的参数就是一个柯里化函数
- 例子,将已有函数柯里化
- 例子1
// v3 == 30
func add2(_ v3: Int) -> (Int) -> (Int) -> Int {
// v2 == 20
return { v2 in
// v1 == 10
return { v1 in
return v1 + v2 + v3
}
}
}
print(add2(30)(20)(10))
- 例子2
func add1(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
func add2(_ v1: Int, _ v2: Int, _ v3: Int) -> Int { v1 + v2 + v3 }
// 将上面两个函数柯里化
func currying<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) -> (A) -> C {
{ b in { a in fn(a, b) } } // 闭包表达式
}
func currying<A, B, C, D>(_ fn: @escaping (A, B, C) -> D) -> (C) -> (B) -> (A) -> D {
{ c in { b in { a in fn(a, b, c) } } }
}
let curriedAdd1 = currying(add1)
print(curriedAdd1(10)(20))
let curriedAdd2 = currying(add2)
print(curriedAdd2(10)(20)(30))
提示:如果一个函数需要接收4个以上的参数,就需要思考函数的是否存在设计上的Bug,并思考如何减少参数
函子(Functor)
- 像Array、Optional这样支持map运算的类型,称为函子(Functor)
// Array<Element>
public func map<T>(_ transform: (Element) -> T) -> Array<T>
// Optional<Wrapped>
public func map<U>(_ transform: (Wrapped) -> U) -> Optional<U>
适用函子(Applicative Functor)
- 对任意一个函子 F , 如果能支持以下运算,该函子就是一个适用函子
func pure<A>(_ value: A) -> F<A>
func <*><A, B>(fn: F<A -> B>, value: F<A>) -> F<B>
- Optional可以成为适用函子
func pure<A>(_ value: A) -> A? { value }
infix operator <*> : AdditionPrecedence
func <*><A, B>(fn: ((A) -> B)?, value: A?) -> B? {
guard let f = fn, let v = value else { return nil }
return f(v)
}
var value: Int? = 10
var fn: ((Int) -> Int)? = { $0 * 2 }
// Optional(20)
print(fn <*> value as Any)
- Array可以成为适用函子
func pure<A>(_ value: A) -> [A] { [value] }
func <*><A, B>(fn: [(A) -> B], value: [A]) -> [B] {
var arr: [B] = []
if fn.count == value.count {
for i in fn.startIndex..<fn.endIndex {
arr.append(fn[i](value[i]))
}
}
return arr
}
print(pure(10))
var arr = [{ $0 * 2 },{ $0 + 10 }, { $0 - 5 }] <*> [1, 2, 3]
print(arr)
单子(Monad)
- 对任意一个类型 F,如果能支持以下运算,那么就可以称为是一个单子(Monad)
func pure<A>(_ value: A) -> F<A>
func flatMap<A, B>(_ value: F<A>, _ fn: (A) -> F<B>) -> F<B>
- 很显然,Array、Optional都是单子
面向协议编程
- 面向协议编程(Protocol Oriented Programming,简称POP)
- 是Swift的一种编程范式,Apple于2015年WWDC提出
- 在Swift标准库中,能见到大量POP的影子
- 同时,Swift也是一门面向对象的编程语言(Object Oriented Programming,简称OOP)
- 在Swift开发中,OOP和POP是相辅相成的,任何一方并不能取代另一方
- POP能弥补OOP一些设计上的不足
回顾OOP
- OOP的三大特性:封装、继承、多态
- 当多个类(比如A、B、C类)具有很多共性时,可以将这些共性抽取到一个父类中(比如D类),最后A、B、C类继承D类
OOP的不足
- 但有些问题,使用OOP并不能很好解决,比如
- 如何将BVC(继承UIViewController)、DVC(UITableViewController)的公共方法run抽取出来?
- 基于OOP想到的一些解决方案
- 1、将run方法放到另一个对象A中,然后BVC、DVC拥有对象A属性
- 多来一些额外的依赖关系
- 2、将run方法添加到UIViewController分类中
- UIViewController会越来越臃肿,而且会影响它的其他所有子类
- 3、将run方法抽取到新的父类,采用多集成?(C++支持多继承)
- 会增加程序设计复杂度,产生菱形继承等问题,需要开发者额外解决
- 1、将run方法放到另一个对象A中,然后BVC、DVC拥有对象A属性
POP的解决方案
- 首先添加protocol,然后给protocol扩展一个方法实现,最后让其使用了该方法的对象添加这个扩展即可
POP的注意点
- 优先考虑创建协议,而不是父类(基类)
- 优先考虑值类型(struct、enum),而不是引用类型(class)
- 巧用协议的扩展功能
- 不要为了面向协议而使用协议
/—/
/– 课时19)从OC到Swift、函数式编程 –/
课前补充:
- 0 Substring
- Substring 发生修改 或者 转为String时,会重新分配新的内存存储字符串数据
- 1 String 无法桥接转换成 NSMutableString
- 但是,Swift可以通过 NSMutableString(string: “string content”) 创建一个NSMutableString对象
- 2 class Car: NSObject的内存结构
- 内存结构是isa指针和对象中的属性
只能被class继承的协议
protocol Runnable1: AnyObject {}
protocol Runnable2: class {}
@objc protocol Runnable3 {}
// 以上协议只能被类去遵守,结构体和枚举都无法遵守
- 被 @objc 修饰的协议,还可以暴露给OC去遵守实现
可选协议
- 可以通过 @objc 定义可选协议,这种协议只能被class遵守
dynamic
- 被 @objc dynamic 修饰的内容会具有动态性,比如调用方法会走runtime那一套流程
KVC\KVO
- Swift支持KVC\KVO的条件
- 属性所在的类、监听器最终继承自NSObject
- 用 @objc dynamic 修饰对应的属性
block方式的KVO
- 可以使用Swift的闭包监听内容的修改
class Person: NSObject {
@objc dynamic var age: Int = 0
var observation: NSKeyValueObservation?
override init() {
super.init()
observation = observe(\Person.age, options: .new) { (person, change) in
print(change.newValue as Any)
}
}
}
var p = Person()
p.age = 20
p.setValue(25, forKey: "age")
关联对象(Associated Object)
- 在Swift中,class依然可以使用关联对象
- 默认情况,extension不可以增加存储属性
- 借助关联对象,可以实现类似extension为class增加存储属性的效果
class Person {}
extension Person {
private static var AGE_KEY: Void? // Bool类型和Void?类型在内存中都是只占用一个字节的
var age: Int {
get {
objc_getAssociatedObject(self, &Self.AGE_KEY) as? Int ?? -1024
}
set {
objc_setAssociatedObject(self, &Self.AGE_KEY, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
}
}
var p = Person()
p.age = 1024
print(p.age)
资源名管理
- 有一种做法实际上是参考了Android的资源名管理方式
资源名管理还有其他思路
- 更多优秀的思路参考
- https://github.com/mac-cain13/R.swift
- https://github.com/SwiftGen/SwiftGen
多线程开发 - 异步
class demo {
public typealias Task = () -> Void
public static func async(_ task: @escaping Task) {
_async(task)
}
public static func async(_ task: @escaping Task,
_ mainTask: @escaping Task) {
_async(task, mainTask)
}
private static func _async(_ task: @escaping Task,
_ mainTask: Task? = nil) {
let item = DispatchWorkItem(block: task)
DispatchQueue.global().async(execute: item)
if let main = mainTask {
item.notify(queue: DispatchQueue.main, execute: main)
}
}
}
小提示:使用Swift开发时,当加上NS前缀无法得到对象的时候,可以尝试将NS前缀去掉,就可以得到之前在OC使用过的类
多线程开发 - 延迟
@discardableResult
public static func delay(_ seconds: Double,
_ block: @escaping Task) -> DispatchWorkItem {
let item = DispatchWorkItem(block: block)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now(),
execute: item)
return item
}
多线程开发 - 异步延迟
private static func _asyncDelay(_ seconds: Double,
_ task: @escaping Task,
_ mainTask: Task? = nil) -> DispatchWorkItem {
let item = DispatchWorkItem(block: task)
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + seconds,
execute: item)
if let main = mainTask {
item.notify(queue: DispatchQueue.main, execute: main)
}
return item // 返回的item可以调用item.cancel()取消延迟执行的任务
}
多线程开发 - once
- dispatch_once在Swift中已被废弃,取而代之
- 可以用 类型属性 或者 全局变量\常量
- 默认自带 lazy + dispatch_once 效果
多线程开发 - 加锁
- gcd信号量(使用DispatchSemaphore加锁)
- Foundation(使用NSRecursiveLock加锁)
思考:在Swift中,什么时候使用结构体,什么时候使用类?
函数式编程
Array的常见操作
lazy的优化
- array.lazy.map 可以使其元素用到时才去调用闭包函数
Optional的map和flatMap
- 对于可选类型使用map或flatMap,它是一个整体
- flatMap发现如果本身就是可选类型就不会再包装一次。
if a>0 && b>0
等价于
if a>0, b>0
/—/
/– 课时18)从OC到Swift –/
课前回顾
- .catch { error } : error是默认自带的
- #warning(“undo”)
- 编码习惯
iOS程序的入口
- 在AppDelegate上面默认有个@UIApplicationMain标记,这表示
- 编译器自动生成入口代码(main函数代码),自动设置AppDelegate为APP的代理
- 也可以删掉@UIApplicationMain,自定义入口代码:新建一个main.swift文件(文件名只能是main.swift)
Swift调用OC
- 新建1个桥接头文件,文件名格式默认为:{targetName}-Bridging-Header.h(targetName一般为项目名称)
- 在{targetName}-Bridging-Header.h 文件中 #import OC需要暴露给Swift的内容
- 如果C语言暴露给Swift的函数名跟Swift中的其他函数名冲突了
- 可以在Swift中使用 @_silgen_name 修改C函数名
OC调用Swift
- Xcode已经默认生成一个用于OC调用Swift的头文件,文件名格式是:{targetName}-Swift.h
- Swift暴露给OC的类最终继承自NSObject
- 使用 @objc 修饰需要暴露给OC的成员
- 使用 @objcMembers 修饰类
- 代表默认所有成员都会暴露给OC(包括扩展中定义的成员)
- 最终是否成功暴露,还需要考虑成员自身的访问级别
- Xcode会根据Swift代码生成对应的OC声明,写入 {targetName}-Swift.h 文件
- 可以通过 @objc 重命名Swift暴露给OC的符号名(类名、属性名、函数名等)
选择器(Selector)
- Swift中依然可以使用选择器,使用 #selector(name) 定义一个选择器
- 必须是被 @objcMembers 或 @objc 修饰的方法才可以定义选择器
补充:
- 纯Swift是没有runtime的
- runtime只存在在OC中
- dynamic关键字修饰的函数,是使用runtime机制去执行的
问题:
- 1、为什么Swift暴露给OC的类最终要继承自NSObject?
- 因为OC需要执行一些runtime的函数得到isa指针,而只有继承自NSObject才能够执行runtime函数
- 2、(p as Person).run()底层是怎么调用的?反之,OC调用Swift底层有时如何调用?
- 纯OC类暴露给Swift使用,其底层还是使用runtime机制
- OC调用Swift底层也是使用runtime机制
- 3、在Swift文件中,暴露给OC使用的类,依然在Swift中使用并执行 car.run() 底层是怎么调用的?
- 其底层依然是用Swift的虚表机制
String
- Swift的字符串类型String,跟OC的NSString,在API设计上还是有较大差异
- String的插入和删除方法
Substring
- String可以通过下标、prefix、suffix等截取子串、子串类型不是String,而是Substring
- Substring和它的base,共享字符串数据
- Substring转为String时,会重新分配新的内存存储字符串数据
String 与 Character
for c in "jack" { // c是Character类型
print(c)
}
var str = "jack"
// c是Character类型
var c = str[str.startIndex]
String相关的协议
- BidirectionalCollection 协议包含的部分内容
- startIndex、endIndex属性、index方法
- String、Array 都遵守了这个协议
- RangeReplaceableCollection 协议包含的部分内容
- append、insert、remove方法
- String、Array都遵守了这个协议
- Dictionary、Set 也有实现上述协议中声明的一些方法,只是并没有遵守上述协议
多行String
let str = """
1
"2"
3
'4'
"""
String 与 NSString
- String 与 NSString 之间可以随时随地桥接转换
- 如果你觉得String的API过于复杂难用,可以考虑将String转为NSString
- String 不能 桥接转换成 NSMutableString。反之,NSMutableString可以桥接转换成String,因为NSMutableString继承自NSString
- 比较字符串内容是否等价
- String使用 == 运算符
- NSString使用isEqual方法,也可以使用 == 运算符(本质还是调用了isEqual方法)
Swift、OC桥接转换表:
- String <-> NSString
- String <- NSMutableString
- Array <-> NSArray
- Array <- NSMutableArray
- Dictionary <-> NSDictionary
- Dictionary <- NSMutableDictionary
- Set <-> NSSet
- Set <- NSMutableSet
/—/
/– 课时17)字面量协议、模式匹配、条件编译 –/
字面量(Literal)
var age = 10
var isRed = false
var name = "Jack"
- 上面代码中的10、false、"Jack"就是字面量
- 常见字面量的默认类型
- public typealias IntegerLiteralType = Int
- public typealias FloatLiteralType = Double
- public typealias BooleanLiteralType = Bool
- public typealias StringLiteralType = String
// 可以通过typealias修改字面量的默认类型
typealias FloatLiteralType = Float
typealias IntegerLiteralType = UInt8
var age = 10 // UInt8
var height = 1.68 // Float
// 一般情况不建议对其进行修改,只需要知道有此功能即可
- Swift自带的绝大部分类型,都支持直接通过字面量进行初始化
- Bool、Int、Float、Double、String、Array、Dictionary、Set、Optional等
字面量协议
- Swift自带类型之所以能够通过字面量初始化,是因为它们遵守了对应的协议
- Bool:ExpressibleByBooleanLiteral
- Int:ExpressibleByIntegerLiteral
- Float、Double:ExpressibleByIntegerLiteral、ExpressibleByFloatLiteral
- Dictionary:ExpressibleByDictionaryLiteral
- String:ExpressibleByStringLiteral
- Array、Set:ExpressibleByArrayLiteral
- Optional:ExpressibleByNilLiteral
字面量协议应用
extension Int : ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) {
self = value ? 1 : 0
}
}
var num: Int = true
print(num) // 1
- 有点类似于C++中的转换构造函数
struct Point {
var x = 0.0, y = 0.0
}
extension Point : ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral {
init(arrayLiteral elements: Double...) {
guard elements.count > 0 else {
return
}
self.x = elements[0]
guard elements.count > 1 else {
return
}
self.y = elements[1]
}
init(dictionaryLiteral elements: (String, Double)...) {
for (k, v) in elements {
if k == "x" {
self.x = v
}else if k == "y" {
self.y = v
}
}
}
}
var p: Point = [10.5, 20.5]
print(p)
p = ["x": 11, "y": 22]
print(p)
模式(Pattern)
- 什么是模式?
- 模式是用于匹配规则,比如switch的case、捕捉错误的catch、if\guard\while\for语句的条件等
Swift中的模式有
-
通配符模式(wildcard Pattern)
- _ 匹配任何值
- _? 匹配非nil值
-
标示符模式(Identifier Pattern)
- 给对应的变量、常量名赋值
-
值绑定模式(Value-Binding Pattern)
-
元组模式(Tuple Pattern)
-
枚举Case模式(Enumeration Case Pattern)
- if case语句等价于只有1个case的switch语句
- for case语句
-
可选模式(Optional Pattern)
-
类型转换模式(Type-Casting Pattern)
-
表达式模式(Expression Pattern)
- 表达式模式用在case中
自定义表达式模式
- 可以通过重载运算符,自定义表达式模式的匹配规则
func isEven(_ i: Int) -> Bool { i % 2 == 0 }
func isOdd(_ i: Int) -> Bool { i % 2 != 0 }
extension Int {
static func ~= (pattern: (Int) -> Bool, value: Int) -> Bool {
pattern(value)
}
}
var age = 9
switch age {
case isEven:
print(age, "是个偶数")
case isOdd:
print(age, "是个奇数")
default: break
}
where
- 可以使用where为模式匹配增加匹配条件
var data = (10, "Jack")
switch data {
case let (age, _) where age > 10:
print(data.1, "age>10")
case let(age, _) where age > 0:
print(data.1, "age>0")
default:
break
}
var ages = [10, 20, 44, 23, 55]
for age in ages where age > 30 {
print(age)
} // 44 55
从OC到Swift
MARK、TODO、FIXME
- // MARK:类似于OC中的 #pragma mark
- // MARK: - 类似于OC中的 #pragma mark -
- // TODO:用于标记未完成的任务
- // FIXME:用于标记待修复的问题
条件编译
系统版本检测
if #available(iOS 10, macOS 10.12, *) {
// 对于iOS平台,只在iOS10及以上版本执行
// 对于macOS平台,只在macOS 10.12及以上版本执行
// 最后的*表示在其他所有平台都执行
}
API可用性说明
@available(iOS 10, macOS 10.15, *)
class Person {}
/—/
/– 课时16)内存访问冲突、指针 –/
局部作用域
- 可以使用 do 实现局部作用域
逃逸闭包的注意点
- 逃逸闭包不可以捕获inout参数
typealias Fn = () -> ()
func other1(_ fn: Fn) { fn() }
func other2(_ fn: @escaping Fn) { fn() }
func test(value: inout Int) -> Fn {
other1 { value += 1 }
// error:逃逸闭包不能捕获inout参数
other2 { value += 1 }
func plus() { value += 1 }
// error:逃逸闭包不能捕获inout参数
return plus
}
内存访问冲突(Conflicting Access to Memory)
- 内存访问冲突会在两个访问满足下列条件时发送:
- 至少一个是写入操作
- 它们访问的是同一块内存
- 它们的访问时间重叠(比如在同一个函数内)
- 如果下面的条件可以满足,就说明重叠访问结构体的属性是安全的
- 你只访问实例存储属性,不是计算属性或者类属性
- 结构体是局部变量而非全局变量
- 结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获
指针
- Swift中也有专门的指针类型,这些都被定性为“Unsafe”(不安全的),常见的有以下4种类型
- UnsafePointer 类似于 const Pointee *
- UnsafeMutablePointer 类似于 Pointee *
- UnsafeRawPointer 类似于 const void *
- UnsafeMutableRawPointer 类似于 void *
获得某个变量的指针
获得指向某个变量的指针
获得指向堆空间实例的指针
创建指针
(以上四个知识点内容省略,详情请查看视频)
指针之间的转换
var ptr = UnsafeMutableRawPointer.allocate(buteCount: 16, alignment: 1)
ptr.assumingMemoryBound(to: Int.self).pointer = 11
(ptr + 8).assumingMemoryBound(to: Double.self).pointee = 22.0
print(unsafeBitCast(ptr, to: UnsafePointer<Int>.self).pointee)
print(unsafeBitCast(ptr + 8, to: UnsafePointer<Double>.self).pointee)
ptr.deallocate()
- unsafeBitCast是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据
- 类似于C++中的reinterpret_cast
/—/
/– 课时15)访问控制、内存管理 –/
CustomStringConvertible
- 遵守CustomStringConvertible、CustomDebugStringConvertible协议,都可以自定义实例的打印字符串
- print调用的是CustomStringConvertible协议的description
- debugPrin、po调用的是CustomDebugStringConvertible协议的debugDescription
Self
- Self代表当前类型
assert(断言)
- 很多编程语言都有断言机制:不符合指定条件就抛出运行时错误,常用于调试(Debug)阶段的条件判断
- 默认情况下,Swift的断言智慧在Debug模式下生效,Release模式下回忽略
- 增加Swift Flags修改断言的默认行为
- -assert-config Release :强制关闭断言
- -assert-config Debug :强制开启断言
fatalError
- 如果遇到严重问题,希望结束程序运行时,可以直接使用fatalError函数抛出错误(这是无法通过do-catch捕捉的错误)
- 使用fatalError函数,就不需要再写return
- 在某些不得不实现、但不希望别人调用的方法,可以考虑内部使用fatalError函数
访问控制(Access Control)
- 在访问权限控制这块,Swift提供了5个不同的访问级别(以下是从高到低排列,实体指被访问级别修饰的内容)
- open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上)
- public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
- internal:只允许在定义实体的模块中访问,不允许在其他模块中访问
- fileprivate:只允许在定义实体的源文件中访问
- private:只允许在定义实体的封闭声明中访问
- (总结:Swift的访问是以文件、模块为访问单位的)
- 绝大部分实体默认都是internal级别
访问级别的使用准则
- 一个实体不可以被更低访问级别的实体定义,比如
- 变量\常量类型 ≥ 变量\常量
- 参数类型、返回值类型 ≥ 函数
- 父类 ≥ 子类
- 父协议 ≥ 子协议
- 原类型 ≥ typealias
- 原始值类型、关联值类型 ≥ 枚举类型
- 定义类型A时用到的其他类型 ≥ 类型A
- ……
元组类型
- 元组类型的访问级别是所有成员类型最低的那个
泛型类型
- 泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个
成员、嵌套类型
- 类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别
- 一般情况下,类型为private或fileprivate,那么成员\嵌套类型默认也是private或fileprivate
- 一般情况下,类型为internal或public,那么成员\嵌套类型默认是internal
- 子类重写的成员访问级别必须 ≥ 父类的成员访问级别
- 直接在全局作用域下定义的private等价于fileprivate
getter、setter
- getter、setter默认自动接收它们所属环境的访问级别
- 可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限
初始化器
- 如果一个public类想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器
- 因为public类的默认初始化器是internal级别
- required初始化器 ≥ 它的默认访问级别
- 如果结构体有private\fileprivate的存储实例属性,那么它的成员初始化器也是private\fileprivate
- 否则默认就是internal
枚举类型的case
- 不能给enum的每个case单独设置访问级别
- 每个case自动接收enum的访问级别
- public enum定义的case也是public
协议
- 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别
- public协议定义的要求也是public
- 协议实现的访问级别必须 ≥ 类型的访问级别,或者 ≥ 协议的访问级别
扩展
- 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别
- 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样
- 可以单独给扩展添加的成员设置访问级别
- 不能给用于遵守协议的扩展显式设置扩展的访问级别
- 在用一文件中的扩展,可以写成类似多个部分的类型声明
- 在原本的声明中的声明一个私有成员,可以在同一文件的扩展中访问它
- 在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
public class Person {
private func run0() {}
private func eat0() {
run1()
}
}
// 扩展一
extension Person {
private func run1() {}
private func eat1() {
run0()
}
}
// 扩展二
extension Person {
private func eat2() {
run1()
}
}
// 总结,扩展相当于将扩展的内容追加到类后面。
将方法赋值给var\let
- 方法也可以像函数那样,赋值给一个let或者var
内存管理
- 跟OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间)
- Swift的ARC中有3种引用
- 强引用(strong reference):默认情况下,引用都是强引用
- 弱引用(weak reference):通过weak定义弱引用
- 必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nil
- ARC自动给弱引用设置nil时,不会触发属性观察器
- 无主引用(unowned reference):通过unowned定义无主引用
- 不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC的 unsafe_unretained )
- 试图在实例销毁后访问无主引用,会产生运行时错误(野指针)
(Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocated)
weak、unowned的使用限制
- weak、unowned只能用在类实例上面
Autoreleasepool
public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result
// 闭包调用
autoreleasepool {
let p = MJPerson(age:20, name: "Jack")
p.run()
}
循环引用(Reference Cycle)
- weak、unowned都能解决循环引用的问题,unowned要比weak少一些性能消耗
- 在生命周期中可能会变为nil的使用weak
- 初始化赋值后再也不会变为nil的使用unowned
闭包的循环引用
- 闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作)
- 下面代码会产生循环引用,导致Person对象无法释放(看不到Person的deinit被调用)
class Person {
var fn: (() -> ())?
func run() { print("run") }
deinit { print("deinit") }
}
func test() {
let p = Person()
p.fn = { p.run() }
}
test()
- 在闭包表达式的捕获列表声明weak或unowned引用,解决循环引用问题
// 第一种
p.fn = {
[weak p] in
p?.run()
}
// 第二种
p.fn = {
[unowned p] in
p.run()
}
// 第三种
p.fn = {
[weak wp = p, unowned up = p, a = 10 + 20] in
wp?.run()
}
- 如果想在定义闭包属性的同时引用self,这个闭包必须是lazy的(因为在实例初始化完毕之后才能引用self)
class Person {
lazy var fn: (() -> ()) = {
[weak self] in
self?.run()
}
func run() { print("run") }
deinit { print("deinit") }
}
- 左边的闭包fn内部如果用到了实例成员(属性、方法)
- 编译器会强制要求明确写出self
- 如果lazy属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生命周期就结束了)
class Person {
var age: Int = 0
lazy var getAge: Int = {
self.age
}()
deinit { print("deinit") }
}
@escaping
- 非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
- 非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内
- 逃逸闭包:闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过@escaping声明
import Dispatch
typealias Fn = () -> ()
class Person {
var fn: Fn
// fn是逃逸闭包
init(fn: @escaping Fn) {
self.fn = fn
}
func run() {
// DispatchQueue.global().async 也是一个逃逸闭包
// 它用到了实例成员(属性、方法),编译器会强制要求明确写出self
DispatchQueue.global().async {
self.run()
}
}
}
(课后研究:什么是Swift捕获列表?Swift的DispatchQueue详情?)
/—/
/– 课时14)可选项的本质、运算符重载、扩展 –/
可选项的本质
- 可选项的本质是enum类型
var age: Int? = .none
age = 10
switch age {
case let v?:
print("some", v)
case nil:
print("none")
}
switch age {
case let .some(v):
print("some", v)
case .none:
print("none")
}
var age_: Int? = 10
var age: Int?? = age_
age = nil
// 等价于
var age0 = Optional.some(Optional.some(10))
age0 = .none
var age1: Optional.<Optional> = .some(.some(10))
age1 = .none
// 以下两种方式声明也是等价的
var age: Int?? = 10
var age0: Optional<Optional> = 10
溢出运算符(Overflow Operator)
- Swift的算数运算符出现溢出时会抛出运行时错误
- Swift有溢出运算符(&+、&-、&*),用来支持溢出运算
运算符重载(Operator Overload)
- 类、结构体、枚举可以为现有的运算符提供自定义的实现,这个操作叫做:运算符重载
struct Point {
var x: Int, y: Int
static func + (p1: Point, p2: Point) -> Point {
Point(x: p1.x + p2.x, y: p1.y + p2.y
}
// 还有以下几种定义方式
static prefix func - (p: Point) -> Point {
Point(x: -p.x, y: -p.y)
}
static func += (p1: inout Point, p2: Point) -> Point {
p1 = p1 + p2
}
static prefix func ++ ……
static postfinx func ++ ……
static func == (p1: Point, p2: Point) -> {
(p1.x == p2.x) && (p1.y == p2.y)
}
}
let p = Point(x:10, y:20) + Point(x: 11, y:22)
print(p) // Point(x: 21, y: 42)
Equatable
- 要想得知2个实例是否等价,一般做法是遵守Equatable协议,重载 == 运算符
- 与此同时, 等价于重载了 != 运算符
struct Point : Equatable {
var x: Int, y: Int
}
var p1 = Point(x: 10, y: 20)
var p2 = Point(x: 11, y: 22)
print(p1 == p2) // false
print(p1 != p2) // true
- Swift为以下类型提供默认Equatable实现
- 没有关联类型的枚举
- 只拥有遵守Equatable协议关联类型的枚举
- 只拥有遵守Equatable协议存储属性的结构体
- 引用类型比较存储的地址值是否相等(是否引用着同一个对象),使用恒等运算符===、!==
Comparable
- 要想比较2个实例的大小,一般做法是:
- 遵守Comparable协议
- 重载相应的运算符
// score大的比较大,若score相等,age小的比较大
struct Student : Comparable {
var age: Int
var score: Int
init(score: Int, age: Int) {
self.score = score
self.age = age
}
static func < (lhs: Student, rhs: Student) -> Bool {
(lhs.score < rhs.score) || (lhs.score == rhs.score && lhs.age > rhs.age)
}
static func > (lhs: Student, rhs: Student) -> Bool {
(lhs.score > rhs.score) || (lhs.score == rhs.score && lhs.age < rhs.age)
}
static func <= (lhs: Student, rhs: Student) -> Bool {
!(lhs > rhs)
}
static func >= (lhs: Student, rhs: Student) -> Bool {
!(lhs < rhs)
}
}
自定义运算符(Custom Operator)
- 可以自定义新的运算符:在全局作用域使用operator进行声明
prefix operator 前缀运算符
postfix operator 后缀运算符
infix operator 中缀运算符 : 优先级组
precedencegroup 优先级组 {
associativity: 结合性(left\right\none)
higherThan: 比谁的优先级高
lowerThan: 比谁的优先级低
assignment: true代表在可选链操作中拥有跟赋值运算符一样的优先级
}
prefix operator +++
infix operator +- : PlusMinusPrecedence
precedencegroup PlusMinusPrecedence {
associativity: none
higherThan: AdditionPrecedence
lowerThan: MultiplicationPrecedence
assignment: true
}
扩展(Extension)
- Swift中的扩展,有点类似于OC中的分类(Category)
- 扩展可以为枚举、结构体、类、协议添加新功能
- 可以添加方法、计算属性、下标、(便捷)初始化器、嵌套类型、协议等等
- 扩展不能办到的事情
- 不能覆盖原有的功能
- 不能添加存储属性,不能向已有的属性添加属性观察器
- 不能添加父类
- 不能添加指定初始化器,不能添加反初始化器
- ……
初始化器
- 如果希望自定义初始化器的同时,编译器也能够生成默认初始化器
- 可以在扩展中编写自定义初始化器
- 类遵守协议实现的required初始化器,也不能写在扩展中
协议
- 如果一个类型已经实现了协议的所有要求,但是还没有声明它遵守了这个协议
- 可以通过扩展来让它遵守这个协议
- 编译一个函数,判断一个整数是否为奇数?
// 第一种
func isOdd<T: BinaryInteger>(_ i: T) -> Bool {
i % 2 != 0
}
// 第二种
extension BinaryInteger {
func isOdd() -> Bool {
self % 2 != 0
}
}
- 扩展可以给协议提供默认实现,也间接实现「可选协议」的效果
- 扩展可以给协议扩充「协议中从未声明过的方法」
protocol TestProtocol {
func test1()
}
extension TestProtocol {
func test1() {
print("TestProtocol test1")
}
func test2() {
print("TestProtocol test2")
}
}
泛型
class Stack<E> {
var elements = [E]()
}
- 扩展中依然可以使用原类型中的泛型类型
extension Stack {
func top() -> E {
elements.last!
}
}
- 符合条件才扩展
extension Stack : Equatable where E : Equatable {
static func == (left: Stack, right: Stack) -> Bool {
left.elements == right.elements
}
}
/—/
/– 课时13)汇编分析String、Array底层 –/
关于String的思考
- 1个String变量占用多少内存?
- 下面2个String变量,底层存储有什么不同?
var str1 = "0123456789"
var str2 = "0123456789ABCDEF"
- 内存地址从低到高分别是,代码区->常量区->全局区(数据段)->堆空间->栈空间->动态库
- 如果对String进行拼接操作,String变量的存储会发生什么变化?
str1.append("ABCDE")
str1.append("F")
str2.append("G")
Mach-O文件是Apple下的可执行文件
- 从编码到启动APP
- OC、Swift源码通过编译链接成Mach-O可以执行文件,然后启动该文件。
dyld_stub_binder
- 符号的延迟绑定通过dyld_stub_binder完成
- jmpq *0xb31(%rip)格式的汇编指令
- 占用6个字节
关于Array的思考
- 1个Array变量占用多少内存?
- 8个字节,并且该变量是一个地址值
- 数组中的数据存放在哪里?
- 堆空间中
/—/
/– 课时12)Error处理、泛型 –/
补充上节课知识
- deinit
- Person和Person.self
创建对象的方式
* var p0 = Person()
* var p1 = Person.init()
* var p2 = type(of: p0).init()
* var p3 = Person.self()
* var p4 = Person.self.init()
错误类型
- 开发过程常见的错误
- 语法错误(编译报错)
- 逻辑错误
- 运行时错误(可能会导致闪退,一般也叫做异常)
自定义错误
- Swift中可以通过Error协议自定义运行时的错误信息
- 函数内部通过throw抛出自定义Error,可能会抛出Error的函数必须加上throws声明
- 需要使用try调用可能会抛出Error的函数
do-catch
- 可以使用do-catch捕捉Error
- 抛出Error后,try下一句直到作用域结束的代码都将停止运行
处理Error
- 处理Error的2种方式
- ① 通过do-catch捕捉Error
- ② 不捕捉Error,在当前函数新增throws声明,Error将自动抛给上层函数
- 如果最顶层函数(main函数)依然没有捕捉Error,那么程序将终止
try?、try!
- 可以使用try?、try!调用可能会抛出Error的函数,这样就不用去处理Error
- a、b是等价的
var a = try? divide(20, 0)
var b: Int?
do{
b = try divide(20, 0)
} catch {}
rethrows
- rethrows表明:函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛
func exec(_ fn: (Int, Int) throws -> Int, _ num1: Int, _ num2: Int) rethrows {
print(try fn(num1, num2))
}
// Fatal error: Error raised at top level
try exec(divide, 20, 0)
defer
- defer语句:用来定义以任何方式(抛错误、return等)离开代码块前必须要执行的代码
- defer语句将延迟至当前作用域结束之前执行
- defer语句的执行顺序与定义顺序相反(也就是,先定义的后执行,后定义的先执行)
泛型(Generics)
- 泛型可以将类型参数化,提高代码复用率,减少代码量
func swapValue<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
- 泛型函数赋值给变量
func test<T1, T2>(_ t1: T1, _ t2: T2) {}
var fn: (Int, Double) -> () = test
关联类型(Associated Type)
- 关联类型的作用:给协议中用到的类型定义一个占位名称
- 协议中可以拥有多个关联类型
- 协议中只能用关联类型,因为泛型参数是用在类、结构体、枚举、函数上的。
- 可以使用 typealias Element = String 给关联类型设定真实类型;也可以不指定,编译器会自动识别器关联类型
类型约束
protocol Runnable { }
class Person { }
func swapValues<T : Person & Runnable>(_ a: inout T, _ b: intou T) {
(a, b) = (b, a)
}
protocol Stackable {
associatedtype Element: Equatable
}
class Stack<E : Equatable> : Stackable { }
协议类型的注意点
- 如果协议中有associatedtype
protocol Runnable {
associatedtype Speed
var speed: Speed { get }
}
class Person : Runnable {
var speed: Double { 0.0 }
}
class Car : Runnable {
var speed: Int { 0 }
}
泛型解决
- 解决方案①:使用泛型
不透明类型(Opaque Type)
- 解决方案②:使用some关键字声明一个不透明类型
- some限制只能返回一种类型
some
- some除了用在返回值类型上,一般还可以用在属性类型上
/—/
/– 课时11)init、deinit、可选链、协议、元类型 –/
required
- 用required修饰指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写实现)(这里有一种情况是子类未写任何初始化器也不报错,原因是因为子类无任何初始化器时,会触发自动继承机制)
- 如果子类重写了required初始化器,也必须加上required,不用加override
属性观察器
- 父类的属性在它自己的初始化器中赋值不会触发属性观察器,但在子类的初始化器中赋值会触发属性观察器
可失败初始化器
- 类、结构体、枚举都可以使用 init? 定义可失败初始化器
- 不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器和非可失败初始化器
- 可以用 init! 定义隐式解包的可失败初始化器
- 可失败初始化器可以调用非可失败初始化器,非可失败初始化器调用可失败初始化器需要进行解包
- 如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都失败,并且之后的代码都停止执行
- 可以用一个非可失败初始化器重写一个可失败初始化器,但反过来是不行的
反初始化器(deinit)
- deinit叫做反初始化器,类似于C++的析构函数、OC中的dealloc方法
- 当类的实例对象被释放内存时,就会调用实例对象的deinit方法
- deinit不接受任何参数,不能写小括号,不能自行调用
- 父类的deinit能被子类继承
- 子类的deinit实现执行完毕后会调用父类的deinit
可选链(Optional Chaining)
- 如果可选项为nil,调用方法、下标、属性失败,结果为nil
- 如果可选项不为nil,调用方法、下标、属性成功,结果会被包装成可选项
- 如果结果本来就是可选项,不会进行再次包装
- 多个 ? 可以链接在一起
- 如果链中任何一个节点是nil,那么整个链就会调用失败
协议(Protocol)
- 协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)
- 协议中定义方法时不能有默认参数值
- 默认情况下,协议中定义的内容必须全部都实现
- 也有办法办到只实现部分内容,以后的课程会讲到
协议中的属性
- 协议中定义属性时必须用var关键字
- 实现协议时的属性权限要不小于协议中定义的属性权限
- 协议定义get、set,用var存储属性或get、set计算属性去实现
- 协议定义get,用任何属性都可以实现
static、class
- 为了保证通用,协议中必须用static定义类型方法、类型属性、类型下标
mutating
- 只有将协议中的实例方法标记为mutating
- 才允许结构体、枚举的具体实现修改自身内存
- 类在实现方法时不用加mutating,枚举、结构体才需要加mutating
init
- 协议中还可以定义初始化器init
- 非final类实现时必须加上required
- 如果从协议实现的初始化器,刚好是重写了父类的指定初始化器
- 那么这个初始化必须同时加required、override
init、init?、init!
- 协议中定义的init?、init!,可以用init、init?、init!去实现
- 协议中定义的init,可以用init、init!去实现
协议的继承
- 一个协议可以继承其他协议
协议组合
protocol Livable { }
protocol Runnable { }
class Person { }
// 接收Person或者其子类的实例
func fn0(obj: Person) { }
// 接收遵守Livable协议的实例
func fn1(obj: Livable) { }
// 接收同时遵守Livable、Runnable协议的实例
func fn2(obj: Livable & Runnable) { }
// 接收同时遵守Livable、Runnable协议、并且是Person或者子类的实例
func fn3(obj: Person & Livable & Runnable) { }
// 另一种,接收同时遵守Livable、Runnable协议、并且是Person或者子类的实例
typealias RealPerson = Person & Livable & Runnable
func fn4(obj: RealPerson) { }
- 协议组合,可以包含1个类类型(最多1个)
CaseIterable
- 让枚举遵守CaseIterable协议,可以实现遍历枚举值
CustomStringConvertible
- 遵守CustomStringConvertible协议,可以自定义实例的打印字符串
Any、AnyObject
- Swift提供了2种特殊的类型:Any、AnyObject
- Any:可以代表任意类型(枚举、结构体、类,也包括函数类型)
- AnyObject:可以代表任意类类型(在协议后面写上:AnyObject代表只有类能遵守这个协议)
is、as?、as!、as
- is用来判断是否为某种类型,as用来做强制类型转换
X.self、X.Type、AnyClass
- X.self是一个元类型(metadata)的指针,metadata存放着类型相关信息(跟对象在堆空间存放的前8个字节是一样的)
- X.self属于X.Type类型
元类型的应用
- 从结果可以看得出来,Swift还有个隐藏的基类:Swift._SwiftObject
Self
- Self一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以作为参数类型)
- 如果Self用在类中,要求返回时调用的初始化器是required的
/—/
/– 课时10)汇编分析多态原理、init –/
Swift中多态的实现原理:类似于C++的虚表(虚函数表)
对象的前16个字节中,前8个是类型信息,后8个是引用技术
初始化器
- 类、结构体、枚举都可以定义初始化器
- 类有2种初始化器:指定初始化器(designated initializer)、便捷初始化器(convenience initializer)
- 每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器
- 默认初始化器总是类的指定初始化器
- 类偏向于少量指定初始化器,一个类通常只有一个指定初始化器
- 初始化器的相互调用规则
- 指定初始化器必须从它的直系父类调用指定初始化器
- 便捷初始化器必须从相同的类里调用另一个初始化器
- 便捷初始化器最终必须调用一个指定初始化器
初始化器的相互调用
- 这一套规则保证了
- 使用任意初始化器,都可以完整地初始化实例
两段式初始化
- Swift在编码安全方面是煞费苦心,为了保证初始化过程的安全,设定了两段式初始化、安全检查
- 两段式初始化
- 第1阶段:初始化所有存储属性
- 外层调用指定\便捷初始化器
- 分配内存给实例,但未初始化
- 指定初始化器确保当前类定义的存储属性都初始化
- 指定初始化器调用父类的指定初始化器,不断向上调用,形成初始化器链
- 第2阶段:设置新的存储属性值
- 从顶部初始化器往下,链中的每一个指定初始化器都有机会进一步定制实例
- 初始化器现在能够使用self(访问、修改它的属性,调用它的实例方法等等)
- 最终,链中任何便捷初始化器都有机会定制实例以及使用self
- 第1阶段:初始化所有存储属性
安全检查
- 指定初始化器必须保证在调用父类初始化器之前,其所在类定义的所有存储属性都要初始化完成
- 指定初始化器必须先调用父类指定初始化器,然后才能为继承的属性设置新值
- 便捷初始化器必须先调用同类中的其他初始化器,然后再为任意属性设置新值
- 初始化器在第1阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self
- 直到第1阶段结束,实例才算完全合法
重写初始化器
- 当重写父类的指定初始化器时,必须加上override(即使子类的实现是便捷初始化器)
- 如果子类写了一个匹配父类便捷初始化器的初始化器,不用加上override
- 因为父类的便捷初始化器永远不会通过子类直接调用,因此,严格来说,子类无法重写父类的便捷初始化器
自动继承
- ① 如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器
- ② 如果子类提供了父类所有指定初始化器的实现(要么通过方式①继承,要么重写)
- 子类自动继承所有的父类便捷初始化器
- ③ 就算子类添加了更多的便捷初始化器,这些规则仍然适用
- ④ 子类以便捷初始化器的形式重写父类的指定初始化器,也可以作为满足规则②的一部分
/—/
/– 课时9)汇编分析类型属性、方法、下标、继承 –/
方法(Method)
枚举、结构体、类都可以定义实例方法、类型方法
- 实例方法(Instance Method):通过实例调用
- 类型方法(Type Method):通过类型调用,用 static 或者 class 关键字定义
self - 在实例方法中代表实例
- 在类型方法中代表类型
mutating
结构体和枚举是值类型,默认情况下,值类型的属性不能被自身的实例方法修改
- 在func关键字前加mutating可以允许这种修改行为
@discardableResult
在func前面加个@discardableResult,可以消除:函数调用后返回值未被使用的警告
下标(subscript)
使用subscript可以给任意类型(枚举、结构体、类)增加下标功能,有些地方也翻译为:下标脚本
- subscript的语法类似于实例方法、计算属性、本质就是方法(函数)
- subscript中定义的返回值类型决定了
- get方法的返回值类型
- set方法中newValue的类型
- subscript可以接受多个参数,并且类型任意
下标的细节
- subscript可以没有set方法,但必须要有get方法
- 如果只有get方法,可以省略get
- 可以设置参数标签(例子:p[index: 1])
- 下标可以是类型方法
了解下:
结构体、类作为返回值对比;
接收多个参数的下标;
继承(Inheritance)
值类型(枚举、结构体)不支持继承,只有类支持继承
没有父类的类,称为:基类
- Swift并没有像OC、Java那样的规定:任何类最终都要继承子某个基类
- 子类可以重写父类的下标、方法、属性,重写必须加上override关键字
内存结构
重写实例方法、下标
重写类型方法、下标
- 被class修饰的类型方法、下标,允许被子类重写
- 被static修饰的类型方法、下标、不允许被子类重写
重写属性
- 子类可以将父类的属性(存储、计算)重写为计算属性
- 子类不可以将父类属性重写为存储属性
- 只能重写var属性,不能重写let属性
- 重写时,属性名、类型要一致
- 子类重写后的属性权限不能小于父类属性的权限
- 如果父类属性是只读的,那么子类重写后的属性可以是只读的、也可以是可读写的
- 如果父类属性是可读写的,那么子类重写后的属性也必须是可读写的
重写实例属性
- 使用属性需要加上super,不然会导致无限递归
重写类型属性
- 被class修饰的计算类型属性,可以被子类重写
- 被static修饰的类型属性(存储、计算),不可以被子类重写
属性观察器
- 可以在子类中为父类属性(除了只读计算属性、let属性)增加属性观察器
final
- 被final修饰的方法、下标、属性,禁止被重写
- 被final修饰的类,禁止被继承
/—/
/– 课时8)属性、汇编分析inout本质 –/
属性
Swift中跟实例相关的属性可以分为2大类
- 存储属性(Stored Property)
- 类似于成员变量这个概念
- 存储在实例的内存中
- 结构体、类可以定义存储属性
- 枚举不可以定义存储属性
- 计算属性(Computed Property)
- 本质就是方法(函数)
- 不占用实例的内存
- 枚举、结构体、类都可以定义计算属性
注意
- 存储属性(关于存储熟悉,Swift有个明确的规定)
- 在创建 类 或 结构体的实例时,必须为所有的存储属性设置一个合适的初始值
- 可以在初始化器里(init函数)为存储属性设置一个初始值
- 可以分配一个默认的属性值作为属性定义的一部分
- 在创建 类 或 结构体的实例时,必须为所有的存储属性设置一个合适的初始值
- 计算属性
- set传入的新值默认叫做newValue,也可以自定义
- 只读计算属性:只有get,没有set
- 定义计算属性只能用var,不能用let
- 计算属性的值是可能发生变化的(即使是只读计算属性)
枚举rawValue原理
枚举原始值rawValue的本质是:只读计算属性
延迟存储属性(Lazy Stored Property)
使用lazy可以定义一个延迟存储属性,在第一次用到属性的时候才会进行初始化
- lazy属性必须是var,不能是let
- let必须在实例的初始化方法完成之前就拥有值
- 如果多条线程同时第一次访问lazy属性
- 无法保证属性只被初始化1次
延迟存储属性注意点
- 当结构体包含一个延迟存储属性时,只有var才能访问延迟存储属性
- 因为延迟属性初始化时需要改变结构体的内存
属性观察器(Property Observer)
可以为非lazy的var存储属性设置属性观察器
willSet会传递新值,默认叫newValue
didSet会传递旧值,默认叫oldValue
在初始化器中设置属性值不会触发willSet和didSet
全局变量、局部变量
属性观察器、计算属性的功能,同样可以应用在全局变量、局部变量身上
inout的再次研究
输入输出的传递就是引用传递
inout的本质总结
- 如果实参有物理内存地址,且没有设置属性观察器
- 直接将实参的内存地址传入函数(实参进行引用传递)
- 如果实参是计算属性 或者 设置了属性观察器
- 采取了Copy In Copy Out的做法
- 调用该函数时,先复制实参的值,产生副本【get】
- 将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
- 函数返回后,再将副本的值覆盖实参的值【set】
- 采取了Copy In Copy Out的做法
- 总结:inout的本质就是引用传递(地址传递)
类型属性(Type Property)
- 严格来说,属性可以分为
- 实例属性(Instance Property):只能通过实例去访问
- 存储实例属性(Stored Instance Property):存储在实例的内存中,每个实例都有1份
- 计算实例属性(Computed Instance Property)
- 类型属性(Type Property):只能通过类型去访问
- 存储类型属性(Stored Type Property):整个程序运行过程中,就只有1份内存(类似于全局变量)
- 计算类型属性(Computed Type Property)
- 实例属性(Instance Property):只能通过实例去访问
- 可通过static定义类型属性
- 如果是类,也可以用关键字class
类型属性细节
- 不同于存储实例属性,你必须给存储类型属性设定初始值
- 因为类型没有像实例那样的init初始化器来初始化存储属性
- 存储类型属性默认就是lazy,会在第一次使用的时候才初始化
- 就算被多个线程同时访问,保证只会初始化一次
- 存储类型属性可以是let
- 枚举类型也可以定义类型属性(存储类型属性、计算类型属性)
单例模式
public class FileManager {
public static let shared = {
// …
// …
return FileManager()
}
Private init() { }
}
/—/
/– 课时7)汇编分析闭包本质02 –/
注意:
如果返回值是函数类型,那么参数的修饰要保持统一
自动闭包
注意:在函数调用中,如果需传递两个参数,其中参数一条件不满足才使用参数二,那么可以将参数二定义为函数的方式,这样如果未使用参数二就不会被调用。
|- @autoclosure 会自动将20封装成闭包 { 20 }
|- @autoclosure 只支持 () -> T 格式的参数
|- @autoclosure 并非只支持最后一个参数
|- 空合运算符 ?? 使用了 @autoclosure 技术
|- 有@autoclosure、无@autoclosure,构成了函数重载
|- 为了避免与期望冲突,使用@autoclosure的地方最好明确注释清楚:这个值会被推迟执行
/—/
/– 课时6)汇编分析闭包本质01 –/
引用类型的赋值操作
值类型、引用类型的let
值类型定义let后不能修改成员值
引用类型定义let可以修改成员值
引用类型
引用赋值给var、let或者给函数传参,是将内存地址拷贝一份
类似于制作一个文件的替身(快捷方式、链接),指向的是同一个文件。属于浅拷贝(shallow copy)
嵌套类型
枚举、结构体、类都可以定义方法
一般把定义在枚举、结构体、类内部的函数,叫做方法
方法不占用对象的内存
方法的本质就是函数
方法、函数都存放在代码段
闭包表达式(Closure Expression)(类似于OC的block)
在Swift中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数。
{
(参数列表) -> 返回值类型 in
函数体代码
}
闭包表达式的简写
例子,闭包表达式的简化
let descendingArray2 = cityArray.sorted(by: {s1,s2 in return s1 > s2})
let descendingArray3 = cityArray.sorted(by: {s1,s2 in s1 > s2})
let descendingArray4 = cityArray.sorted(by: {$0 > $1})
let descendingArray5 = cityArray.sorted(by: > )
尾随闭包
如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性。
|- 尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式
如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号
示例 - 数组的排序
忽略参数
闭包(Closure)
一个函数和它所捕获的变量\常量环境组合起来,称为闭包
|- 一般指定义在函数内部的函数
|- 一般她捕获的是外层函数的局部变量\常量
可以把闭包想象成是一个类的实例对象
|- 内存在堆空间
|- 捕获的局部变量\常量就是对象的成员(存储属性)
|- 组成闭包的函数就是类内部定义的方法
/—/
/– 课时5)汇编分析结构体、类的内存布局 –/
结构体
在Swift标准库中,绝大多数的公开类型都是结构体,而枚举和类只占很小一部分
比如Bool、Int、Double、String、Array、Dictionary等常见类型都是结构体
struct Date{
var year: Int
var month: Int
var day: Int
}
var date = Date(year:2019, month:6, day:23)
所有的结构体都有一个编译器自动生成的初始化器(initializer,初始化方法、构造器、构造方法)
在上述代码最后一行,可以传入所有成员值,用以初始化所有成员变量(存储属性,Stored Property)
结构体的初始化器
编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是:保证所有成员都有初始化值
自定义初始化器
一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其他初始化器
窥探初始化器的本质
自己写初始化值,系统会自动生成init函数
类
类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器
类的初始化器
如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器
成员的初始化是在当前初始化器中完成的
结构体与类的本质区别
结构体是值类型(枚举也是值类型),类是引用类型(指针类型)
值类型
值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份
类似于对文件进行copy、paste操作,产生了全新的文件副本。属于深拷贝(deep copy)
Swift底层用C++写的,所以底层跟C++很像
规律
内存地址格式为:0x4bdc(%rip),一般是全局变量,全局区(数据段)。
内存地址格式为:-0x78(%rbp),一般是局部变量,栈空间。
内存地址格式为:0x10(%rax),一般是堆空间。
值类型的复制操作(字符串、数组都是struct)
在Swift标准库中,为了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技术
(比如仅当有“写”操作时,才会真正执行拷贝操作)
(对于标准库值类型的赋值操作,Swift能确保最佳性能,所以没必要为了保证最佳性能来避免赋值)
建议:不需要修改的,尽量定义成let
引用类型
引用赋值给var、let或者给函数传参,是将内存地址拷贝一份
(类似于制作一个文件的替身(快捷方式、链接),指向的是同一个文件。属于浅拷贝(shallow copy))
/—/
/– 课时3)枚举、可选项 –/
枚举的基本用法
关联值(Associated Value)
有时会将枚举的成员值跟其他类型的关联存储在一起,会非常有用。
enum Score {
case points(Int)
case grade(Character)
}
原始值(Raw Values)
枚举成员可以使用相同类型的默认值预先关联,这个默认值叫做:原始值
enum PokerSuit: Character {
case spade = “♠️”
case heart = “❤️”
case diamond = “F”
case club = “♣️”
}
隐式原始值(Implicitly Assigned Raw Values)
如果枚举的原始值类型是Int、String,Swift会自动分配原始值
递归枚举(Recursive Enumeration)
indirect enum ArithExpr {
case number(Int)
case sum(ArithExpr, ArithExpr)
case difference(ArithExpr, ArithExpr)
}
MemoryLayout
可以使用MemoryLayout获取数据类型占用的内存大小
可选项(Optional)
一般也叫可选类型,它允许将值设置为nil
在类型名称后面加个问号(?)来定义一个可选类型
强制解包(Forced Unwrapping)
可选项是对其他类型的一层包装,可以将它理解为一个盒子
如果为nil,那么它是个空盒子
如果不为nil,那么盒子里装的是:被包装类型的数据
如果要从可选项中取出被包装的数据(将盒子里装的东西取出来),需要使用感叹号 !进行强制解包。
如果对值为nil的可选项(空盒子)进行强制解包,将会产生运行时错误。
判断可选项是否包含值
let number = Int(“k123”)
number != nil
可选项绑定(Optional Binding)
可以使用可选项绑定来判断可选项是否包含值
如果包含就自动解包,把值赋值给一个临时的常量(let)或者变量(var),并返回true,否则返回false
if let number = Int(“123”) {
}else {}
等阶写法
可选项绑定需要使用逗号分割判断
While循环中使用可选项绑定
空合运算符?? (Nil-Coalescing Operator)
let username = loginName ?? “Guest”
loginName 表示为可选性,如果loginName 为空,则使用默认名称 Guest
a ?? b
a 是可选项
b 是可选项 或者 不是可选项
b 跟a 的存储类型必须相同
如果a 不为nil,就返回a
如果a 为nil,就返回b
如果b 不是可选项,返回a 时会自动解包
多个 ?? 一起使用
?? 跟 if let 配合使用
let a: Int? = nil
let b: Int? = 2
if let c = a ?? b {
print©
}// 类似于 if a!=nil || b!=nil
if let c = a, let d = b {
print©
print(d)
}// 类似于 if a!=nil && b!=nil
guard语句
guard 条件 else {
退出当前作用域
}
当guard语句的条件为false时,就会执行大括号里面的代码
当guard语句的条件为true时,就会跳过guard语句
Guard语句特别适合用来”提前退出“
当使用guard语句进行可选项绑定时,绑定的常量(let)、变量(var)也能在外层作用域中使用
隐式解包(Implicitly Unwrapped Optional)
在某些情况下,可选项一旦被设定值之后,就会一直拥有值
在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它能确定每次访问的时候都有值。
可以在类型后面加个感叹号 !定义一个隐式解包的可选项。
字符串插值
可选项在字符串插插值或者直接打印,编译器会发出警告
多重可选项
var num1: Int? = 10
var num2: Int?? = num1
var num3: Int?? = 10
可以使用lldb指令 frame variable -R 或者 fr v -R 查看区别
关于感叹号( ! ),若在对象后面加感叹号则表示其所对应的变量或常量一定有值。
/—/
/– 课时2)流程控制、函数、汇编分析内联优化 –/
流程控制
if-else
if后面的条件可以省略小括号,但条件后面的大括号不可以省略。
if后面的条件只能是Bool类型
while 和 repeat-while(相当于C语言中的do-while)
从Swift3开始,去除了自增(++)、自减(–)运算符
for
闭区间运算符:a…b,a <= 取值 <= b
例子:
for i in 0…3 {
print(i)
} // 0 1 2 3
半开区间运算符: a…<b,a <= 取值 < b
例子:
for i in 1…<5 {
print(i)
} // 1 2 3 4
for - 区间运算符用在数组上
let names = [“Anna”, “Alex”, “Brian”, “Jack”]
for name in names[0…3] {
print(name)
} // Anna Alex Brian Jack
单侧区间:让区间朝一个方向尽可能的远
names[2…] 或者 names[…2] 或者 names[…<2]
switch
在其case、default后面不能写大括号{}
默认可以不写break,并不会贯穿到后面的条件
但是,使用fallthrough可以实现贯穿效果
switch必须要保证能处理所有情况
case、default后面至少要有一条语句
如果不想做任何事,加个break即可
如果能保证已处理所有情况,也可以不必使用default
复合条件,switch也支持Character、String类型
区间匹配、元组匹配
值绑定、where、标签语句(需要在嵌套的for循环里面控制外面的循环加个标签即可)
函数的定义:
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”)
}
隐式返回:
如果整个函数体是一个单一表达式,那么函数会隐式返回这个表达式
func sum(v1: Int, v2: Int) -> Int {
v1 + v2
}
返回元组:实现多返回值
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
函数的文档注释(好麻烦的,做个了解就好)
参数标签
可以修改参数标签
func goToWork(at time: String) {
print(“this time is (time)”)
}
goToWork(at: “08:00”)
// this time is 08:00
可以使用下划线_ 省略参数标签
func sum(_ v1: Int, _ v2: Int) -> Int {
v1 + v2
}
sum(10, 20)
默认参数值(Default Parameter Value)
func check(name: String = “nobody”, age: Int, job: String = “none”) {
print(“name=(name), age=(age), job=(job)”)
}
check(name: “Jack”, age: 20, job: “Doctor”)
check(name: “Rose”, age: 18)
check(age: 10, job: “Batman”)
check(age: 15)
C++的默认参数值有个限制:必须从右往左设置。由于Swift拥有参数标签,因此并没有此类限制。
但是在省略参数标签时,需要特别注意,避免出错。
可变参数(Variadic Parameter)
func sum(_ numbers: Int…) -> Int {
var total = 0
for number in numbers {
total += number
}
return total
}
sum(10, 20, 30, 40) // 100
一个函数最多只能有1个可变参数
紧跟在可变参数后面的参数不能省略参数标签
Swift自带的print函数
输入输出参数(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)
可变参数不能标记为inout
inout参数不能有默认值
inout参数的本质是地址传递(引用传递)
inout参数只能传入可以被多次赋值的
函数重载(Function Overload)
规则:
函数相同
参数个数不同 || 参数类型不同 || 参数标签不同
注意点:
返回值类型与函数重载无关
默认参数值和函数重载一起使用产生二义性时,编译器并不会报错(在C++中会报错)
可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器有可能会报错
内联函数(Inline Function)
如果开启了编译器优化(Release模式默认会开启优化),编译器会自动将某些函数编程内联函数
其作用是将函数调用展开成函数体
哪些函数不会被内联?
函数体比较长
包含递归调用
包含动态派发
函数类型(Function Type)
每一个函数都是有类型的,函数类型由形式参数类型、返回值类型组成
函数类型作为函数参数
函数类型作为函数返回值
返回值是函数类型的函数,叫做高阶函数(Higher-Order Function)
typealias(用来给类型起别名)
typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64
嵌套函数(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)
forward(false)(3)
/—/
/– 课时1)基础语法、汇编初探 –/
swiftc是Swift的编译指令;
swiftc存放在Xcode内部;
一些操作
+生成语法树: swiftc -dump-ast main.swift
+生成最简洁的SIL代码: swiftc -emit-sil main.swift
+生成LLVM IR代码: swiftc -emit-ir main.swift -0 main.ll
*生成汇编代码: swiftc -emit-assembly main.swift -o main.s
能对汇编代码进行分析,可以真正掌握编程语言的本质。
支持单行、多行、嵌套等注释方式
还支持markup(与markdown相似)(注释后面紧跟一个冒号开始markup)
开始markup渲染效果:Editor -> Show Rendered Markup
注意:Markup只在Playground中有效
常量
只能赋值1次
它的值不要求在编译时期确定,但使用之前必须赋值1次
常量可以在一开始不赋值,但是需要在一开始时定义其类型
标识符
标识符(比如常量名、变量名、函数名)几乎可以使用任何字符
标识符不能以数字开头,不能包含空白字符、制表支付、箭头等特殊字符
常见数据类型
值类型(value type)
|-- {枚举(enum),Optional}
|-- {结构体(struct)Bool、Int、Float、Double、Character;
String、Array、Dictionary、Set}
引用类型(reference type)
|-- {类(class}
整数
let intDecimal = 17 // 十进制
let intBinary = 0b10001 // 二进制
let intOctal = 0o21 // 八进制
Let intHexadecimal = 0x11 // 十六进制
/—/
/– 目录 –/
目录:从入门到精通Swift编程
├─01-基础语法、汇编初探
│ └─1-1-【回放】基础语法、汇编初探.mp4
│
├─02-流程控制、函数、汇编分析内联优化
│ └─2-1-【回放】流程控制、函数、汇编分析内联优化.mp4
│
├─03-枚举、可选项
│ └─3-1-【回放】枚举、可选项.mp4
│
├─04-汇编分析枚举的内存布局
│ └─4-1-【回放】汇编分析枚举的内存布局.mp4
│
├─05-汇编分析结构体、类的内存布局
│ └─5-1-【回放】汇编分析结构体、类的内存布局.mp4
│
├─06-汇编分析闭包本质01
│ └─6-1-【回放】汇编分析闭包本质01.mp4
│
├─07-汇编分析闭包本质02
│ └─7-1-【回放】汇编分析闭包本质02 .mp4
│
├─08-属性、汇编分析inout本质
│ └─8-1-【回放】属性、汇编分析inout本质 .mp4
│
├─09-汇编分析类型属性、方法、下标、继承
│ └─9-1-【回放】汇编分析类型属性、方法、下标、继承 .mp4
│
├─10-汇编分析多态原理、init
│ └─10-1-【回放】汇编分析多态原理、初始化、可选链 .mp4
│
├─11-init、deinit、可选链、协议、元类型
│ └─11-01-【回放】init、deinit、可选链、协议、元类型 .mp4
│
├─12-Error处理、泛型
│ └─12-01-【回放】Error处理、泛型 .mp4
│
├─13-汇编分析String、Array底层
│ └─13-01-【回放】汇编分析String、Array底层 .mp4
│
├─14-可选项的本质、运算符重载、扩展
│ └─14-01-【回放】可选项的本质、运算符重载、扩展 .mp4
│
├─15-访问控制、内存管理
│ └─15-01-【回放】访问控制、内存管理.mp4
│
├─16-内存访问冲突、指针
│ └─16-01-【回放】内存访问冲突、指针 .mp4
│
├─17-字面量协议、模式匹配、条件编译
│ └─17-01-【回放】字面量协议、模式匹配、条件编译 .mp4
│
├─18-从OC到Swift
│ └─18-01-【回放】从OC到Swift.mp4
│
├─19-从OC到Swift、函数式编程
│ └─19-01-【回放】从OC到Swift、函数式编程 .mp4│
│
├─20- 函数式编程、面向协议编程
│ └─20-01-【回放】 函数式编程、面向协议编程 .mp4
│
├─21-面向协议编程、响应式编程
│ └─21-01-【回放】面向协议编程、响应式编程.mp4
│
├─22-标准库源码分析、项目实战
│ └─22-01-【回放】标准库源码分析、项目实战.mp4
│
└─22. 标准库源码分析、项目实战
└─22. 标准库源码分析、项目实战_batch.mp4
/—/