【iOS面试题五】函数响应式编程

21 阅读7分钟

GitHub移动端APP:支持代码浏览、Git事件和AI一键翻译等

一、概念

1、什么是函数响应式编程?

(1) 函数式编程是指函数作为函数的参数和返回值的高阶函数编程方式。

(2) 响应式编程是指面向数据流及其变化传播的编程方式,它以高阶函数的方式把监听代码和处理代码放在一起。传统式编程中的数据流,都是预先填充中,比如数组;而响应式编程中的数据流,除了可以同步产生,也可以异步产生,比如按钮的点击事件。

2、函数响应式框架有哪些?

(1) ObjC语言的ReactiveObjC(简称RAC)

(2) Swift语言中,适合UIKit的RxSwift和适合SwiftUI的Combine

3、函数响应式框架中的相关术语

(1) RAC:订阅/信号/订阅者。

(2) RxSwift:数据绑定/可监听序列/观察者。

(3) Combine:订阅或绑定/发布者/订阅者。

4、信号在RAC和RxSwift中的区别?

(1) 在RAC中,信号是传递数据变化的工具,当信号所监听的数据发生变化时,将会通知其所有订阅了该信号的订阅者,从而订阅者可以响应数据的变化。可被理解为Combine中的事件。

(2) 在RxSwift中,信号是可监听序列的一种,它只能用于主线程中,不产生error事件。

5、函数响应式框架中的逻辑运算?

(1) 或(任意一个):merge

(2) 与(按相同索引合并):zip

(3) 与(按最新值合并):combineLatest

(4) 顺序执行:concat

二、RAC

1、RAC中冷信号和热信号的区别

(1) 冷信号是被动的,只有被订阅后才会推送,每订阅一次、全新推送一次(一对一,类似于点播)

(2) 热信号是共享的,即使没有订阅事件,信号仍然会推送,信号与所有订阅者共享(一对多,类似于直播)

(3) 在RAC中RACSubject及其子类都是热信号,排除RACSubject类以外的RACSignal都是冷信号。

2、RAC中冷热信号的应用场景

如果网络请求的信号是冷信号,需要注意每次订阅都会发起一次网络请求,当对同一个网络请求进行各种flattenMap、merge(对返回数据进行处理),会造成发起多次网络请求,浪费流量。

3、什么是RAC中的信号转换?

任何信号的转换(如flattenMap/startWith/catchTo/merge等方法)都是对原信号进行订阅而产生新值的信号,因此如果原信号是冷信号、会在转换过程都被执行一次。

4、RAC中冷信号转热信号的标准方法有哪些?

(1) (RACMulticastConnection *)publish;

(2) (RACMulticastConnection *)multicast:(RACSubject *)subject;

(3) (RACSignal *)replay;

(4) (RACSignal *)replayLast;

(5) (RACSignal *)replayLazily;

注:使用一个Subject来订阅原始信号,并让其他订阅者订阅这个Subject,这个Subject就是热信号。

5、RAC中信号的创建过程是怎样的?

创建信号就是使用createSignal方法,创建一个信号,并为信号设置了一个didSubscribe的Block属性,该Block属性以订阅者(RACSubscriber协议)作为参数、以RACDisposable作为返回值。当订阅信号时,Block属性会被执行,在Block内,可以通过订阅协议来发送数据给订阅者。

6、RAC中信号的订阅过程是怎样的?

(1) 使用subscribeNext的方法参数,创建出一个订阅者subscriber。

(2) 信号对象执行了订阅操作subscribe,方法中传入参数是刚创建的订阅者。其内部执行过程为:除了对于订阅者和清理对象的再次封装外,最重要的就是创建信号时为信号设置Block(didSubscribe)被调用了,而且Block参数使用了我们创建的订阅者。

7、通过RACSubject作为冷信号的订阅者时,可以将冷信号转换为热信号吗?

可以

三、RxSwift

1、什么是RxSwift?

RxSwift是一个简化异步编程的Swift框架。

2、RxSwift提供哪些方法来支持常用的设计模式

(1) Target/Action: rx.tap

(2) 代理: rx.items/rx.contentOffset

(3) 闭包/Block/代码块: rx.xxx

(4) 通知: NotificationCenter.default.rx.notification(.xxx)

(5) 链式调用: .flatMap

