AdvancedCombine/高度な組み合わせ、Futures/将来のパブリッシャーに変換されたエスケープ クロージャの詳細な使用

1. 詳細な使用法のための高度な組み合わせを作成する AdvancedCombineBootcamp.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 インスタンス ビュー、FuturesBootcamp.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()
    }
}

おすすめ

転載: blog.csdn.net/u011193452/article/details/133905240