AdvancedCombine/高级组合,Futures/转义闭包转换为未来发布者 的详细使用

1. 创建详细使用的高级组合 View AdvancedCombineBootcamp.swift

Swift 复制代码
import SwiftUI
import Combine

/// 数据服务
class AdvancedCombineDataService{
    // @Published var basicPublisher: String = "first publish"
    // CurrentValueSubject 通用函数
    // let currentValuePublisher = CurrentValueSubject<Int, Error>("first publish")
    // 发布者数据
    let passThroughPublisher = PassthroughSubject<Int, Error>()
    // 发布者数据
    let boolPublisher = PassthroughSubject<Bool, Error>()
    // 发布者数据
    let intPublisher = PassthroughSubject<Int, Error>()
    
    init(){
        publishFakeData()
        //publishFakeData2()
    }
    
    //发布模拟数据
    private func publishFakeData(){
        // Array(1 ..< 11)
        // 重复数据用来过滤重复数据使用,并且是连续的才有效果
        // let items: [Int] = [1, 2, 3, 4, 4, 5, 5, 4, 6, 7, 8, 9, 10]
        //, 11, 12, 13, 14, 15, 16, 17, 18
        let items: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        for x in items.indices {
            DispatchQueue.main.asyncAfter(deadline: .now() + Double(x)) {
               
                self.passThroughPublisher.send(items[x])
                if x > 4 && x < 8 {
                    self.boolPublisher.send(true)
                    self.intPublisher.send(999)
                }else {
                    self.boolPublisher.send(false)
                }
                
                if x == items.indices.last{
                    //获取最后一个数据,计算最大值/最小身上 必须打开完成关闭操作,需要知道数据的区间范围
                    self.passThroughPublisher.send(completion: .finished)
                    //self.boolPublisher.send(completion: .finished)
                }
            }
        }
    }
    
    // 发布模拟数据2,配合 .debounce 使用
    private func publishFakeData2(){
        DispatchQueue.main.asyncAfter(deadline: .now() + 0) {
            self.passThroughPublisher.send(1)
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.passThroughPublisher.send(2)
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
            self.passThroughPublisher.send(3)
        }
    }
}

/// ViewModel
class AdvancedCombineBootcampViewModel: ObservableObject{
    @Published var data: [String] = []
    @Published var dataBools: [Bool] = []
    @Published var error: String = ""
    
    let dataService = AdvancedCombineDataService()
    var cancellable = Set<AnyCancellable>()
    
    // 多播数据
    let multiCastPublisher = PassthroughSubject<Int, Error>()
    
    init() {
        addSubscribers()
    }
    