(6) 并发: .zip

3、RxSwift的核心概念有哪些?

(1) Observable(可监听序列,Signal/ControlEvent):产生事件

(2) Observer(观察者,AnyObserver/Binder):响应事件

(3) Operator(操作符):组合事件

(4) Disposable(清除,DisposeBag/takeUntil):管理数据绑定的生命周期

(5) Schedulers(调度器,subscribeOn/observeOn):线程队列调度器

① MainScheduler:主调度器

② SerialDispatchQueueScheduler:串行调度器

③ ConcurrentDispatchQueueScheduler:并行调度器

④ OperationQueueScheduler:NSOperationQueue的封装

4、什么是RxSwift中的连接操作符?

类似于RAC中的热信号。可连接的可观察序列类似于普通的可观察序列,不同的是它们在被订阅时不会开始发送元素,而是在调用connect方法后才开始。因此,你有机会在可连接的可观察序列发送元素之前,将所有的订阅者都进行订阅。将普通的可观察序列转换为可连接的可观察序列的方法叫连接操作符,分别为:publish、multicast或replay方法。

四、Combine

1、什么是actor(行为体)?

actor是Swift 5.5引入的,用于表示并发安全的类型,类似于class、但它保证线程安全。

(1) 它的状态在内部访问时是串行进行的,不会发生数据竞争;在外部访问时需要采用await进行异步进行。

(2) 默认情况下,actor的所有属性和实例方法都是隔离的,需要await访问,但可以使用nonisolated来让某些属性或方法能被同步访问。

(3) actor不能继承,但可以遵循协议,对于异变(mutating)或异步(async)的协议方法,需要实现为async方法。

(4) 不同actor之间的通信仍然需要await异步进行。

(5) 采用@MainActor可以指定actor只能运行在主线程。

(6) actor的方法需要一个闭包参数时,该闭包必须采用@Sendable修饰,以确保它可以安全地在不同线程上执行。

2、什么是Sendable(可发送体)?

Sendable是Swift 5.5引入的,它是一个协议,用于确保遵循该协议类型的线程安全。

(1) 值类型(枚举和结构体)在多线程中传递时,是复制的,不会有数据竞争。但引用类型(类),在多线程中同时访问时,访问的是同一个实例,从而导致数据竞争。因此,Swift提供Sendable,以确保类型在并发环境中传递时的安全性。

(2) 如果枚举或结构体中只包含Sendable的属性,则它默认是Sendable。

(3) 类默认不是Sendable,除非是所有属性都是不可变(let)的最终类(final)。@unchecked Sendable可以表示该类虽然不是Sendable、但开发者保证其线程安全。

(4) actor和Sendable的关系:当actor的方法需要一个闭包参数时,该闭包必须采用@Sendable修饰,以确保它可以安全地在不同线程上执行。

3、什么是Task(任务)?

Task是Swift 5.5引入的,用于提供执行async函数的环境,是一种结构化并发模型(让异步代码像同步代码一样,但支持异步操作)。

(1) Task的创建方式

① 启动一个代码块,如:

Task { 
    let result = await fetchData() 
    print("Result:", result) 
}

② 直接绑定返回值,如:

let task = Task { return await fetchData() } 
let result = await task.value 
print("Fetched result:", result)

(2) 支持取消和检查取消:cancel、checkCancellation和isCancelled方法

(3) 支持任务分离(采用完全独立的异步环境,不依赖当前任务的上下文):detached方法

(4) 支持设置优先级:high、userInitiated、default、utility和background

(5) 支持任务组:withTaskGroup和withThrowingTaskGroup函数

(6) 与async let的对比:async let适用于等待多个任务并发执行的所有结果;Task适用于异步任务的启动和管理。

4、什么是Future(将来)?

Future由Combine框架提供,属于Publisher的子类,只能返回单个异步值,常用于将async/await代码转换为Combine代码。它在定义时便创建好了、等到被订阅时才会执行,且只执行一次并缓存结果,多个订阅者共享相同的结果。(类似于RAC中的热信号)

5、什么是Deferred(延迟)?

Deferred由Combine框架提供,一个特殊的Publisher,用于推迟Publisher的创建。它确保每次订阅时都创建一个新的Publisher并执行,不同订阅者得到属于自己的结果。(类似于RAC中的冷信号)