06-📝Swift5常用核心语法|编程范式与Swift源码【从OC到Swift、函数式编程、面向协议编程、响应式编程、Swift源码分析】

3,609 阅读17分钟

一、概述

本系列文章旨在复习Swift5核心语法且适当进行底层原理探索,属于阶段性复习和巩固,以供日后进一步探索Swift语言的底层原理做铺垫。

整个系列文章如下,每一文章知识点独立成篇,欢迎各位按需或按兴趣点击阅读:

二、从OC到Swift

1. 标记

我们可以通过一些注释标记来特殊标明注释的含义

  • // MARK: 类似OC中的#pragma mark
  • // MARK: - 类似OC中的#pragma mark -
  • // TODO: 用于标记未完成的任务
  • // FIXME: 用于标记待修复的问题

使用示例如下

-w370 -w440

我们还可以使用#warning来作为警告的提示,效果更为显著

-w714

2. 条件编译

-w720

我们还可以在Build Settings-> Swift Compiler -Custom Flags自定义标记

Other Swift Flags里自定义的标记要以-D开头

-w716

-w278

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() {}
} 

-w642

更多用法参考:docs.swift.org/swift-book/…

6. 程序入口

AppDelegate上面默认有个@main标记,这表示编译器自动生成入口代码(main函数代码),自动设置AppDelegate为APP的代理

-w776

之前的Xcode版本会生成@UIApplicationMain标记,和@main的作用一样

-w776

也可以删掉@main或者@UIApplicationMain,自定义入口代码

1.创建main.swift文件

-w728

2.去掉AppDelegate里的标记

-w775

3.在main.swift里面自定义UIApplication并增加入口代码

-w748

7. Swift调用OC

如果我们在Swift项目中需要调用到OC的代码,需要建立一个桥接头文件,文件名格式为{targetName}-Bridging-Header.h

在桥接文件里引用需要的OC头文件

-w1119

Build Setting -> Swift Compiler - General中写好桥接文件路径

-w849

如果我们是在Swift项目里第一次创建OC文件,Xcode会提示是否需要帮助创建桥接文件

-w731

然后我们就可以在Swift文件里调用OC的代码了

-w532 -w728

如果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

-w847

Swift暴露给OC的类最终都要继承自NSObject

使用@objc修饰需要暴露给OC的成员

-w400 -w592

使用@objcMembers修饰类,代表默认所有成员都会暴露给OC(包括扩展中定义的成员)

最终是否成功暴露,还需要考虑成员自身的权限问题

-w496

我们进入到test-Swift.h里看看编译器默认帮我们转成的OC代码是怎样的

-w722

我们还可以通过@objc来对Swift文件里的类和成员重命名,来更适应于OC的代码规范

-w573 -w635

9. 选择器

Swift中依然可以使用选择器,使用#selector(name)定义一个选择器

必须是被@objcMembers@objc修饰的方法才可以定义选择器

-w728

如果不加@objcMembers@objc是会报错的

-w777

10. 混编调用的本质

我们先来思考一个问题,为什么Swift暴露给OC的类最终要继承NSObject?

只有OC调用最后还是走的消息发送机制,要想能够实现消息机制,就需要有isa指针,所以要继承NSObject

我们在调用的地方打上断点,然后进行反汇编

-w557

我们发现,反汇编内部最终调用了objc_msgSend,很明显是消息发送机制

-w846

那Swift调用OC的方法,是走的消息发送机制,还是Swift本身的调用方式呢?

我们在调用的地方打上断点,然后进行反汇编

-w781

我们发现,反汇编内部最终调用了objc_msgSend,很明显是消息发送机制

-w846

暴露给OC使用的Swift函数和类,如果被Swift调用,是走的消息发送机制,还是Swift本身的调用方式呢?

我们在调用的地方打上断点,然后进行反汇编

-w784

我们发现,反汇编内部是按照根据元类信息里的函数地址去调用的方式,没有Runtime相关的调用

-w841 -w847

我们可以加上dynamic关键字,这样不管是OC调用还是Swift调用都会走Runtime的消息发送机制

-w505

反汇编之后

-w843

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的内容,编译器会自动做优化 -w467

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协议包含的部分内容

    • startIndexendIndex属性、index方法
    • StringArray都遵守了这个协议
  • RangeReplaceableCollection协议包含的部分内容

    • appendinsertremove方法
    • StringArray都遵守了这个协议
  • DictionarySet也有实现上述协议中声明的一些方法,只是并没有遵守上述协议

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

  • StringNSString之间可以随时随地的桥接转换
  • 如果你觉得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 