    // 添加订阅
    private func addSubscribers(){
        //.$basicPublisher currentValuePublisher
        // dataService.passThroughPublisher
        
        // Sequence Operations: 序列运算
        /*
         // 第一个数据操作
         //  .first()
         //  .first(where: {$0 > 4})
         //  .tryFirst(where: { value in
         //      if value == 3 {
         //          throw URLError(.badServerResponse)
         //      }
         //      return value > 1
         //  })
         
         // 最后一个数据操作
         // last: 获取最后一个值,监听值需要加 .send(completion: .finished),表示执行到最后
         // .last()
         // .last(where: { $0 < 4})
         // .tryLast(where: { value in
         //     if value == 13{
         //         throw URLError(.badServerResponse)
         //     }
         //     print("\(value)")
         // value < 4 = 3
         // value > 4 = 10
         //     return value > 4
         // })
         
         // 删除操作
         // 删除第一个值
         // .dropFirst()
         // 删除前三个
         // .dropFirst(3)
         // drop 保留小于号,不支持大于号,因为大于号开始就删除,返回 false,表示执行闭包结束,删除成功,没有实际的意义
         // .drop(while: { $0 < 5 })
         //   .tryDrop(while: { value in
         //       if value == 5{
         //           throw URLError(.badServerResponse)
         //       }
         //       return value < 6
         //   })
         
         // 返回前几个操作
         // prefix(4) 取前 4 个
         // $0 > 5 闭包立即返回 false,前缀实际上完成了,所以不返回
         // $0 < 5 返回数组中的前 五 的元素,第一个条件必须为真,否则不返回
         // .prefix(while: { $0 < 5 })
         // .tryPrefix(while: )
         
         // 根据下标索引,输出数值
         // .output(at: 3)
         // 根据 输出范围
         // .output(in: 2 ..< 4)
         */
        
        // Mathematic Operations: 数学运算
        /*
         // 获取最大值
         // .max()
         // 两者相比较,取最大值: 10
         // .max(by: { value1, value2 in
         //     return value1 < value2
         // })
         // .tryMax(by: )
         
         // 获取最小值
         // .min()
         // 两者相比较取最小值
         // .tryMin(by: { value1, value2 in
         //     return value1 < value2
         // })
         */
        
        // Filter / Reducing Operations: 过滤 / 减少运算
        /*
         // 遍历数据
         //   .map({ String($0) })
         // 遍历到 5 抛出异常,并结束
         //    .tryMap({ value in
         //        if value == 5 {
         //            throw URLError(.badServerResponse)
         //        }
         //        return String(value)
         //    })
         
         // 遍历去除值为 5 的数据
         //    .compactMap({ value in
         //        if value == 5 {
         //            return nil
         //        }
         //        return String(value)
         //    })
         // 遍历到 5 抛出异常,并结束
         //    .tryCompactMap({ value in
         //        if value == 5 {
         //            throw URLError(.badServerResponse)
         //        }
         //        return String(value)
         //    })
         
         // 过滤器 输出大于3 小于7 的值
         //    .filter({ ($0 > 3) && ($0 < 7)})
         // 输出到 3 直接抛出异常,并结束
         //    .tryFilter({ value in
         //        if value > 3 && value < 7{
         //            throw URLError(.badServerResponse)
         //        }
         //        return true
         //    })
         
         // 删除重复项
         // 删除数据中相同的数据,必须是相邻的,否则不起作用
         //    .removeDuplicates()
         // 删除重复项 于 removeDuplicates 删除重复项的情况完全相同
         //    .removeDuplicates(by: { value1, value2 in
         //        return value1 == value2
         //    })
         //    .tryRemoveDuplicates(by: )
         
         // 传递值时 nil 替换为 指定的值,
         // 数值改为可选项卡 PassthroughSubject<Int?, Error>(),
         // let items: [Int?]  = [1, nil, 3, 4, 4, 5, 4, 6, 7, 8, 9, 10]
         //    .replaceNil(with: 5)
         // 为空值时 替换为指定的数据
         //    .replaceEmpty(with: 5)
         // 搭配 try 开头的语句使用,如 tryMap 抛出异常为 throw URLError(.badServerResponse)
         // 抛出的异常文字替换为指定的为 Default Value 的字符串值
         //    .replaceError(with: "Default Value")
         
         // 扫描 现原有值,新值
         //    .scan(0, { existingValue, newValue in
         // 0:设定原有值 + 1: 接收的新值  = 1:原有值
         // 1:原有值 + 2: 接收的新值 =  3: 原有值
         // 3: 原有值 + 3: 接收的新值 = 6:原有值
         //        return existingValue + newValue
         //    })
         // 简写扫描操作
         //    .scan(0, { $0 + $1 })
         // 更简洁的写法
         //    .scan(0, +)
         // 抛出异常写法
         //    .tryScan(,)
         
         // 减少运算
         //    .reduce(0, { existingValue, newValue in
         // 集合中数据相加总和
         //        return existingValue + newValue
         //    })
         // 简写方法
         //    .reduce(0, +)
         
         // 收集数据
         // 收集所有的发布,一次性返回数据集合,调用此方法,要在 .map 方法后
         // //self?.data = returnedValue
         //    .collect()
         // 三个一组收集发送
         // 接收值  self?.data.append(contentsOf: returnedValue)
         //    .collect(3)
         
         // 所有的值是否满足,满足条件为 true,否则为 false
         //    .allSatisfy({ $0 > 0 })
         // 跟之前 try 功能相似
         //    .tryAllSatisfy()
         */
        
        // Timing Operations: 计时运算
        /*
         // 反弹操作,用于输入入文本操作,等待设定的时间结束再返回
         // 0.75 秒等待,如果期间返回两次,取最后返回的一次值
         //    .debounce(for: 0.75, scheduler: DispatchQueue.main)
         
         // 延时操作,延时两秒后,在接收发送过来的值
         //    .delay(for: 2, scheduler: DispatchQueue.main)
         
         // 测量间隔主要是测试用的,查看每次拿到数据的时间间隔
         //    .measureInterval(using: DispatchQueue.main)
         // stride 间隔距离
         //    .map({ stride in
         //        return "\(stride.timeInterval)"
         //    })
         
         // 点节流 for: 10: 可以每 10 秒打开和关闭它一次,然后 10 秒钟内不打开 ,latest: 是否输出最新值
         // let items: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
         // for: 10, latest: true, 输出值为 1 , 10
         // for: 10, latest: false, 输出值为 1 , 2
         //    .throttle(for: 30, scheduler: DispatchQueue.main, latest: true)
         
         // 用在网络请求数据发生异常,指定重试次数,制定次数范围内,都是异常,才返回异常
         //    .retry(3)
         
         // 超时操作,超过设定时长,则不返回
         //    .timeout(0.75, scheduler: DispatchQueue.main)
         */
        
        // Multiple Publishers / Subscribers: 多个 发布 / 订阅
        /*
         // 组合
         // 组合多个数据
         //    .combineLatest(dataService.boolPublisher, dataService.intPublisher)
         
         //    .compactMap({ (int, bool) in
         //        if bool {
         //            return String(int)
         //        }
         //        return nil
         //    })
         
         // 简写
         //    .compactMap({ $1 ? String($0) : "n/a" })
         // 删除重复项,必须是连续的,由于发布数据,两个都是独立的,为 true/false 时,两个都会更新订阅的数据,所以会显示两次
         //    .removeDuplicates()
         
         // 完成三个发布者数据 compactMap: 对给定数组的每个元素,执行闭包中的映射,将非空的映射结果放置在数组中返回
         // 三个数据都有的情况下,返回数据,否则返回,最小的数组
         //    .compactMap({ (int1, bool, int2) in
         //        if bool {
         //            return String(int1)
         //        }
         //        return "n/a"
         //    })
         
         
         // 合并
         // 数据类型相同的合并处理
         //    .merge(with: dataService.intPublisher)
         
         // 压缩
         // 将两个不同数据类型进行压缩
         //    .zip(dataService.boolPublisher, dataService.intPublisher)
         // tuple: (Int, Bool) 组
         // 三个数据都有的情况下,返回数据,否则根据最小的数组返回
         //    .map({ tuple in
         //        return String(tuple.0) + tuple.1.description  + String(tuple.2)
         //    })
         
         // 捕捉
         // 如果映射数据发生异常,通过 .catch 函数 捕捉异常,返回对应的指定的数据,进行再次映射返回数据
         //    .tryMap({ int in
         // 为 5 时返回捕捉异常数据,并结束,否则返回原数据
         //        if int == 5 {
         //            throw URLError(.badServerResponse)
         //        }
         //        return int
         //    })
         //    .catch({ error in
         //        return self.dataService.intPublisher
         //    })
         //    .map({ String($0) })
         */
        
        let sharedPublisher = dataService.passThroughPublisher
            // 删除前三项
           // .dropFirst(3)
            // 输出共享给多个订阅者
            .share()
            // 多播数据,更改了发布者的数据,自动连接数据,自动开始发布
           // .multicast {
           //     PassthroughSubject<Int, Error>()
           // }
            // 传递新建的多播发布者
            .multicast(subject: multiCastPublisher)
        
        sharedPublisher
            .map({ String($0) })
            .sink { completion in
                switch completion {
                case .finished:
                    break
                case .failure(let error):
                    self.error = "ERROR: \(error)"
                    break
                }
            } receiveValue: {[weak self] returnedValue in
                //self?.data.append(contentsOf: returnedValue)
                //self?.data = returnedValue
                self?.data.append(returnedValue)
            }
            .store(in: &cancellable)
        
        sharedPublisher
            .map({ $0 > 5 ? true : false })
            .sink { completion in
                switch completion {
                case .finished:
                    break
                case .failure(let error):
                    self.error = "ERROR: \(error)"
                    break
                }
            } receiveValue: {[weak self] returnedValue in
                self?.dataBools.append(returnedValue)
            }
            .store(in: &cancellable)
        
        /// 测试取消多播发布的数据
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            sharedPublisher
                .connect()
                // 取消数据里可能是多播发布的数据
                .store(in: &self.cancellable)
        }
    }
}

