一、概述
本系列文章旨在复习Swift5
核心语法且适当进行底层原理探索,属于阶段性复习和巩固,以供日后进一步探索Swift
语言的底层原理做铺垫。
整个系列文章如下,每一文章知识点独立成篇,欢迎各位按需或按兴趣点击阅读:
- 01-📝Swift5常用核心语法|了解Swift【Swift简介、Swift的版本、Swift编译原理】
- 02-📝Swift5常用核心语法|基础语法【Playground、常量与变量、常见数据类型、字面量、元组、流程控制、函数、枚举、可选项、guard语句、区间】
- 03-📝Swift5常用核心语法|面向对象【闭包、结构体、类、枚举】
- 04-📝Swift5常用核心语法|面向对象【属性、inout、类型属性、单例模式、方法、下标、继承、初始化】
- 05-📝Swift5常用核心语法|高级语法【可选链、协议、错误处理、泛型、String与Array、高级运算符、扩展、访问控制、内存管理、字面量、模式匹配】
- 06-📝Swift5常用核心语法|编程范式与Swift源码【从OC到Swift、函数式编程、面向协议编程、响应式编程、Swift源码分析】
二、从OC到Swift
1. 标记
我们可以通过一些注释标记来特殊标明注释的含义
// MARK:
类似OC
中的#pragma mark
// MARK: -
类似OC
中的#pragma mark -
// TODO:
用于标记未完成的任务// FIXME:
用于标记待修复的问题
使用示例如下
我们还可以使用#warning
来作为警告的提示,效果更为显著
2. 条件编译
我们还可以在Build Settings-> Swift Compiler -Custom Flags
自定义标记
在Other Swift Flags
里自定义的标记要以-D
开头
3. 打印
我们可以自定义打印的内容,便于开发中的详情观察
/*
* msg: 打印的内容
* file: 文件名
* line: 所在行数
* fn: 执行的函数名
*/
func log<T>(_ msg: T, file: NSString = #file, line: Int = #line, fn: String = #function) {
#if DEBUG
let prefix = "(file.lastPathComponent)_(line)_(fn):"
print(prefix, msg)
#endif
}
func test() {
log("哈哈")
}
// 输出:
// main.swift_66_test(): 哈哈
4. 系统的版本检测
if #available(iOS 10, macOS 10.12, *) {
// 对于iOS平台,只在iOS10及以上版本执行
// 对于macOS平台,只在macOS 10.12以上版本执行
// 最后的*表示在其他所有平台都执行
}
5. API可用性说明
@available(iOS 10, macOS 10.12, *)
class Person {}
struct Student {
// 旧的方法名更改,使用者用到时就会报错
@available(*, unavailable, renamed: "study")
func study_() {}
func study() {}
// 表示该方法在这个平台已经过期
@available(iOS, deprecated: 11)
@available(macOS, deprecated: 10.12)
func run() {}
}
更多用法参考:docs.swift.org/swift-book/…
6. 程序入口
在AppDelegate
上面默认有个@main
标记,这表示编译器自动生成入口代码(main函数代码),自动设置AppDelegate
为APP的代理
之前的Xcode
版本会生成@UIApplicationMain
标记,和@main
的作用一样
也可以删掉@main
或者@UIApplicationMain
,自定义入口代码
1.创建main.swift
文件
2.去掉AppDelegate
里的标记
3.在main.swift
里面自定义UIApplication
并增加入口代码
7. Swift调用OC
如果我们在Swift项目中需要调用到OC的代码,需要建立一个桥接头文件,文件名格式为{targetName}-Bridging-Header.h
在桥接文件里引用需要的OC头文件
在Build Setting -> Swift Compiler - General
中写好桥接文件路径
如果我们是在Swift项目里第一次创建OC文件,Xcode
会提示是否需要帮助创建桥接文件
然后我们就可以在Swift文件里调用OC的代码了
如果C语言暴露给Swift的函数名和Swift中的其他函数名冲突了,可以在Swift中使用@_silgen_name
修改C语言的函数名
// C文件
int sum(int a, int b) {
return a + b;
}
// Swift文件
func sum(_ a: Int, _ b: Int) -> Int {
a - b
}
@_silgen_name("sum")
func swift_sum(_ a: Int32, _ b: Int32) -> Int32
print(sum(5, 6))
print(swift_sum(5, 6))
@_silgen_name
还可以用来调用C的私有函数
8. OC调用Swift
我们要是想在OC文件中调用Swift代码,需要引用一个隐藏文件{targetName}-Swift.h
Swift暴露给OC的类最终都要继承自NSObject
使用@objc
修饰需要暴露给OC的成员
使用@objcMembers
修饰类,代表默认所有成员都会暴露给OC(包括扩展中定义的成员)
最终是否成功暴露,还需要考虑成员自身的权限问题
我们进入到test-Swift.h
里看看编译器默认帮我们转成的OC代码是怎样的
我们还可以通过@objc
来对Swift文件里的类和成员重命名,来更适应于OC的代码规范
9. 选择器
Swift中依然可以使用选择器,使用#selector(name)
定义一个选择器
必须是被@objcMembers
或@objc
修饰的方法才可以定义选择器
如果不加@objcMembers
或@objc
是会报错的
10. 混编调用的本质
我们先来思考一个问题,为什么Swift暴露给OC的类最终要继承NSObject?
只有OC调用最后还是走的消息发送机制,要想能够实现消息机制,就需要有isa指针
,所以要继承NSObject
我们在调用的地方打上断点,然后进行反汇编
我们发现,反汇编内部最终调用了objc_msgSend
,很明显是消息发送机制
那Swift调用OC的方法,是走的消息发送机制,还是Swift本身的调用方式呢?
我们在调用的地方打上断点,然后进行反汇编
我们发现,反汇编内部最终调用了objc_msgSend
,很明显是消息发送机制
暴露给OC使用的Swift函数和类,如果被Swift调用,是走的消息发送机制,还是Swift本身的调用方式呢?
我们在调用的地方打上断点,然后进行反汇编
我们发现,反汇编内部是按照根据元类信息里的函数地址去调用的方式,没有Runtime
相关的调用
我们可以加上dynamic
关键字,这样不管是OC调用还是Swift调用都会走Runtime
的消息发送机制
反汇编之后
11. String
Swift的字符串类型String
,和OC的NSString
,在API设计上还是有较大差异的
// 空字符串
var emptyStr1 = ""
var emptyStr2 = String()
// 拼接字符串
var str: String = "1"
str.append("_2")
// 重载运算符
str = str + "_3"
str += "_4"
// 插值
str = "(str)_5"
print(str, str.count) // 1_2_3_4_5, 9
// 字符串的判断
var str = "123456"
print(str.hasPrefix("123")) // true
print(str.hasSuffix("456")) // true
11.1 String的插入和删除
var str = "1_2"
str.insert("_", at: str.endIndex) // 1_2_
str.insert(contentsOf: "3_4", at: str.endIndex) // 1_2_3_4
str.insert(contentsOf: "666", at: str.index(after: str.startIndex)) // 1666_2_3_4
str.insert(contentsOf: "888", at: str.index(before: str.endIndex)) // 1666_2_3_8884
str.insert(contentsOf: "hello", at: str.index(str.startIndex, offsetBy: 4)) // 1666hello_2_3_8884
str.remove(at: str.firstIndex(of: "1")!) // 666hello_2_3_8884
str.removeAll { $0 == "6" } // hello_2_3_8884
let range = str.index(str.endIndex, offsetBy: -4)..<str.index(before: str.endIndex)
str.removeSubrange(range) // hello_2_3_4
11.2 Substring
String
可以通过下标、prefix、suffix
等截取子串,子串类型不是String
,而是Substring
var str = "1_2_3_4_5"
var substr1 = str.prefix(3) // 1_2
var substr2 = str.suffix(3) // 4_5
var range = str.startIndex..<str.index(str.startIndex, offsetBy: 3)
var substr3 = str[range] // 1_2
// 最初的String
print(substr3.base) // 1_2_3_4_5
// Substring -> String
var str2 = String(substr3)
Substring
和它的base
,共享字符串数据- 其本质是
Substring
内部有一个指针指向String
对应的区域 Substring
发生修改或者转为String
时,会分配新的内存存储字符串数据,不会影响到最初的String
的内容,编译器会自动做优化
11.3 String与Character
for c in "jack" { // c是Character类型
print(c)
}
var str = "jack"
var c = str[str.startIndex] // c是Character类型
11.4 String相关的协议
-
BidirectionalCollection
协议包含的部分内容startIndex
、endIndex
属性、index
方法String
、Array
都遵守了这个协议
-
RangeReplaceableCollection
协议包含的部分内容append
、insert
、remove
方法String
、Array
都遵守了这个协议
-
Dictionary
、Set
也有实现上述协议中声明的一些方法,只是并没有遵守上述协议
11.5 多行String
let str = """
1
”2“
3
'4'
"""
如果要显示3引号,至少转义1个引号
let str = """
Escaping the first quote """
Escaping two quotes """
Escaping all three quotes """
"""
以下两个字符是等价的
let str1 = "These are the same."
let str2 = """
These are the same.
"""
缩进以结尾的3引号为对齐线
let str = """
1
2
3
4
"""
11.6 String和NSString
String
和NSString
之间可以随时随地的桥接转换- 如果你觉得
String
的API过于复杂难用,可以考虑将String
转为NSString
var str1: String = "jack"
var str2: NSString = "rose"
var str3 = str1 as NSString
var str4 = str2 as String
var str5 = str3.substring(with: NSRange(location: 0, length: 2))
print(str5) // ja
我们通过反汇编发现,String
和NSString
的转换会调用函数来实现的,相对会有性能的消耗,但由于编译器的优化,消耗的成本可以忽略不计
比较字符串内容是否等价
String
使用==
运算符NSString
使用isEqual
方法,也可以使用==
运算符(本质还是调用了isEqual
方法)
var str1: NSString = "jack"
var str2: String = "rose"
var str5: String = "rose"
var str6: NSString = "jack"
print(str2 == str5)
print(str1 == str6)
通过反汇编,我们可以看到==
运算符的本质还是调用了isEqual
方法
下面是Swift和OC的几个类型的转换表格
String
和NSString
可以相互转换,而NSMutableString
就只能单向转换成String
其他类型同理
12. 只能被class继承的协议
- 如果协议对应
AnyObject、class、@objc
来修饰,那么只能被类所遵守 - 被
@objc
修饰的协议,还可以暴露给OC去遵守协议实现
// Swift文件
@objc protocol Runnable4 {
func run()
}
// OC文件
@interface LLTest : NSObject<Runnable4>
@end
@implementation LLTest
- (void)run { }
@end
可以通过@objc
定义可选协议,这种协议只能被class
遵守
@objc protocol Runnable4 {
func run()
@objc optional func eat()
}
class Person: Runnable4 {
func run() {
print("run")
}
}
13. dynamic
被@objc dynamic
修饰的内容会具有动态性,比如调用方法会走Runtime
的消息发送机制
class Dog {
@objc dynamic func test1() {}
func test2() {}
}
var d = Dog()
d.test1()
d.test2()
具体汇报调用过程可以参考上文混编调用的本质
14. KVC、KVO
-
- Swift支持
KVC、KVO
的条件需要属性所在的类、监听器最终继承自NSObject
- Swift支持
-
- 用
@objc dynamic
修饰对应的属性
- 用
class Observer: NSObject {
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("observeValue", change?[.newKey] as Any)
}
}
class Person: NSObject {
@objc dynamic var age: Int = 0
var observer: Observer = Observer()
override init() {
super.init()
addObserver(observer, forKeyPath: "age", options: .new, context: nil)
}
deinit {
removeObserver(observer, forKeyPath: "age")
}
}
var p = Person()
p.age = 20
p.setValue(25, forKey: "age")
// Optional(20)
// Optional(25)
-
block
方式的`KVO
class Person: NSObject {
@objc dynamic var age: Int = 0
var observation: NSKeyValueObservation?
override init() {
super.init()
observation = observe(\Person.age, options: .new, changeHandler: { (person, change) in
print(change.newValue as Any)
})
}
}
var p = Person()
p.age = 20
p.setValue(25, forKey: "age")
// Optional(20)
// Optional(25)
15. 关联对象(Associated Object)
- 在Swift中,
class
依然可以使用关联对象 - 默认情况下,
extension
不可以增加存储属性 - 借助关联对象,可以实现类似
extension
为class
增加存储属性的效果
class Person {}
extension Person {
// Void类型只占一个字节
private static var AGE_KEY: Void?
var age: Int {
get {
(objc_getAssociatedObject(self, &Self.AGE_KEY) as? Int) ?? 0
}
set {
objc_setAssociatedObject(self, &Self.AGE_KEY, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
}
}
var p = Person()
print(p.age) // 0
p.age = 10
print(p.age) // 10
16. 资源名管理
我们日常在代码中对资源的使用如下
let img = UIImage(named: "logo")
let btn = UIButton(type: .custom)
btn.setTitle("添加", for: .normal)
performSegue(withIdentifier: "login_main", sender: self)
- 我们采用
枚举
的方式对资源名进行管理 - 这种方式是参考了
Android
的资源名管理方式
enum R {
enum string: String {
case add = "添加"
}
enum image: String {
case logo
}
enum segue: String {
case login_main
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let img = UIImage(named: R.image.logo)
let btn = UIButton(type: .custom)
btn.setTitle(R.string.add, for: .normal)
performSegue(withIdentifier: R.segue.login_main, sender: self)
}
}
extension UIImage {
convenience init?(named name: R.image) {
self.init(named: name.rawValue)
}
}
extension UIViewController {
func performSegue(withIdentifier identifier: R.segue, sender: Any?) {
performSegue(withIdentifier: identifier.rawValue, sender: sender)
}
}
extension UIButton {
func setTitle(_ title: R.string, for state: UIControl.State) {
setTitle(title.rawValue, for: state)
}
}
资源名管理的其他思路
原始写法如下
let img = UIImage(named: "logo")
let font = UIFont(name: "Arial", size: 14)
enum R {
enum image {
static var logo = UIImage(named: "logo")
}
enum font {
static func arial(_ size: CGFloat) -> UIFont? {
UIFont(name: "Arial", size: size)
}
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let img = R.image.logo
let font = R.font.arial(14)
}
}
更多优秀的思路请参考以下链接:
17. 多线程开发
- 利用
DispatchWorkItem
封装常用多线程执行函数
public typealias Task = () -> Void
public struct Asyncs {
/// 异步执行
public static func async(_ task: @escaping Task) {
_async(task)
}
public static func async(_ task: @escaping Task,
_ mainTask: @escaping Task) {
_async(task, mainTask)
}
/// 主线程延迟执行
@discardableResult
public static func delay(_ seconds: Double,
_ block: @escaping Task) -> DispatchWorkItem {
let item = DispatchWorkItem(block: block)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + seconds, execute: item)
return item
}
/// 异步延迟执行
@discardableResult
public static func asyncDelay(_ seconds: Double,
_ task: @escaping Task) -> DispatchWorkItem {
_asyncDelay(seconds, task)
}
@discardableResult
public static func asyncDelay(_ seconds: Double,
_ task: @escaping Task,
_ mainTask: @escaping Task) -> DispatchWorkItem {
_asyncDelay(seconds, task, mainTask)
}
}
// MARK: - 私有API
extension Asyncs {
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)
}
}
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
}
}
dispatch_once
在Swift中已被废弃,取而代之的是用类型属性
或者全局变量\常量
- 默认自带
lazy+dispatch_once
效果
fileprivate let initTask2: Void = {
print("initTask2")
}()
class ViewController: UIViewController {
static let initTask1: Void = {
print("initTask1------------")
}()
override func viewDidLoad() {
super.viewDidLoad()
let _ = Self.initTask1
let _ = initTask2
}
}
- 多个线程操作同一份数据会有资源抢夺问题,需要进行加锁
class Cache {
private static var data = [String : Any]()
private static var lock = DispatchSemaphore(value: 1)
static func set(_ key: String, _ value: Any) {
lock.wait()
defer { lock.signal() }
data[key] = value
}
}
private static var lock = NSLock()
static func set(_ key: String, _ value: Any) {
lock.lock()
defer {
lock.unlock()
}
}
private static var lock = NSRecursiveLock()
static func set(_ key: String, _ value: Any) {
lock.lock()
defer {
lock.unlock()
}
}
三、函数式编程(Funtional Programming)
1. 基本概念
函数式编程(Funtional Programming,简称FP)是一种编程范式,也就是如何编写程序的方法论
- 主要思想:把计算过程尽量分解成一系列可复用函数的调用
- 主要特征:”函数的第一等公民“,函数与其他数据类型一样的地位,可以赋值给其他变量,也可以作为函数参数、函数返回值
函数式编程最早出现在LISP
语言,绝大部分的现代编程语言也对函数式编程做了不同程度的支持,比如Haskell、JavaScript、Swift、Python、Kotlin、Scala
等
函数式编程中几个常用概念
- Higher-Order Function、Function Currying
- Functor、Applicative Functor、Monad
参考资料:
2. Array的常见操作
var array = [1, 2, 3, 4]
// map:遍历数组,可以将每个元素对应做调整变成新的元素,放入新的数组中
var array2 = array.map { $0 * 2 } // [2, 4, 6, 8]
// filter:遍历数组,选出符合条件的元素放入新的数组中
var array3 = array.filter { $0 % 2 == 0 }
// reduce:首先设定一个初始值(0)
// $0:上一次遍历返回的结果(0,1,3,10)
//$1:每次遍历到的数组元素(1,2,3,4)
var array4 = array.reduce(0) { $0 + $1 } // 10
var array5 = array.reduce(0, +) // 同array4一样
var array = [1, 2, 3, 4]
func double(_ i: Int) -> Int { i * 2 }
print(array.map(double)) // [2, 4, 6, 8]
print(array.map { double($0) }) // [2, 4, 6, 8]
-
map
和flatMap、compactMap
的区别
var arr = [1, 2, 3] var arr2 = arr.map { Array(repeating: $0, count: $0) } // [[1], [2, 2], [3, 3, 3]] // flatMap会将处理完的新元素都放在同一个数组中 var arr3 = arr.flatMap { Array(repeating: $0, count: $0) } // [1, 2, 2, 3, 3, 3]
var arr = ["123", "test", "jack", "-30"] var arr1 = arr.map { Int($0) } // [Optional(123), nil, nil, Optional(-30)] var arr2 = arr.compactMap { Int($0) } // [123, -30] var arr3 = arr.flatMap(Int.init)
-
- 使用
reduce
分别实现map、filter
功能
var arr = [1, 2, 3, 4] // map var arr1 = arr.map { $0 * 2 } print(arr1) var arr2 = arr.reduce([]) { $0 + [$1 * 2] } print(arr1) // filter var arr3 = arr.filter { $0 % 2 == 0 } print(arr3) var arr4 = arr.reduce([]) { $1 % 2 == 0 ? $0 + [$1] : $0 } print(arr4)
- 使用
-
lazy
的优化
let arr = [1, 2, 3] let result = arr.lazy.map { (i: Int) -> Int in print("mapping (i)") return i * 2 } print("begin-----") print("mapped", result[0]) print("mapped", result[1]) print("mapped", result[2]) print("end-----") //begin----- //mapping 1 //mapped 2 //mapping 2 //mapped 4 //mapping 3 //mapped 6 //end-----
-
Optional
的map
和flatMap
会先将可选类型解包,处理完会再进行包装返回出去
var num1: Int? = 10 var num2 = num1.map { $0 * 2 } // Optional(20) var num3: Int? = nil var num4 = num3.map { $0 * 2 } // nil
var num1: Int? = 10 var num2 = num1.map { Optional.some($0 * 2) } // Optional(Optional(20)) //flatMap发现其为可选项,不会再进行包装 var num3 = num1.flatMap { Optional.some($0 * 2) } // Optional(20) var num4 = num1.flatMap { $0 * 2 } // Optional(20)
var num1: Int? = 10 var num2 = (num1 != nil) ? (num1! + 10) : nil // Optional(20) var num3 = num1.map { $0 + 10 } // Optional(20)
var fmt = DateFormatter() fmt.dateFormat = "yyyy-MM-dd" var str: String? = "2011-09-10" var date1 = str != nil ? fmt.date(from: str!) : nil // Optional(2011-09-09 16:00:00 +0000) var date2 = str.flatMap(fmt.date) // Optional(2011-09-09 16:00:00 +0000)
var score: Int? = 98 var str1 = score != nil ? "score is (score!)" : "No score" // score is 98 var str2 = score.map { "score is ($0)"} ?? "No score" // score is 98
struct Person { var name: String var age: Int } var items = [ Person(name: "jack", age: 20), Person(name: "rose", age: 21), Person(name: "kate", age: 22) ] func getPerson1(_ name: String) -> Person? { // 遍历数组找到对应的索引 let index = items.firstIndex { $0.name == name } return index != nil ? items[index!] : nil } func getPerson2(_ name: String) -> Person? { items.firstIndex { $0.name == name } .map { items[$0] } } let p1 = getPerson1("rose") let p2 = getPerson2("rose")
struct Person { var name: String var age: Int init?(_ json: [String : Any]) { guard let name = json["name"] as? String, let age = json["age"] as? Int else { return nil } self.name = name self.age = age } } var json: Dictionary? = ["name" : "Jack", "age" : 10] var p1 = json != nil ? Person(json!) : nil // Optional(__lldb_expr_36.Person(name: "Jack", age: 10)) var p2 = json.flatMap(Person.init) // Optional(__lldb_expr_36.Person(name: "Jack", age: 10))
3. 函数式的写法
- 假如要实现以下功能:
[(num + 3) * 5 - 1] % 10 / 2
- 传统写法
var 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)
- 函数式写法
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)
4. 高阶函数(Higher-Order Function)
高阶函数至少满足下列一个条件的函数:
- 接受一个或多个函数作为输入(map、filter、reduce等)
- 返回一个函数
FP中到处都是高阶函数
5. 柯里化(Currying)
- 什么是柯里化?
- 将一个接受多参数的函数变换为一系列只接受单个参数的函数
Array、Optional
的map
方法接收的参数就是一个柯里化函数
演变示例
func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
add(2 + 4)
变为函数式的写法:
func add(_ v2: Int) -> (Int) -> Int {
return { v1 in
return v1 + v2
}
}
add(4)(2)
再精简:
func add(_ v2: Int) -> (Int) -> Int {{ v1 in v1 + v2 }}
add(4)(2)
再精简:
func add(_ v: Int) -> (Int) -> Int {{ $0 + v }}
add(4)(2)
柯里化:
func currying<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) -> (A) -> C {{ b in { a in fn(a, b) }}}
let curriedAdd = currying(add)
print(curriedAdd(4)(2))
func add(_ v1: Int, _ v2: Int, _ v3: Int) -> Int { v1 + v2 + v3 }
add(2, 3, 5)
变为函数式的写法:
func add(_ v3: Int) -> (Int) -> (Int) -> Int {
// v2是3
return { v2 in
// v1是2
return { v1 in
return v1 + v2 + v3
}
}
}
add(5)(3)(2)
再精简:
func add(_ v3: Int) -> (Int) -> (Int) -> Int {{ v2 in { v1 in v1 + v2 + v3 }}}
add(5)(3)(2)
柯里化:
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 curriedAdd = currying(add)
print(curriedAdd(10)(20)(30))
一开始的示例就可以都保留旧的方法,然后通过柯里化来调用
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 }
prefix func ~<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) -> (A) -> C {{ b in { a in fn(a, b) }}}
infix operator >>> : AdditionPrecedence
func >>><A, B, C>(_ f1: @escaping (A) -> B,
_ f2: @escaping (B) -> C) -> (A) -> C {{ f2(f1($0)) }}
var num = 1
var fn = (~add)(3) >>> (~multiple)(5) >>> (~sub)(1) >>> (~mod)(10) >>> (~divide)(2)
fn(num)
6. 函子(Functor)
- 像
Array、Optional
这样支持map运算
的类型,称为函子(Functor)
下图充分解释了函子
7. 适用函子(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 }
print(fn <*> value as Any) // Optional(20)
Array
可以成为适用函子
func pure<A>(_ value: A) -> [A] { [value] }
infix operator <*> : AdditionPrecedence
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)) // [10]
var arr = [{ $0 * 2 }, { $0 + 10 }, { $0 - 5 }] <*> [1, 2 , 3]
print(arr) // [2, 12, -2]
8. 单子(Monad)
对任意一共类型F
,如果能支持以下运算,那么就可以称为是一个单子
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)
1. 基本概念
面向协议编程(Protocol Oriented Programming,简称POP)
- 是Swift的一种编程范式,
Apple
于2015年WWDC
提出 - 在Swift标准库中,能见到大量
POP
的影子 同时,Swift也是一门面向对象的编程语言(Object Oriented Programming,简称OOP)
在Swift开发中,OOP
和POP
是相辅相成的,任何一方并不能取代另一方
POP
能弥补OOP
一些设计上的不足
2. OOP和POP
回顾OOP
OOP
的三大特性:封装
、继承、多态
- 继承的经典使用场合:
当多个类(比如A、B、C类)具有很大共性时,可以将这些共性抽取到一个父类中(比如D类),最后A、B、C类继承D类
- OOP的不足
- 但有些问题,使用
OOP
并不能很好解决,比如如何将BVC、DVC的公共方法run
抽出来
class BVC: UIViewController {
func run() {
print("run")
}
}
class DVC: UITableViewController {
func run() {
print("run")
}
}
- 基于
OOP
想到的一些解决方案:
1.将run方法
放到另一个对象A中,然后BVC、DVC拥有对象A属性
- 多了一些额外的依赖关系
2.将run方法
增加到UIViewController分类
中
UIViewController
会越来越臃肿,而且会影响它的其他所有子类
3.将run方法
抽取到新的父类,采用多继承(C++
支持多继承)
- 会增加程序设计的复杂度,产生菱形继承等问题,需要开发者额外解决
POP的解决方案
protocol Runnable {
func run()
}
extension Runnable {
func run() {
print("run")
}
}
class BVC: UIViewController, Runnable {}
class DVC: UITableViewController, Runnable {}
POP的注意点
- 优先考虑创建协议,而不是父类(基类)
- 优先考虑值类型(struct、enum),而不是引用类型(class)
- 巧用协议的扩展功能
- 不要为了面向协议而使用协议
3. POP的应用
下面我们利用协议来实现前缀效果
var string = "123fdsf434"
protocol NameSpaceWrapperProtocol {
associatedtype WrappedType
var wrappedValue: WrappedType { get set }
init(value: WrappedType)
}
struct NameSpaceWrapper<T>: NameSpaceWrapperProtocol {
var wrappedValue: T
init(value: T) {
self.wrappedValue = value
}
}
protocol NamespaceWrappable { }
extension NamespaceWrappable {
var ll: NameSpaceWrapper<Self> {
get { NameSpaceWrapper(value: self) }
set {}
}
static var ll: NameSpaceWrapper<Self>.Type {
get { NameSpaceWrapper.self }
set {}
}
}
extension NameSpaceWrapperProtocol where WrappedType: ExpressibleByStringLiteral {
var numberCount: Int {
(wrappedValue as? String)?.filter { ("0"..."9").contains($0) }.count ?? 0
}
}
extension String: NamespaceWrappable {}
extension NSString: NamespaceWrappable {}
print(string.ll.numberCount)
print((string as NSString).ll.numberCount) // 6
利用协议实现类型判断
func isArray(_ value: Any) -> Bool { value is [Any] }
print(isArray([1, 2])) // true
print(isArray(["1", 2])) // true
print(isArray(NSArray())) // true
print(isArray(NSMutableArray())) // true
protocol ArrayType {}
func isArrayType(_ type: Any.Type) -> Bool { type is ArrayType.Type }
extension Array: ArrayType {}
extension NSArray: ArrayType {}
print(isArrayType([Int].self)) // true
print(isArrayType([Any].self)) // true
print(isArrayType(NSArray.self)) // true
print(isArrayType(NSMutableArray.self)) // true
print(isArrayType(String.self)) // false
五、响应式编程(Reactive Programming)
1. 基本概念
响应式编程(Reactive Programming,简称RP),是一种编程范式,于1997年提出,可以简化异步编程,提供更优雅的数据绑定
一般与函数式融合在一起,所以也会叫做:函数响应式编程(Functional Reactive Programming,简称FRP)
比较著名的、成熟的响应式框架
- ReactiveCocoa:简称RAC,有Objective-C、Swift版本
- ReactiveX:简称Rx,有众多编程语言版本,比如RxJava、RxKotlin、RxJS、RxCpp、RxPHP、RxGo、RxSwift等
2. RxSwift
RxSwift
(ReactiveX for Swift),ReactiveX
的Swift
版本
源码: github.com/ReactiveX/R… beeth0ven.github.io/RxSwift-Chi…
六、Swift源码分析
我们通过分析Swift标准库源码
来更近一步了解Swift的语法
1.Array相关
map、filter的源码路径:/swift-main/stdlib/public/core/Sequence.swift
flatMap、compactMap、reduce的源码路径:/swift-main/stdlib/public/core/SequenceAlgorithms.swift
1.1 map
@inlinable
public func map<T>(
_ transform: (Element) throws -> T
) rethrows -> [T] {
let initialCapacity = underestimatedCount
var result = ContiguousArray<T>()
result.reserveCapacity(initialCapacity)
var iterator = self.makeIterator()
// Add elements up to the initial capacity without checking for regrowth.
for _ in 0..<initialCapacity {
result.append(try transform(iterator.next()!))
}
// Add remaining elements, if any.
while let element = iterator.next() {
// 如果element是数组,会把整个数组作为元素加到新数组中
result.append(try transform(element))
}
return Array(result)
}
1.2 flatMap
@inlinable
public func flatMap<SegmentOfResult: Sequence>(
_ transform: (Element) throws -> SegmentOfResult
) rethrows -> [SegmentOfResult.Element] {
var result: [SegmentOfResult.Element] = []
for element in self {
// 将数组元素添加到新数组中
result.append(contentsOf: try transform(element))
}
return result
}
1.3 filter
@inlinable
public __consuming func filter(
_ isIncluded: (Element) throws -> Bool
) rethrows -> [Element] {
return try _filter(isIncluded)
}
@_transparent
public func _filter(
_ isIncluded: (Element) throws -> Bool
) rethrows -> [Element] {
var result = ContiguousArray<Element>()
var iterator = self.makeIterator()
while let element = iterator.next() {
if try isIncluded(element) {
result.append(element)
}
}
return Array(result)
}
1.4 compactMap
@inlinable // protocol-only
public func compactMap<ElementOfResult>(
_ transform: (Element) throws -> ElementOfResult?
) rethrows -> [ElementOfResult] {
return try _compactMap(transform)
}
@inlinable // protocol-only
@inline(__always)
public func _compactMap<ElementOfResult>(
_ transform: (Element) throws -> ElementOfResult?
) rethrows -> [ElementOfResult] {
var result: [ElementOfResult] = []
for element in self {
// 会进行解包,只有不为空才会被加到数组中
if let newElement = try transform(element) {
result.append(newElement)
}
}
return result
}
1.5 reduce
@inlinable
public func reduce<Result>(
_ initialResult: Result,
_ nextPartialResult:
(_ partialResult: Result, Element) throws -> Result
) rethrows -> Result {
// 上一次的结果
var accumulator = initialResult
for element in self {
accumulator = try nextPartialResult(accumulator, element)
}
return accumulator
}
2. Substring相关
Substring的源码路径:/swift-main/stdlib/public/core/Substring.swift
2.1初始化
@frozen
public struct Substring: ConcurrentValue {
@usableFromInline
internal var _slice: Slice<String>
@inlinable
internal init(_ slice: Slice<String>) {
let _guts = slice.base._guts
let start = _guts.scalarAlign(slice.startIndex)
let end = _guts.scalarAlign(slice.endIndex)
// 保存传进来的字符串的内容和位置
self._slice = Slice(
base: slice.base,
bounds: Range(_uncheckedBounds: (start, end)))
_invariantCheck()
}
@inline(__always)
internal init(_ slice: _StringGutsSlice) {
self.init(String(slice._guts)[slice.range])
}
/// Creates an empty substring.
@inlinable @inline(__always)
public init() {
self.init(Slice())
}
}
extension Substring {
/// Returns the underlying string from which this Substring was derived.
@_alwaysEmitIntoClient
// _slice.base就是初始化传进来的字符串
public var base: String { return _slice.base }
@inlinable @inline(__always)
internal var _wholeGuts: _StringGuts { return base._guts }
@inlinable @inline(__always)
// 从这里也能看出和传进来的String共有的是同一块区域,在这块区域进行偏移获取Substring的内容
internal var _offsetRange: Range<Int> {
return Range(
_uncheckedBounds: (startIndex._encodedOffset, endIndex._encodedOffset))
}
#if !INTERNAL_CHECKS_ENABLED
@inlinable @inline(__always) internal func _invariantCheck() {}
#else
@usableFromInline @inline(never) @_effects(releasenone)
internal func _invariantCheck() {
// Indices are always scalar aligned
_internalInvariant(
_slice.startIndex == base._guts.scalarAlign(_slice.startIndex) &&
_slice.endIndex == base._guts.scalarAlign(_slice.endIndex))
self.base._invariantCheck()
}
#endif // INTERNAL_CHECKS_ENABLED
}
2.2 append
extension Substring: RangeReplaceableCollection {
@_specialize(where S == String)
@_specialize(where S == Substring)
@_specialize(where S == Array<Character>)
public init<S: Sequence>(_ elements: S)
where S.Element == Character {
if let str = elements as? String {
self.init(str)
return
}
if let subStr = elements as? Substring {
self = subStr
return
}
self.init(String(elements))
}
// Substring的拼接
@inlinable // specialize
public mutating func append<S: Sequence>(contentsOf elements: S)
where S.Element == Character {
// 拼接时会创建一个新的字符串
var string = String(self)
self = Substring() // Keep unique storage if possible
string.append(contentsOf: elements)
self = Substring(string)
}
}
2.3 lowercased、uppercased
extension Substring {
public func lowercased() -> String {
return String(self).lowercased()
}
public func uppercased() -> String {
return String(self).uppercased()
}
public func filter(
_ isIncluded: (Element) throws -> Bool
) rethrows -> String {
return try String(self.lazy.filter(isIncluded))
}
}
3. Optional相关
Optional的源码路径:/swift-main/stdlib/public/core/Optional.swift
3.1 map
@inlinable
public func map<U>(
_ transform: (Wrapped) throws -> U
) rethrows -> U? {
switch self {
case .some(let y): // 先解包进行处理
return .some(try transform(y)) // 然后再包装一层可选类型返回出去
case .none:
return .none
}
}
3.2flatMap
@inlinable
public func flatMap<U>(
_ transform: (Wrapped) throws -> U?
) rethrows -> U? {
switch self {
case .some(let y): // 先进行解包
return try transform(y) // 将解包后的处理完直接给出去
case .none:
return .none
}
}
3.3 ==
==
两边都为可选项
@inlinable
public static func ==(lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l == r
case (nil, nil):
return true
default:
return false
}
}
==
左边为可选项
,右边为nil
@_transparent
public static func ==(lhs: Wrapped?, rhs: _OptionalNilComparisonType) -> Bool {
switch lhs {
case .some:
return false
case .none:
return true
}
}
==
左边为nil
,右边为可选项
@_transparent
public static func ==(lhs: _OptionalNilComparisonType, rhs: Wrapped?) -> Bool {
switch rhs {
case .some:
return false
case .none:
return true
}
}
_OptionalNilComparisonType
是一个遵守ExpressibleByNilLiteral
协议的结构体,可以用nil
来进行初始化
// 遵守ExpressibleByNilLiteral协议的结构体,可以用nil来进行初始化
@frozen
public struct _OptionalNilComparisonType: ExpressibleByNilLiteral {
/// Create an instance initialized with `nil`.
@_transparent
public init(nilLiteral: ()) {
}
}
3.4 ??
@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
rethrows -> T {
switch optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?)
rethrows -> T? {
switch optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
4. Metadata相关
4.1 了解Metadata
首先,我们引入Apple的Metadata文档截图,了解一下 Metadata 是什么:
- 从上面附图中,我们不难得出结论:
Swift 运行时 为程序中使用的每种数据类型(包括泛型类型的每个实例) 保留元数据(Metadata)记录
- 换句话说,Metadata就是用于保留类型信息
- (TODO:
反射
和)调试器工具
可以使用这些元数据记录来了解有关类型的信息- 应用:
字典转模型
、类的底层信息获取
、调试器调试类信息
等
- 应用:
- 对于
非泛型
标称类型,这些元数据记录由编译器静态生成 - 对于
泛型
类型的实例以及内部类型(例如元组、函数、协议组合等),元数据记录由运行时根据需要延迟创建 - 每种类型都有唯一的元数据记录;
- 两个元数据指针值相等,当且仅当类型相等
4.2 Metadata通用信息布局
所有元数据记录共享一个公共标头,其中包含以下字段:
- 虚函数表的地址值,提供基本操作,例如分配、复制和销毁
- 记录类型的大小、对齐方式、步幅和其他基本属性
- 记录指针位于距元数据指针的偏移量 -1 处,即紧邻指针所引用地址之前的指针大小的字。
kind 字段
是一个指针大小的整数- 描述元数据所描述的类型的种类
- 该字段位于元数据指针的偏移量 0 处
4.3 当前kind值与类型关系如下:
- 类元数据的类型为 0,除非该类需要与 Objective-C 互操作。
- 如果类需要与 Objective-C 互操作,则 kind 字段是指向 Objective-C 元类的 isa 指针。
- 这样的指针可以与枚举元数据类型区分开来,因为它保证具有大于 2047 的值。
- 请注意,这是比 @objc 属性含义更基本的互操作意义:
- 它是支持 Objective-C 所需的内容 消息发送和保留/释放。
- 在为 Apple 平台构建时,所有类都需要在此级别上与 Objective-C 进行互操作。
4.4 简单介绍Class Metadata
源码路径:
/swift-main/include/swift/ABI/Metadata.h
/swift-main/include/swift/ABI/MetadataKind.def
/swift-main/include/swift/ABI/MetadataValues.h
/swift-main/include/swift/Reflection/Records.h
文档路径:
/swift-main/docs/ABI/TypeMetadata.rst
在下面的布局描述中,相对于元数据指针的偏移量作为指针数组的索引给出。
- 在32位平台上,偏移1表示偏移4字节
- 在64位平台上,偏移1表示偏移8字节
Class Metadata
我们可以从第三方库KaKaJSON
中的ClassType
,以及对应Metadata
的相关文档来分析Class Metadata
信息
struct ClassLayout: ModelLayout {
let kind: UnsafeRawPointer
/// 指向父类类型的指针
let superclass: Any.Type
/// The cache data is used for certain dynamic lookups; it is owned by the runtime and generally needs to interoperate with Objective-C's use
/// 缓存数据用于某些动态查找;它属于运行时,通常需要与Objective-C的使用进行互操作
let runtimeReserved0: UInt
let runtimeReserved1: UInt
/// The data pointer is used for out-of-line metadata and is generally opaque, except that the compiler sets the low bit in order to indicate that this is a Swift metatype and therefore that the type metadata header is present
let rodata: UInt
/// Swift-specific class flags
/// 类标志
let flags: UInt32
/// The address point of instances of this type
/// 实例的地址值
let instanceAddressPoint: UInt32
/// The required size of instances of this type. 'InstanceAddressPoint' bytes go before the address point; 'InstanceSize - InstanceAddressPoint' bytes go after it
/// 实例大小
let instanceSize: UInt32
/// The alignment mask of the address point of instances of this type
/// 实例对齐掩码
let instanceAlignMask: UInt16
/// Reserved for runtime use
/// 运行时保留字段
let reserved: UInt16
/// The total size of the class object, including prefix and suffix extents
/// 类对象的大小
let classSize: UInt32
/// The offset of the address point within the class object
/// 类对象地址
let classAddressPoint: UInt32
// Description is by far the most likely field for a client to try to access directly, so we force access to go through accessors
/// An out-of-line Swift-specific description of the type, or null if this is an artificial subclass. We currently provide no supported mechanism for making a non-artificial subclass dynamically
var description: UnsafeMutablePointer<ClassDescriptor>
/// A function for destroying instance variables, used to clean up after an early return from a constructor. If null, no clean up will be performed and all ivars must be trivial
let iVarDestroyer: UnsafeRawPointer
var genericTypeOffset: Int {
let descriptor = description.pointee
// don't have resilient superclass
if (0x4000 & flags) == 0 {
return (flags & 0x800) == 0
? Int(descriptor.metadataPositiveSizeInWords - descriptor.numImmediateMembers)
: -Int(descriptor.metadataNegativeSizeInWords)
}
return GenenicTypeOffset.wrong
}
}
关于MetaData的内容,若您感兴趣,可以进一步进入官方文档了解:
七、 Swift语言特性
1. 面向协议
- 从Swift基础库的角度来说,定义的协议数量远比定义的类多很多
- Swift协议可扩展,并通过扩展提供默认实现,这也是Swift面向协议的核心
- Swift协议支持泛型
- Swift协议使用范围更广,并非像OC中只有类才能遵循协议,Swift中结构体(Int、String、Array等也是结构体)、枚举、类都能遵循协议
2. 枚举增强
- OC中枚举,枚举量的值都是整型数据,而Swift中,枚举量的值,除了Int,可以是字符串等类型(只能是字符串、整形、浮点型这三种)
- Swift中枚举,可以增加参数
- Swift中枚举,可以增加方法
- Swift中枚举,可以遵循协议
- 很多功能的设计都依赖于Swift的枚举,如可选型的定义、Alamofire中的Result等
- 经常会遇到枚举量想跟字符串绑定,OC中枚举,需要单独去定义字典去映射,Swift则不需要,甚至可以在定义枚举的时候就处理好这种映射
3. Swift中的类有更多特性
- swift中的类就是普通的一个类,并没有特定意义的基类(OC中是NSObject)
- swift中的类一般不具备运行时特性,除非是继承自NSObject的类,或者是增加@objc修饰
- swift类支持内部类
- swift类泛型支持远比OC灵活和强大
- swift类支持方法重载
4. 函数更灵活
- 支持函数嵌套
- 支持多返回值(新增了元组数据类型)
- 支持参数默认值
5. for\switch等语句更灵活
-
for遍历方式更多
-
switch不仅仅只支持整型,也支持字符串等类型
-
代码示例:
1、for循环//在swift之前的版本中,声明一个空数组变量,如果不指定元素类型,会默认为元素是NSObject类型,但是现在不行,必须指定元素类型 var arr:[Int] = [1,3,2,8,6,7] //直接遍历 for i in arr{ print(i) } //元组方式遍历 for (indexTemp,valueTemp) in arr.enumerated(){ print("元素下标:(indexTemp),元素值:(valueTemp)") } //常规方式遍历,在swift3之中已经被移除了 //for var i = 0; i<arr.count; i += 1 { // //} //for in可以通过区间遍历 for i in 3...21{ print(i) }
2、while循环与repeat while(swift中不再叫do while,而是repeat while)
print("\n\nwhile循环") var whileInt = 10 while whileInt>0 { print(whileInt) whileInt -= 1 } print("\n\nrepeat while循环") whileInt = 10 repeat{ print(whileInt) whileInt -= 1 }while whileInt>0 /* 控制转移语句: break,终止本层循环,如一个for循环,会直接终止这个for循环 continue,只终止本轮循环,不会终止整层循环,不再执行continue之后的语句,直接开启下一次循环 return 函数体中调用,结束整个函数 */
6. if else
// if条件语句,必须是bool值,并不是像其他语言那样非0就默认为true
if cConditon1 == 1{
print(cConditon1)
}
else{
}
//例外的情况是,if let解包可以用=号,否则必须传入bool值或进行逻辑运算</pre>
### Switch
swift中switch语句十分强大,并不限定于整型,
swift中switch的case语句会默认break,不需要显示写
但可以通过fallthrough,取消掉break中断,达到OC中继续执行之后的case的效果
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n61" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; position: relative !important;">/*
*/
var switchInt = 4
switch switchInt {
//case 3.1..<10.2:
// print("5")
case let x where x<10:
print("case let 的形式直接取值")
case 1..<5,6...10:
print("switchInt = (switchInt)")
case 2:
print("condition = (2)")
default:
print("No Result")
}
var switchStr = "Abc"
switch switchStr {
case "a","AB":
print("a")
//case 5:
// print("5tt")
case "Abc":
print(switchStr)
default:
print("default")
}
var switchTuple = (5,"tuple")
switch switchTuple {
case (1..<6,_):
print("YES")
fallthrough
case (_ , _):
print(switchTuple)
fallthrough
case (4 , _):
print("fallthrough 4")
case (let x,"tuple")://可以用var修饰
print(x)
case (let x, let y):
print(x,y,"YES")
default:
print("default")
}
var switchArray = ["A","ab","AG"]
switch switchArray {
case ["A"]:
print("A")
default:
print("default")
}
var switchRange = 1..<10
switch switchRange {
case 2..<5://必须为半开区间,不能为闭区间
print(switchRange)
default:
print("default")
}
//需要注意的是
//但case变量需要和条件变量类型一致(或者case 变量所表示的区间值类型,需要和条件变量类型一致),这点是相同的,
//并且比如switch条件为区间,那么case条件必须为区间,而switch条件为整型,case条件可以为整型也可以为整型区间
//switch的case下,必须携带一句可执行的语句,可以是空语句
//switch的每个case,默认包含break,不会因为漏写break而继续执行下一个case,如果想像其他语言一样,达到某个case条件时继续向下执行case,那么可以使用fallthrough关键字
//switch中的的每个case语句,条件可以是多个,表示或,用逗号隔开,只要满足其中一项,就达到这个case执行的条件
//switch中,default语句可以不写,但是必须满足所有列出的case已经穷举出switch条件所有的可能性
7. 类型安全
- 强类型语言:swift在变量声明的时候,必须显示的声明类型,类型一旦确定,不可更改
- 可选性的定义,使得开发者必须去考虑空的情况
- Swift是静态语言,有类型诊断,OC是动态语言
8. 注重值类型
- swift与OC很大的区别之一在于,Array、Dictionary、Set等,不再是引用类型,而是基于结构体的值类型
- 我们在OC看到的绝大多数常见的数据类型,在Swift中都是值类型,甚至针对UIKit,还出了一套侧重于值类型的SwiftUI框架
9. 支持运算符重载
10. 变量名称更灵活
var 名字 = "name"
var 🏷 = "表情"
print(名字,🏷)
//变量名不局限于字母
11. 强大的泛型支持
- 协议支持泛型
- 泛型约束更灵活多样
12. 支持多种派发方式
-
swift支持
静态派发(效率高)
、动态派发
(函数表派发
、消息派发
)方式,OC支持动态派发
(消息派发
)方式。 -
1、直接派发(Direct Dispatch)
静态派发
,又叫做早期绑定
,是指在编译期将方法调用绑定到方法的实现上,这种派发方式非常快。
在编译期,编译器可以看到调用方和被调方的所有信息,直接生成跳转代码,这样在运行期就不会有其它额外的开销。并且编译器可以根据自己知道的信息进行优化,比如内联,可以极大提高程序运行效率。
在 Swift 中,结构体和枚举的方法调用,以及被final
标记的类和类的方法,都会采用这种派发方式。
直接派发
是最快的,不止是因为需要调用的指令集会更少,并且编译器还能够有很大的优化空间,例如函数内联等,直接派发也有人称为静态调用。 然而,对于编程来说直接调用也是最大的局限,而且因为缺乏动态性所以没办法支持继承和多态。 -
2、函数表派发(Table Dispatch)
函数表派发
是编译型语言实现动态行为最常见的实现方式函数表使用了一个数组来存储类声明的每一个函数的指针.大部分语言把这个称为virtual table"虚函数表), Swift里称为“witness table.
每一个类都会维护一个函数表,里面记录着类所有的函数,如果父类函数被 override的话,表里面只会保存被 override之后的函数.一个子类新添加的函数,都会被插入到这个数组的最后运行时会根据这一个表去决定实际要被调用的函数 查表是一种简单,易实现,而且性能可预知的方式.然而,这种派发方式比起直接派发还是慢一点从字节码角度来看,多了两次读和一次跳转,由此带来了性能的损耗.另一个慢的原因在于编译器可能会由于函数内执行的任务导致无法优化(如果函数带有副作用的话) 这种基于数组的实现,缺陷在于函数表无法拓展.子类会在虚数函数表的最后插入新的函数,没有位置可以让 extension安全地插入函数. -
3、消息机制派发(Message Dispatch)
消息机制
是调用函数最动态的方式.也是Cocoa的基石,这样的机制催生了KVO, UIAppearence和 CoreData等功能.
这种运作方式的关键在于开发者可以在运行时改变函数的行为.不止可以通过swizzling
来改变,甚至可以用isa-swizzling
修改对象的继承关系可以在面向对象的基础上实现自定义派发.
Swift运行时 纯 Swift类的函数调用已经不再是 Objective-c的行时发消息,而是类似C++的vtable,在编译时就确定了调用哪个函数,所以没法通过 runtime获取方法、属性。
而Swift为了兼容 Objective-C,凡是继承自 NSObjec的类都会保留其动态性,所以我们能通过 runtime拿到他的方法。这里有一点说明:老版本的 Swift(如2.2)是编译器隐式的自动帮你加上了@objc,而4.0以后版本的 Swift编译器去掉了隐式特性,必须使用显式添加。
不管是纯Swift类还是继承自 NSObject的类只要在属性和方法前面添加@objc关键字就可以使用 runtime. 项目 原始定义 扩展 值类型 直接派发 直接派发 协议 函数表派发 直接派发 类 函数表派发 直接派发 继承自NSObject的类 函数表派发 消息机制派发 -
小结
值类型总是会使用直接派发,简单易懂 而协议
和类
的 extension都会使用直接派发 NSObject的 extension会使用消息机制进行派发 NSObject声明作用域里的函数都会使用函数表进行派发.
协议里声明的,并且带有默认实现的函数会使用函数表进行派发 一些关键字的派发机制
关键字 机制 final 直接派发 dynamic 消息机制派发 @objc & @nonobjc 改变在OC里的可见性 @inline 告诉编译器可以直接派发 Swift 运行时-final @objc
可以在标记为 final的同时,也使用@objc让函数可以使用消息机制派发.这么做的结果就是,调用函数的时候会使用直接派发,但也会在 Objective-c的运行时里注册响应的 selector.函数可以响应 perform(selector)以及别的 Objective-C特性但在直接调用时又可以有直接派发的性能.
13. 能更灵活地支持函数式编程(链式调用)
14. 有命名空间
- 使得一个项目中同名文件或者同名类可以存在
15. 有更严格的访问控制
- open>public>internal(默认,无需显示声明)>fileprivate>private
- OC中,我们一般很少去使用访问控制符,并且OC的类,都是能被子类继承的
- Swift中,如果一个类不想被子类继承,可以用final修饰
- Swift中,open修饰的类,才能在其他模块中被访问和继承,并且open修饰的类中,如果方法或者属性有open修饰,也才能被重写
- Swift中,public修饰的类型,在其他模块只能访问,如果是类,只能被访问,不能被继承
- Swift中,internal修饰的类型(默认修饰符,无须显示声明)是默认权限. 在模块内的文件,其类定义和函数定义是互相可见的,但是模块外是不可见的,所以它又可理解为:对模块私有.
- Swift中,private修饰的类型,只能在当前private修饰类型(或者变量、方法)的所在作用域内被访问
- Swift中,fileprivate修饰的类型,只能当前文件内被访问
15. 代码简洁
16. 写时拷贝特性(COW:CopyOnWrite)
- 简而言之,写时拷贝,就是指只是单纯赋值时,不进行拷贝,而只有发生内容修改的时候才拷贝
- 如a、b、c三个变量,指向同一个值类型,此时并不会发生内容拷贝,三个变量都用的同一份内存地址
- 但是当修改b的时候,只有b的内存地址发生变化,a、c保持不变
17. 区间符
[n,m]闭区间,用...表示
[n,m)半开区间,用..<表示
18. 语法糖
[1, 2, 3].map{ $0 * 2 }
[1, 2, 3] 使用了, Array 实现的ExpressibleByArrayLiteral 协议, 用于接收数组的字面值 map{xxx} 使用了闭包作为作为最后一个参数时, 可以直接写在调用后面, 而且, 如果是唯一参数的话, 圆括号也可以省略 闭包没有声明函数参数, 返回值类型, 数量, 依靠的是闭包类型的自动推断 闭包中语句只有一句时, 自动将这一句的结果作为返回值 [图片上传失败...(image-ce962-1708916876270)] 0, 后续参数以此类推
19. print函数(打印更灵活)
比OC的NSLog函数跟灵活
(1)支持直接打印一个变量,不局限于NSLog只能格式化输出
(2)支持,分割打印多个变量
(3)支持"(变量名)",快速格式化打印变量
(4)支持多个变量打印时分隔控制(separator:默认空格 terminator:默认换行)
var a = 12
print(a)
print("BGG"+"66666")
print("fafjfk","affdf",separator:"---")
print("rrrr","hhhhh",separator:"~~",terminator:"!!!")
//separator:默认空格 terminator:默认换行
print("新行吗?")
print("a=(a)是整数")</pre>
20. 其他特性
...
八、OC 和 Swift 的异同
1. 共同点:
-
OC出现过的绝大多数概念,在Swift中继续有效,比如:
- 内存管理(
引用计数
、自动引用计数ARC
)、属性
、协议
、接口
、初始化
、扩展类
、命名参数
、匿名函数
等
- 内存管理(
-
Swift和Objective-C 共用一套运行时环境
- Swift的类型可以桥接到Objective-C(下面我简称OC)
- 反之亦然
2.不同点:
-
Swift 更加注重安全,OC注重灵活
- Swift 中的可选类型,适用于所有数据类型,而不仅仅局限于类。相比于OC中的nil更加安全和简明
- Swift提供可选绑定、可选链条 可以确保 属性和方法的访问,是建立在属性值真实存在的情况下
- Swift 中 Class对象的 初始化链
-
Swift 可以进行
面向协议
编程、函数式
编程、面向对象
编程,OC注重面向对象
编程 -
Swift 有
值类型
、引用类型
,OC注重指针
和引用
-
Swift 既有
静态语言特性
又有动态语言特性
,OC 仅有动态语言特性
-
Swift 简洁、易读,文件结构和大部分语法简易化,只有.swift文件. 单行代码结尾不需要分号
-
Swift 中的泛型类型更加方便和通用,而非OC中只能为集合类型添加泛型
-
Swift 中各种方便快捷的高阶函数(函数式编程) (Swift的标准数组支持三个高阶函数:map, filter和reduce,以及map的扩展flatMap)
- Swift 中函数可以当作参数或返回值,OC中不可以
-
Swift 中独有的元组类型(tuples),把多个值组合成复合值。元组内的值可以是任何类型,并不要求是相同 类型的。
-
...
Swift相关的官方文档
专题系列文章
1. 前知识
- 01-探究iOS底层原理|综述
- 02-探究iOS底层原理|编译器LLVM项目【Clang、SwiftC、优化器、LLVM】
- 03-探究iOS底层原理|LLDB
- 04-探究iOS底层原理|ARM64汇编
2. 基于OC语言探索iOS底层原理
- 05-探究iOS底层原理|OC的本质
- 06-探究iOS底层原理|OC对象的本质
- 07-探究iOS底层原理|几种OC对象【实例对象、类对象、元类】、对象的isa指针、superclass、对象的方法调用、Class的底层本质
- 08-探究iOS底层原理|Category底层结构、App启动时Class与Category装载过程、load 和 initialize 执行、关联对象
- 09-探究iOS底层原理|KVO
- 10-探究iOS底层原理|KVC
- 11-探究iOS底层原理|探索Block的本质|【Block的数据类型(本质)与内存布局、变量捕获、Block的种类、内存管理、Block的修饰符、循环引用】
- 12-探究iOS底层原理|Runtime1【isa详解、class的结构、方法缓存cache_t】
- 13-探究iOS底层原理|Runtime2【消息处理(发送、转发)&&动态方法解析、super的本质】
- 14-探究iOS底层原理|Runtime3【Runtime的相关应用】
- 15-探究iOS底层原理|RunLoop【两种RunloopMode、RunLoopMode中的Source0、Source1、Timer、Observer】
- 16-探究iOS底层原理|RunLoop的应用
- 17-探究iOS底层原理|多线程技术的底层原理【GCD源码分析1:主队列、串行队列&&并行队列、全局并发队列】
- 18-探究iOS底层原理|多线程技术【GCD源码分析1:dispatch_get_global_queue与dispatch_(a)sync、单例、线程死锁】
- 19-探究iOS底层原理|多线程技术【GCD源码分析2:栅栏函数dispatch_barrier_(a)sync、信号量dispatch_semaphore】
- 20-探究iOS底层原理|多线程技术【GCD源码分析3:线程调度组dispatch_group、事件源dispatch Source】
- 21-探究iOS底层原理|多线程技术【线程锁:自旋锁、互斥锁、递归锁】
- 22-探究iOS底层原理|多线程技术【原子锁atomic、gcd Timer、NSTimer、CADisplayLink】
- 23-探究iOS底层原理|内存管理【Mach-O文件、Tagged Pointer、对象的内存管理、copy、引用计数、weak指针、autorelease
3. 基于Swift语言探索iOS底层原理
关于函数
、枚举
、可选项
、结构体
、类
、闭包
、属性
、方法
、swift多态原理
、String
、Array
、Dictionary
、引用计数
、MetaData
等Swift基本语法和相关的底层原理文章有如下几篇:
- 01-📝Swift5常用核心语法|了解Swift【Swift简介、Swift的版本、Swift编译原理】
- 02-📝Swift5常用核心语法|基础语法【Playground、常量与变量、常见数据类型、字面量、元组、流程控制、函数、枚举、可选项、guard语句、区间】
- 03-📝Swift5常用核心语法|面向对象【闭包、结构体、类、枚举】
- 04-📝Swift5常用核心语法|面向对象【属性、inout、类型属性、单例模式、方法、下标、继承、初始化】
- 05-📝Swift5常用核心语法|高级语法【可选链、协议、错误处理、泛型、String与Array、高级运算符、扩展、访问控制、内存管理、字面量、模式匹配】
- 06-📝Swift5常用核心语法|编程范式与Swift源码【从OC到Swift、函数式编程、面向协议编程、响应式编程、Swift源码分析】
其它底层原理专题
1. 底层原理相关专题
2. iOS相关专题
- 01-iOS底层原理|iOS的各个渲染框架以及iOS图层渲染原理
- 02-iOS底层原理|iOS动画渲染原理
- 03-iOS底层原理|iOS OffScreen Rendering 离屏渲染原理
- 04-iOS底层原理|因CPU、GPU资源消耗导致卡顿的原因和解决方案