IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> SwiftUI Combine的Future -> 正文阅读

[移动开发]SwiftUI Combine的Future

Future和Promise

  • Future是什么

Future在苹果官方文档的解释为:最终发布一个数据并立即结束。

这个解释过于简单了,其实Future的作用是:我将在未来的某一时刻,发布一个数据,并伴随着成功或失败的状态。

这个功能你可能听起来比较耳熟,对,他就是逃逸闭包(@escaping closure)的功能。

查看Future的源代码,我们可以看到

final public class Future<Output, Failure> : Publisher where Failure : Error {

    /// A type that represents a closure to invoke in the future, when an element or error is available.
    ///
    /// The promise closure receives one parameter: a `Result` that contains either a single element published by a ``Future``, or an error.
    public typealias Promise = (Result<Output, Failure>) -> Void

    /// Creates a publisher that invokes a promise closure when the publisher emits an element.
    ///
    /// - Parameter attemptToFulfill: A ``Future/Promise`` that the publisher invokes when the publisher emits an element or terminates with an error.
    public init(_ attemptToFulfill: @escaping (@escaping Future<Output, Failure>.Promise) -> Void)

    /// Attaches the specified subscriber to this publisher.
    ///
    /// Implementations of ``Publisher`` must implement this method.
    ///
    /// The provided implementation of ``Publisher/subscribe(_:)-4u8kn``calls this method.
    ///
    /// - Parameter subscriber: The subscriber to attach to this ``Publisher``, after which it can receive values.
    final public func receive<S>(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
}

它包含一个Promise类型和一个带有@escaping闭包的初始化函数。

Future和PassthroughSubject、CurrentValueSubject一样,是一个类,不同的是Future实现的是Publiser协议,而PassthroughtSubject和CurrentValueSubject实现的是Subject协议,所以后两个可以使用send方法,而Future不能。

  • Promise是什么

Promise是Future的最终结果。就像英文名所表现的那样,一个是承诺,一个是未来。我们要用承诺来初始化一个未来。Future和Promise需要配套使用。

从Future源码可以看到,Future的初始化Init函数中需要实现一个@escaping闭包,闭包类型就是Promise。Promise的本质可以理解为是一个接收单个Result类型参数的闭包。

let future = Future<Int, Never> { promise in
   // 1
   promise(.success(1))
}

上面的代码是Future的初始化及发布的简单过程,注释为1的行,表示的动作就是,我承诺给你一个数据为Int(1),可以是现在就给予,也可以是在未来给予,比如使用DispatchQueue.main.async 包裹 promise

Future基础用法

让我们按照惯例先在playground中测试下Future的基本用法。

新建一个playground文件,输入以下代码

let futurePublisher = Future<Int, Never> { promise in
    promise(.success(10))
}

let subscription = futurePublisher
    .print("_Future_")
    .receive(on: RunLoop.main)
    .sink { completion in
        switch completion {
        case .finished:
            print("finished")
        case .failure(let error):
            print("Got a error: \(error)")
        }
    } receiveValue: { value in
        print(value)
    }

// Output
_Future_: receive subscription: (Future)
_Future_: request unlimited
_Future_: receive value: (10)
_Future_: receive finished

我们首先创建了一个Future的publisher,可以看到,他和Pass和Current两个Publisher不同,创建时需要实现@escaping闭包,闭包的参数是Promise类型,我们将他命名为promise。

在闭包中,我们发布一个10的整型值。紧接着我们订阅并接收这个publihser,从打印信息中可以看到,一切都和PassthroughSubject和CurrentValueSubject没什么区别。

但是Future带有一个@escaping闭包,意味着我们可以在闭包中进行异步调用,比如DispathQueue,我们试一下。我们将闭包声明改为

let futurePublisher = Future<Int, Never> { promise in
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        promise(.success(10))
    }
}

订阅方式不变,此时运行,打印信息会先打印

_Future_: receive subscription: (Future)
_Future_: request unlimited

过2秒后,接着打印

_Future_: receive value: (10)
_Future_: receive finished
10
finished