/// 高级组合
struct AdvancedCombineBootcamp: View {
    // View Model
    @StateObject private var viewModel  = AdvancedCombineBootcampViewModel()
    
    var body: some View {
        ScrollView {
            HStack {
                VStack {
                    ForEach(viewModel.data, id: \.self) {
                        Text($0)
                            .font(.largeTitle)
                            .fontWeight(.black)
                    }
                    if !viewModel.error.isEmpty{
                        Text(viewModel.error)
                    }
                }
                
                VStack {
                    ForEach(viewModel.dataBools, id: \.self) {
                        Text($0.description)
                            .font(.largeTitle)
                            .fontWeight(.black)
                    }
                }
            }
        }
    }
}

struct AdvancedCombineBootcamp_Previews: PreviewProvider {
    static var previews: some View {
        AdvancedCombineBootcamp()
    }
}

2. Futures 用于转义闭包转换为未来发布者,作用于统一接口

2.1 创建实例 View,FuturesBootcamp.swift
Swift 复制代码
import SwiftUI
import Combine

// download with Combine: 使用组合下载
// download with @escaping closure: 使用转义闭包
// convert @escaping closure to combine: 转换转义闭包到组合

class FuturesBootcampViewModel: ObservableObject{
    // 发布者,标题文本
    @Published var title: String = "Starting title"
    // https://www.google.com.hk
    let url = URL(string: "https://www.baidu.com")
    var cancellable = Set<AnyCancellable>()
    