我们通过反汇编发现,StringNSString的转换会调用函数来实现的,相对会有性能的消耗,但由于编译器的优化,消耗的成本可以忽略不计

-w715

比较字符串内容是否等价

  • 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方法

-w714 -w714 !-w713

下面是Swift和OC的几个类型的转换表格

StringNSString可以相互转换,而NSMutableString就只能单向转换成String

其他类型同理

-w506

12. 只能被class继承的协议

  • 如果协议对应AnyObject、class、@objc来修饰,那么只能被类所遵守 -w654
  • @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

    1. Swift支持KVC、KVO的条件需要属性所在的类、监听器最终继承自NSObject
    1. @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) 
    1. 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不可以增加存储属性
  • 借助关联对象,可以实现类似extensionclass增加存储属性的效果
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]
    1. mapflatMap、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)
    
    1. 使用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) 
    
    1. 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----- 
    
    1. OptionalmapflatMap
      会先将可选类型解包,处理完会再进行包装返回出去
    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)

  • 什么是柯里化?
  • 将一个接受多参数的函数变换为一系列只接受单个参数的函数 -w633
  • Array、Optionalmap方法接收的参数就是一个柯里化函数 -w619

演变示例

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) -w619 -w601

下图充分解释了函子

-w910

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开发中,OOPPOP是相辅相成的,任何一方并不能取代另一方

POP能弥补OOP一些设计上的不足

2. OOP和POP

回顾OOP

  • OOP的三大特性:封装继承多态
  • 继承的经典使用场合:
    当多个类(比如A、B、C类)具有很大共性时,可以将这些共性抽取到一个父类中(比如D类),最后A、B、C类继承D类

-w514

  • 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 {} 

-w447

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),ReactiveXSwift版本

源码: 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 是什么:

  • image.png
  • 从上面附图中,我们不难得出结论:
  • Swift 运行时 为程序中使用的每种数据类型(包括泛型类型的每个实例) 保留元数据(Metadata)记录
  • 换句话说,Metadata就是用于保留类型信息
  • (TODO:反射和)调试器工具可以使用这些元数据记录来了解有关类型的信息
    • 应用:字典转模型类的底层信息获取调试器调试类信息
  • 对于非泛型标称类型,这些元数据记录由编译器静态生成
  • 对于泛型类型的实例以及内部类型(例如元组、函数、协议组合等),元数据记录由运行时根据需要延迟创建
  • 每种类型都有唯一的元数据记录
  • 两个元数据指针值相等,当且仅当类型相等

4.2 Metadata通用信息布局

所有元数据记录共享一个公共标头,其中包含以下字段:

  • 虚函数表的地址值,提供基本操作,例如分配、复制和销毁
  • 记录类型的大小、对齐方式、步幅和其他基本属性
  • 记录指针位于距元数据指针的偏移量 -1 处,即紧邻指针所引用地址之前的指针大小的字。
  • kind 字段是一个指针大小的整数
    • 描述元数据所描述的类型的种类
    • 该字段位于元数据指针的偏移量 0 处

4.3 当前kind值与类型关系如下:

image.png

  • 类元数据的类型为 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中switchcase语句会默认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注重面向对象编程

  • Swift 有 值类型引用类型,OC注重 指针引用

  • Swift 既有静态语言特性又有动态语言特性,OC 仅有 动态语言特性

  • Swift 简洁、易读,文件结构和大部分语法简易化,只有.swift文件. 单行代码结尾不需要分号

  • Swift 中的泛型类型更加方便和通用,而非OC中只能为集合类型添加泛型

  • Swift 中各种方便快捷的高阶函数(函数式编程) (Swift的标准数组支持三个高阶函数:map, filter和reduce,以及map的扩展flatMap)

    • Swift 中函数可以当作参数或返回值,OC中不可以
  • Swift 中独有的元组类型(tuples),把多个值组合成复合值。元组内的值可以是任何类型,并不要求是相同 类型的。

  • ...

Swift相关的官方文档

专题系列文章

1. 前知识

2. 基于OC语言探索iOS底层原理

3. 基于Swift语言探索iOS底层原理

关于函数枚举可选项结构体闭包属性方法swift多态原理StringArrayDictionary引用计数MetaData等Swift基本语法和相关的底层原理文章有如下几篇:

其它底层原理专题

1. 底层原理相关专题

2. iOS相关专题

3. webApp相关专题

4. 跨平台开发方案相关专题

5. 阶段性总结:Native、WebApp、跨平台开发三种方案性能比较

6. Android、HarmonyOS页面渲染专题

7. 小程序页面渲染专题