注意:如果使用订阅的时候,没有使用futurePublisher.sink后的结果,也就是没有赋值给subscription的话,会出现 request cancel的打印,同时会不能接收到2秒后的数据。这涉及到Combine的内存管理的部分,我们在其他章节再讨论。

Future实战应用

既然Future有@escaping闭包的功能,我们在实际工程里,可以将它用来获取网络数据上。

(实际使用中,Future通常被用来当作函数返回值或计算属性来使用)

这里我们利用https://jsonplaceholder.typicode.com/todos/1?来测试Future的功能,他返回的json如下

{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

首先创建一个SwiftUI的工程,在View中先加入网络处理部分的代码

// MARK:- 数据结构
struct JSONData: Decodable {
    var userId: Int
    var id: Int
    var title: String
    var completed: Bool
}
// MARK:- 错误枚举
enum NetworkError: Error {
    case someError
}

// MARK:- 网络数据处理类
class NetworkService {
    static let shared = NetworkService()
    private init() { }
    
    var cancellables = Set<AnyCancellable>()

    // @escaping closure的写法
    func getWebDataNormal(completion: @escaping (Result<String, Error>) -> Void) {
        URLSession.shared.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/todos/1")!)
            .print("_webdata_")
            .filter { ($0.response as! HTTPURLResponse).statusCode == 200 }
            .map { $0.data }
            .decode(type: JSONData.self, decoder: JSONDecoder())
            .sink { result in
                switch result {
                case .finished:
                    print("finished")
                case .failure(let error):
                    print("Got a error: \(error)")
                    completion(.failure(NetworkError.someError))
                }
            } receiveValue: { data in
                completion(.success(data.title))
            }
            .store(in: &self.cancellables)
    }

    // Combine Future的写法
    func getWebData() -> Future<String, Error> {
        return Future() { [weak self] promise in
            if let self = self {
                URLSession.shared.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/todos/1")!)
                    .print("_webdata_")
                    .filter { ($0.response as! HTTPURLResponse).statusCode == 200 }
                    .map { $0.data }
                    .decode(type: JSONData.self, decoder: JSONDecoder())
                    .sink { completion in
                        switch completion {
                        case .finished:
                            print("finished")
                        case .failure(let error):
                            print("Got a error: \(error)")
                            // "承诺失败",发布错误
                            promise(.failure(NetworkError.someError))
                        }
                    } receiveValue: { data in
                        // "承诺成功",发布数据
                        promise(.success(data.title))
                    }
                    .store(in: &self.cancellables)
            }
        }
    }
}

在代码中,我们只想要显示获取json中的“title”数据。我们在Future的闭包中,调用dataTaskPublisher来获取网络数据,并接收,如果有错误,用promise(.failure())来发布错误,没有错误,用promise(.success())来发布数据。

我们用两种写法实现了获取数据的功能,一个是@escaping闭包的方式,一个是Combine Future的方式,大家可以看下区别。

使用combine时,future可以替代@escaping闭包的功能。

然后我们新建一个ViewModel,来调用网络数据处理的功能

extension FutureView {
    
    class ViewModel: ObservableObject {
        private var cancellables = Set<AnyCancellable>()
        // MARK:- 刷新视图用的变量
        @Published var title: String = ""
        
        func fetchData() {
            // getWebData的返回值是publisher,所以用Combine方式处理
            NetworkService.shared.getWebData()
                .print("_fetchData_")
                .receive(on: RunLoop.main)
                .sink { completion in
                    switch completion {
                    case .failure(let err):
                        // promise发布的错误
                        print("Error is \(err.localizedDescription)")
                    case .finished:
                        print("Finished")
                    }
                }
                receiveValue: { [weak self] data in
                    print("fetchWebData: \(data)")
                    // promise发布的数据,存储到title
                    self?.title = data
                }
                .store(in: &cancellables)
        }
    }
}

最后实现我们的View

struct FutureView: View {
    @StateObject var vm = ViewModel()
    private var cancellables = Set<AnyCancellable>()
    
    var body: some View {
        Text(vm.title)
            .onAppear {
                vm.fetchData()
            }
    }
}

最后运行,得到结果

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-22 14:48:20  更:2021-09-22 14:49:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 19:39:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码