    init() {
        //download()
        //download2()
        download3()
    }
    
    // 下载数据1
    func download() {
        getCombinePublisher()
            .sink { _ in
                
            } receiveValue: { [weak self] returnedValue in
                self?.title = returnedValue
            }
            .store(in: &cancellable)
    }
    
    // 下载数据2
    func download2(){
        getEscapingClosure { [weak self] returnedValue, error in
            self?.title = returnedValue
        }
    }
    
    // 下载数据3
    func download3(){
        getFuturePublisher()
            .sink { _ in
                
            } receiveValue: { [weak self] returnedValue in
                self?.title = returnedValue
            }
            .store(in: &cancellable)
    }
    
    // 获取组合发布者
    func getCombinePublisher() -> AnyPublisher<String, URLError>{
        // 判断是否异常
        guard let url = url else { return PassthroughSubject<String, URLError>().eraseToAnyPublisher() }
        // 进行请求
        return URLSession.shared.dataTaskPublisher(for: url)
            .timeout(1, scheduler: DispatchQueue.main)
            .map({ _ in
                return "New value"
            })
            .eraseToAnyPublisher()
    }
    
    // 获取转义闭包
    func getEscapingClosure(completionHandler: @escaping (_ value: String, _ error: Error?) -> ()){
        guard let url = url else {
            completionHandler("", nil)
            return
        }
        
        URLSession.shared.dataTask(with: url) { data, response, error in
            completionHandler("New value 2", nil)
        }
        // 执行实际的数据任务
        .resume()
    }
    
    // 转义闭包 转换为 未来发布者
    func getFuturePublisher() -> Future<String, Error>{
        Future { promise in
            self.getEscapingClosure { returnedValue, error in
                if let error = error {
                    promise(.failure(error))
                }else{
                    promise(.success(returnedValue))
                }
            }
        }
    }
    
    // 一些案例 1 简单操作
    func doSomething(completion: @escaping (_ value: String) -> ()){
        DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
            completion("NEW STRING")
        }
    }
    
    // 2
    func doSomethingInTheFuture() -> Future <String, Never>{
        Future { promise in
            self.doSomething { value in
                promise(.success(value))
            }
        }
    }
}

// 表示未来的值
struct FuturesBootcamp: View {
    @StateObject private var viewModel = FuturesBootcampViewModel()
    
    var body: some View {
        Text(viewModel.title)
    }
}

struct FuturesBootcamp_Previews: PreviewProvider {
    static var previews: some View {
        FuturesBootcamp()
    }
}
相关推荐
I烟雨云渊T5 小时前
iOS 门店营收表格功能的实现
ios
明月看潮生11 小时前
青少年编程与数学 01-011 系统软件简介 07 iOS操作系统
ios·青少年编程·操作系统·系统软件
90后的晨仔13 小时前
RxSwift 框架解析
前端·ios
Humbunklung13 小时前
Rust Floem UI 框架使用简介
开发语言·ui·rust
大熊猫侯佩17 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(五)
swiftui·swift·apple watch
可爱小仙子17 小时前
ios苹果系统,js 滑动屏幕、锚定无效
前端·javascript·ios
未来猫咪花18 小时前
# Flutter状态管理对比:view_model vs Riverpod
flutter·ios·android studio
咕噜企业签名分发-淼淼21 小时前
开发源码搭建一码双端应用分发平台教程:逐步分析注意事项
android·ios
CodeCraft Studio1 天前
【案例分享】如何借助JS UI组件库DHTMLX Suite构建高效物联网IIoT平台
javascript·物联网·ui
插件开发1 天前
免费插件集-illustrator插件-Ai插件-随机填色
ui·illustrator