- import SwiftUI
- import Combine
-
- /// 数据服务
- class AdvancedCombineDataService{
- // @Published var basicPublisher: String = "first publish"
- // CurrentValueSubject 通用函数
- // let currentValuePublisher = CurrentValueSubject
("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
(), - // 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
() - // }
- // 传递新建的多播发布者
- .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()
- }
- }
- 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()
- }
- }