本章内容概览:Combine
上一节:【iOS】09_Swift编程规范、资料推荐
下一节:【iOS】11_Swift-Concurrency
同系列文章请查看:快乐码元 - iOS篇
Ⅰ.介绍
1.Combine 是什么?
WWDC 2019苹果推出Combine,Combine是一种响应式编程范式,采用声明式 的Swift API。
Combine 写代码的思路是你写代码不同于以往命令式的描述如何处理数据,Combine 是要去描述好数据会经过哪些逻辑运算处理。这样代码更好维护,可以有效的减少嵌套闭包以及分散的回调等使得代码维护麻烦的苦恼。
声明式和过程时区别可见如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func sum1 (arr : [Int ]) -> Int { var sum: Int = 0 for v in arr { sum += v } return sum } func sum2 (arr : [Int ]) -> Int { return arr.reduce(0 , + ) }
Combine 主要用来处理异步的事件和值。苹果 UI 框架都是在主线程上进行 UI 更新,Combine 通过 Publisher 的 receive 设置回主线程更新UI会非常的简单。
已有的 RxSwift 和 ReactiveSwift 框架和 Combine 的思路和用法类似。
2.Combine 的三个核心概念
简单举个发布数据和类属性绑定的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let pA = Just (0 )let _ = pA.sink { v in print ("pA is: \(v) " ) } let pB = [7 ,90 ,16 ,11 ].publisherlet _ = pB .sink { v in print ("pB: \(v) " ) } class AClass { var p: Int = 0 { didSet { print ("property update to \(p) " ) } } } let o = AClass ()let _ = pB.assign(to: \.p, on: o)
3.Combine 资料
官方文档链接 Combine | Apple Developer Documentation 。还有 Using Combine 这里有大量使用示例,内容较全。官方讨论Combine的论坛 Topics tagged combine 。StackOverflow上相关问题 Newest ‘combine’ Questions 。
WWDC上关于Combine的Session如下:
和Combine相关的Session:
Ⅱ.使用说明
1.publisher
publisher 是发布者,sink 是订阅者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import Combinevar cc = Set <AnyCancellable >()struct S { let p1: String let p2: String } [S (p1: "1" , p2: "one" ), S (p1: "2" , p2: "two" )] .publisher .print("array" ) .sink { print ($0 ) } .store(in: & cc)
输出:
1 2 3 4 5 6 7 array: receive subscription: ([yesmore的博客.AppDelegate.(unknown context at $10ac82d20).(unknown context at $10ac82da4).S(p1: "1", p2: "one"), yesmore的博客.AppDelegate.(unknown context at $10ac82d20).(unknown context at $10ac82da4).S(p1: "2", p2: "two")]) array: request unlimited array: receive value: (S(p1: "1", p2: "one")) S(p1: "1", p2: "one") array: receive value: (S(p1: "2", p2: "two")) S(p1: "2", p2: "two") array: receive finished
2.Just
Just 是发布者,发布的数据在初始化时完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import Combinevar cc = Set <AnyCancellable >()struct S { let p1: String let p2: String } let pb = Just (S (p1: "1" , p2: "one" ))pb .print("pb" ) .sink { print ($0 ) } .store(in: & cc)
输出:
1 2 3 4 5 pb: receive subscription: (Just ) pb: request unlimited pb: receive value: (S (p1: "1" , p2: "one" )) S (p1: "1" , p2: "one" )pb: receive finished
3.PassthroughSubject
PassthroughSubject 可以传递多值,订阅者可以是一个也可以是多个,send 指明 completion 后,订阅者就没法接收到新发送的值了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import Combinevar cc = Set <AnyCancellable >()struct S { let p1: String let p2: String } enum CError : Error { case aE, bE } let ps1 = PassthroughSubject <S , CError >()ps1 .print("ps1" ) .sink { c in print ("completion:" , c) } receiveValue: { s in print ("receive:" , s) } .store(in: & cc) ps1.send(S (p1: "1" , p2: "one" )) ps1.send(completion: .failure(CError .aE)) ps1.send(S (p1: "2" , p2: "two" )) ps1.send(completion: .finished) ps1.send(S (p1: "3" , p2: "three" )) let ps2 = PassthroughSubject <String , Never >()ps2.send("one" ) ps2.send("two" ) let sb1 = ps2 .print("ps2 sb1" ) .sink { s in print (s) } ps2.send("three" ) let sb2 = ps2 .print("ps2 sb2" ) .sink { s in print (s) } ps2.send("four" ) sb1.store(in: & cc) sb2.store(in: & cc) ps2.send(completion: .finished)
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ps1: receive subscription: (PassthroughSubject) ps1: request unlimited ps1: receive value: (S(p1: "1", p2: "one")) receive: S(p1: "1", p2: "one") ps1: receive error: (aE) completion: failure(戴铭的开发小册子.AppDelegate.(unknown context at $10b15ce10).(unknown context at $10b15cf3c).CError.aE) ps2 sb1: receive subscription: (PassthroughSubject) ps2 sb1: request unlimited ps2 sb1: receive value: (three) three ps2 sb2: receive subscription: (PassthroughSubject) ps2 sb2: request unlimited ps2 sb1: receive value: (four) four ps2 sb2: receive value: (four) four ps2 sb1: receive finished ps2 sb2: receive finished
4.Empty
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import Combinevar cc = Set <AnyCancellable >()struct S { let p1: String let p2: String } let ept = Empty <S , Never >() ept .print("ept" ) .sink { c in print ("completion:" , c) } receiveValue: { s in print ("receive:" , s) } .store(in: & cc) ept.replaceEmpty(with: S (p1: "1" , p2: "one" )) .sink { c in print ("completion:" , c) } receiveValue: { s in print ("receive:" , s) } .store(in: & cc)
输出:
1 2 3 4 5 6 ept: receive subscription: (Empty) ept: request unlimited ept: receive finished completion: finished receive: S(p1: "1", p2: "one") completion: finished
5.CurrentValueSubject
CurrentValueSubject 的订阅者可以收到订阅时已发出的那条数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import Combinevar cc = Set <AnyCancellable >()let cs = CurrentValueSubject <String , Never >("one" )cs.send("two" ) cs.send("three" ) let sb1 = cs .print("cs sb1" ) .sink { print ($0 ) } cs.send("four" ) cs.send("five" ) let sb2 = cs .print("cs sb2" ) .sink { print ($0 ) } cs.send("six" ) sb1.store(in: & cc) sb2.store(in: & cc)
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 cs sb1: receive subscription: (CurrentValueSubject) cs sb1: request unlimited cs sb1: receive value: (three) three cs sb1: receive value: (four) four cs sb1: receive value: (five) five cs sb2: receive subscription: (CurrentValueSubject) cs sb2: request unlimited cs sb2: receive value: (five) five cs sb1: receive value: (six) six cs sb2: receive value: (six) six cs sb1: receive cancel cs sb2: receive cancel
6.removeDuplicates
使用 removeDuplicates,重复的值就不会发送了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import Combinevar cc = Set <AnyCancellable >()let pb = ["one" ,"two" ,"three" ,"three" ,"four" ] .publisher let sb = pb .print("sb" ) .removeDuplicates() .sink { print ($0 ) } sb.store(in: & cc)
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 sb: receive subscription: (["one", "two", "three", "three", "four"]) sb: request unlimited sb: receive value: (one) one sb: receive value: (two) two sb: receive value: (three) three sb: receive value: (three) sb: request max: (1) (synchronous) sb: receive value: (four) four sb: receive finished
7.flatMap
flatMap 能将多个发布者的值打平发送给订阅者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Combinevar cc = Set <AnyCancellable >()struct S { let p: AnyPublisher <String , Never > } let s1 = S (p: Just ("one" ).eraseToAnyPublisher())let s2 = S (p: Just ("two" ).eraseToAnyPublisher())let s3 = S (p: Just ("three" ).eraseToAnyPublisher())let pb = [s1, s2, s3].publisher let sb = pb .print("sb" ) .flatMap { $0 .p } .sink { print ($0 ) } sb.store(in: & cc)
输出
1 2 3 4 5 6 7 8 9 sb: receive subscription: ([戴铭的开发小册子.AppDelegate.(unknown context at $101167070).(unknown context at $1011670f4).S(p: AnyPublisher), 戴铭的开发小册子.AppDelegate.(unknown context at $101167070).(unknown context at $1011670f4).S(p: AnyPublisher), 戴铭的开发小册子.AppDelegate.(unknown context at $101167070).(unknown context at $1011670f4).S(p: AnyPublisher)]) sb: request unlimited sb: receive value: (S(p: AnyPublisher)) one sb: receive value: (S(p: AnyPublisher)) two sb: receive value: (S(p: AnyPublisher)) three sb: receive finished
8.append
append 会在发布者发布结束后追加发送数据,发布者不结束,append 的数据不会发送。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import Combinevar cc = Set <AnyCancellable >()let pb = PassthroughSubject <String , Never >()let sb = pb .print("sb" ) .append("five" , "six" ) .sink { print ($0 ) } sb.store(in: & cc) pb.send("one" ) pb.send("two" ) pb.send("three" ) pb.send(completion: .finished)
输出
1 2 3 4 5 6 7 8 9 sb: receive subscription: ([戴铭的开发小册子.AppDelegate.(unknown context at $101167070).(unknown context at $1011670f4).S(p: AnyPublisher), 戴铭的开发小册子.AppDelegate.(unknown context at $101167070).(unknown context at $1011670f4).S(p: AnyPublisher), 戴铭的开发小册子.AppDelegate.(unknown context at $101167070).(unknown context at $1011670f4).S(p: AnyPublisher)]) sb: request unlimited sb: receive value: (S(p: AnyPublisher)) one sb: receive value: (S(p: AnyPublisher)) two sb: receive value: (S(p: AnyPublisher)) three sb: receive finished
9.prepend
prepend 会在发布者发布前先发送数据,发布者不结束也不会受影响。发布者和集合也可以被打平发布。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import Combinevar cc = Set <AnyCancellable >()let pb1 = PassthroughSubject <String , Never >()let pb2 = ["nine" , "ten" ].publisherlet sb = pb1 .print("sb" ) .prepend(pb2) .prepend(["seven" ,"eight" ]) .prepend("five" , "six" ) .sink { print ($0 ) } sb.store(in: & cc) pb1.send("one" ) pb1.send("two" ) pb1.send("three" )
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 five six seven eight nine ten sb: receive subscription: (PassthroughSubject) sb: request unlimited sb: receive value: (one) one sb: receive value: (two) two sb: receive value: (three) three sb: receive cancel
10.merge
订阅者可以通过 merge 合并多个发布者发布的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import Combinevar cc = Set <AnyCancellable >()let ps1 = PassthroughSubject <String , Never >()let ps2 = PassthroughSubject <String , Never >()let sb1 = ps1.merge(with: ps2) .sink { print ($0 ) } ps1.send("one" ) ps1.send("two" ) ps2.send("1" ) ps2.send("2" ) ps1.send("three" ) sb1.store(in: & cc)
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 sb1: receive subscription: (Merge) sb1: request unlimited sb1: receive value: (one) one sb1: receive value: (two) two sb1: receive value: (1) 1 sb1: receive value: (2) 2 sb1: receive value: (three) three sb1: receive cancel
11.zip
zip 会合并多个发布者发布的数据,只有当多个发布者都发布了数据后才会组合成一个数据给订阅者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import Combinevar cc = Set <AnyCancellable >()let ps1 = PassthroughSubject <String , Never >()let ps2 = PassthroughSubject <String , Never >()let ps3 = PassthroughSubject <String , Never >()let sb1 = ps1.zip(ps2, ps3) .print("sb1" ) .sink { print ($0 ) } ps1.send("one" ) ps1.send("two" ) ps1.send("three" ) ps2.send("1" ) ps2.send("2" ) ps1.send("four" ) ps2.send("3" ) ps3.send("一" ) sb1.store(in: & cc)
输出
1 2 3 4 5 sb1: receive subscription: (Zip) sb1: request unlimited sb1: receive value: (("one", "1", "一")) ("one", "1", "一") sb1: receive cancel
12.combineLatest
combineLatest 会合并多个发布者发布的数据,只有当多个发布者都发布了数据后才会触发合并,合并每个发布者发布的最后一个数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import Combinevar cc = Set <AnyCancellable >() let ps1 = PassthroughSubject <String , Never >()let ps2 = PassthroughSubject <String , Never >()let ps3 = PassthroughSubject <String , Never >()let sb1 = ps1.combineLatest(ps2, ps3) .print("sb1" ) .sink { print ($0 ) } ps1.send("one" ) ps1.send("two" ) ps1.send("three" ) ps2.send("1" ) ps2.send("2" ) ps1.send("four" ) ps2.send("3" ) ps3.send("一" ) ps3.send("二" ) sb1.store(in: & cc)
输出
1 2 3 4 5 6 7 sb1: receive subscription: (CombineLatest) sb1: request unlimited sb1: receive value: (("four", "3", "一")) ("four", "3", "一") sb1: receive value: (("four", "3", "二")) ("four", "3", "二") sb1: receive cancel
13.Scheduler
Scheduler 处理队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import Combinevar cc = Set <AnyCancellable >() let sb1 = ["one" ,"two" ,"three" ].publisher .print("sb1" ) .subscribe(on: DispatchQueue .global()) .handleEvents(receiveOutput: { print ("receiveOutput" ,$0 ) }) .receive(on: DispatchQueue .main) .sink { print ($0 ) } sb1.store(in: & cc)
输出
1 2 3 4 5 6 7 8 9 10 11 12 sb1: receive subscription: ([1, 2, 3]) sb1: request unlimited sb1: receive value: (1) receiveOutput 1 sb1: receive value: (2) receiveOutput 2 sb1: receive value: (3) receiveOutput 3 sb1: receive finished 1 2 3
Ⅲ.使用场景
1.网络请求
网络URLSession.dataTaskPublisher使用例子如下:
1 2 let req = URLRequest (url: URL (string: "http://www.starming.com" )! )let dpPublisher = URLSession .shared.dataTaskPublisher(for: req)
一个请求Github接口并展示结果的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 import SwiftUIimport Combinestruct CombineSearchAPI : View { var body: some View { GithubSearchView () } } struct GithubSearchView : View { @State var str: String = "Swift" @StateObject var ss: SearchStore = SearchStore () @State var repos: [GithubRepo ] = [] var body: some View { NavigationView { List { TextField ("输入:" , text: $str , onCommit: fetch) ForEach (self .ss.repos) { repo -> GithubRepoCell in GithubRepoCell (repo: repo) } } .navigationTitle("搜索" ) } .onAppear(perform: fetch) } private func fetch () { self .ss.search(str: self .str) } } struct GithubRepoCell : View { let repo: GithubRepo var body: some View { VStack (alignment: .leading, spacing: 20 ) { Text (self .repo.name) Text (self .repo.description) } } } struct GithubRepo : Decodable , Identifiable { let id: Int let name: String let description: String } struct GithubResp : Decodable { let items: [GithubRepo ] } final class GithubSearchManager { func search (str : String ) -> AnyPublisher <GithubResp , Never > { guard var urlComponents = URLComponents (string: "https://api.github.com/search/repositories" ) else { preconditionFailure ("链接无效" ) } urlComponents.queryItems = [URLQueryItem (name: "q" , value: str)] guard let url = urlComponents.url else { preconditionFailure ("链接无效" ) } let sch = DispatchQueue (label: "API" , qos: .default, attributes: .concurrent) return URLSession .shared .dataTaskPublisher(for: url) .receive(on: sch) .tryMap({ element -> Data in print (String (decoding: element.data, as: UTF8 .self )) return element.data }) .decode(type: GithubResp .self , decoder: JSONDecoder ()) .catch { _ in Empty ().eraseToAnyPublisher() } .eraseToAnyPublisher() } } final class SearchStore : ObservableObject { @Published var query: String = "" @Published var repos: [GithubRepo ] = [] private let searchManager: GithubSearchManager private var cancellable = Set <AnyCancellable >() init (searchManager : GithubSearchManager = GithubSearchManager ()) { self .searchManager = searchManager $query .debounce(for: .milliseconds(500 ), scheduler: RunLoop .main) .flatMap { query -> AnyPublisher <[GithubRepo ], Never > in return searchManager.search(str: query) .map { $0 .items } .eraseToAnyPublisher() } .receive(on: DispatchQueue .main) .assign(to: \.repos, on: self ) .store(in: & cancellable) } func search (str : String ) { self .query = str } }
抽象基础网络能力,方便扩展,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 import SwiftUIimport Combinestruct CombineAPI : View { var body: some View { RepListView (vm: .init ()) } } struct RepListView : View { @ObservedObject var vm: RepListVM var body: some View { NavigationView { List (vm.repos) { rep in RepListCell (rep: rep) } .alert(isPresented: $vm .isErrorShow) { () -> Alert in Alert (title: Text ("出错了" ), message: Text (vm.errorMessage)) } .navigationBarTitle(Text ("仓库" )) } .onAppear { vm.apply(.onAppear) } } } struct RepListCell : View { @State var rep: RepoModel var body: some View { HStack () { VStack () { AsyncImage (url: URL (string: rep.owner.avatarUrl ?? "" ), content: { image in image .resizable() .aspectRatio(contentMode: .fit) .frame(width: 100 , height: 100 ) }, placeholder: { ProgressView () .frame(width: 100 , height: 100 ) }) Text ("\(rep.owner.login) " ) .font(.system(size: 10 )) } VStack (alignment: .leading, spacing: 10 ) { Text ("\(rep.name) " ) .font(.title) Text ("\(rep.stargazersCount) " ) .font(.title3) Text ("\(String(describing: rep.description ?? "" )) " ) Text ("\(String(describing: rep.language ?? "" )) " ) .font(.title3) } .font(.system(size: 14 )) } } } final class RepListVM : ObservableObject , UnidirectionalDataFlowType { typealias InputType = Input private var cancellables: [AnyCancellable ] = [] enum Input { case onAppear } func apply (_ input : Input ) { switch input { case .onAppear: onAppearSubject.send(()) } } private let onAppearSubject = PassthroughSubject <Void , Never >() @Published private(set) var repos: [RepoModel ] = [] @Published var isErrorShow = false @Published var errorMessage = "" @Published private(set) var shouldShowIcon = false private let resSubject = PassthroughSubject <SearchRepoModel , Never >() private let errSubject = PassthroughSubject <APISevError , Never >() private let apiSev: APISev init (apiSev : APISev = APISev ()) { self .apiSev = apiSev bindInputs() bindOutputs() } private func bindInputs () { let req = SearchRepoRequest () let resPublisher = onAppearSubject .flatMap { [apiSev] in apiSev.response(from: req) .catch { [weak self ] error -> Empty <SearchRepoModel , Never > in self ? .errSubject.send(error) return .init () } } let resStream = resPublisher .share() .subscribe(resSubject) cancellables += [resStream] } private func bindOutputs () { let repStream = resSubject .map { $0 .items } .assign(to: \.repos, on: self ) let errMsgStream = errSubject .map { error -> String in switch error { case .resError: return "network error" case .parseError: return "parse error" } } .assign(to: \.errorMessage, on: self ) let errStream = errSubject .map { _ in true } .assign(to: \.isErrorShow, on: self ) cancellables += [repStream,errStream,errMsgStream] } } protocol UnidirectionalDataFlowType { associatedtype InputType func apply (_ input : InputType ) } struct SearchRepoRequest : APIReqType { typealias Res = SearchRepoModel var path: String { return "/search/repositories" } var qItems: [URLQueryItem ]? { return [ .init (name: "q" , value: "Combine" ), .init (name: "order" , value: "desc" ) ] } } struct SearchRepoModel : Decodable { var items: [RepoModel ] } struct RepoModel : Decodable , Hashable , Identifiable { var id: Int64 var name: String var fullName: String var description: String ? var stargazersCount: Int = 0 var language: String ? var owner: OwnerModel } struct OwnerModel : Decodable , Hashable , Identifiable { var id: Int64 var login: String var avatarUrl: String ? } protocol APIReqType { associatedtype Res : Decodable var path: String { get } var qItems: [URLQueryItem ]? { get } } protocol APISevType { func response <Request >(from req : Request ) -> AnyPublisher <Request .Res , APISevError > where Request : APIReqType } final class APISev : APISevType { private let rootUrl: URL init (rootUrl : URL = URL (string: "https://api.github.com" )! ) { self .rootUrl = rootUrl } func response <Request >(from req : Request ) -> AnyPublisher <Request .Res , APISevError > where Request : APIReqType { let path = URL (string: req.path, relativeTo: rootUrl)! var comp = URLComponents (url: path, resolvingAgainstBaseURL: true )! comp.queryItems = req.qItems print (comp.url? .description ?? "url wrong" ) var req = URLRequest (url: comp.url! ) req.addValue("application/json" , forHTTPHeaderField: "Content-Type" ) let de = JSONDecoder () de.keyDecodingStrategy = .convertFromSnakeCase return URLSession .shared.dataTaskPublisher(for: req) .map { data, res in print (String (decoding: data, as: UTF8 .self )) return data } .mapError { _ in APISevError .resError } .decode(type: Request .Res .self , decoder: de) .mapError(APISevError .parseError) .receive(on: RunLoop .main) .eraseToAnyPublisher() } } enum APISevError : Error { case resError case parseError(Error ) }
2.KVO
例子如下:
1 2 3 4 5 6 7 8 9 10 private final class KVOObject : NSObject { @objc dynamic var intV: Int = 0 @objc dynamic var boolV: Bool = false } let o = KVOObject ()let _ = o.publisher(for: \.intV) .sink { v in print ("value : \(v) " ) }
3.通知
使用例子如下:
1 2 3 4 5 6 7 8 extension Notification .Name { static let noti = Notification .Name ("nameofnoti" ) } let notiPb = NotificationCenter .default.publisher(for: .noti, object: nil ) .sink { print ($0 ) }
退到后台接受通知的例子如下:
1 2 3 4 5 6 7 8 9 10 11 class A { var storage = Set <AnyCancellable >() init () { NotificationCenter .default.publisher(for: UIWindowScene .didEnterBackgroundNotification) .sink { _ in print ("enter background" ) } .store(in: & self .storage) } }
4.Timer
使用方式如下:
1 2 3 4 5 let timePb = Timer .publish(every: 1.0 , on: RunLoop .main, in: .default)let timeSk = timePb.sink { r in print ("r is \(r) " ) } let cPb = timePb.connect()
参考资料:
Tips:
Please indicate the source and original author when reprinting or quoting this article.