You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

684 lines
17 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. [![CircleCI](https://img.shields.io/circleci/project/github/RxSwiftCommunity/RxSwiftExt/master.svg)](https://circleci.com/gh/RxSwiftCommunity/RxSwiftExt/tree/master)
  2. ![pod](https://img.shields.io/cocoapods/v/RxSwiftExt.svg)
  3. [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
  4. RxSwiftExt
  5. ===========
  6. If you're using [RxSwift](https://github.com/ReactiveX/RxSwift), you may have encountered situations where the built-in operators do not bring the exact functionality you want. The RxSwift core is being intentionally kept as compact as possible to avoid bloat. This repository's purpose is to provide additional convenience operators and Reactive Extensions.
  7. Installation
  8. ===========
  9. This branch of RxSwiftExt targets Swift 5.x and RxSwift 5.0.0 or later.
  10. * If you're looking for the Swift 4 version of RxSwiftExt, please use version `3.4.0` of the framework.
  11. #### CocoaPods
  12. Add to your `Podfile`:
  13. ```ruby
  14. pod 'RxSwiftExt', '~> 5'
  15. ```
  16. This will install both the `RxSwift` and `RxCocoa` extensions.
  17. If you're interested in only installing the `RxSwift` extensions, without the `RxCocoa` extensions, simply use:
  18. ```ruby
  19. pod 'RxSwiftExt/Core'
  20. ```
  21. Using Swift 4:
  22. ```ruby
  23. pod 'RxSwiftExt', '~> 3'
  24. ```
  25. #### Carthage
  26. Add this to your `Cartfile`
  27. ```
  28. github "RxSwiftCommunity/RxSwiftExt"
  29. ```
  30. Operators
  31. ===========
  32. RxSwiftExt is all about adding operators and Reactive Extensions to [RxSwift](https://github.com/ReactiveX/RxSwift)!
  33. ## Operators
  34. These operators are much like the RxSwift & RxCocoa core operators, but provide additional useful abilities to your Rx arsenal.
  35. * [unwrap](#unwrap)
  36. * [ignore](#ignore)
  37. * [ignoreWhen](#ignorewhen)
  38. * [Observable.once](#once)
  39. * [distinct](#distinct)
  40. * [map](#map)
  41. * [not](#not)
  42. * [and](#and)
  43. * [Observable.cascade](#cascade)
  44. * [pairwise](#pairwise)
  45. * [nwise](#nwise)
  46. * [retry](#retry)
  47. * [repeatWithBehavior](#repeatwithbehavior)
  48. * [catchErrorJustComplete](#catcherrorjustcomplete)
  49. * [pausable](#pausable)
  50. * [pausableBuffered](#pausablebuffered)
  51. * [apply](#apply)
  52. * [filterMap](#filtermap)
  53. * [Observable.fromAsync](#fromasync)
  54. * [Observable.zip(with:)](#zipwith)
  55. * [Observable.merge(with:)](#mergewith)
  56. * [withUnretained](#withunretained)
  57. * [count](#count)
  58. * [partition](#partition)
  59. * [bufferWithTrigger](#bufferWithTrigger)
  60. There are two more available operators for `materialize()`'d sequences:
  61. * [errors](#errors-elements)
  62. * [elements](#errors-elements)
  63. Read below for details about each operator.
  64. ## Reactive Extensions
  65. RxSwift/RxCocoa Reactive Extensions are provided to enhance existing objects and classes from the Apple-ecosystem with Reactive abilities.
  66. * [UIViewPropertyAnimator.animate](#uiviewpropertyanimatoranimate)
  67. * [UIScrollView.reachedBottom](#uiscrollviewreachedbottom)
  68. --------
  69. Operator details
  70. ===========
  71. #### unwrap
  72. Unwrap optionals and filter out nil values.
  73. ```swift
  74. Observable.of(1,2,nil,Int?(4))
  75. .unwrap()
  76. .subscribe { print($0) }
  77. ```
  78. ```
  79. next(1)
  80. next(2)
  81. next(4)
  82. ```
  83. #### ignore
  84. Ignore specific elements.
  85. ```swift
  86. Observable.from(["One","Two","Three"])
  87. .ignore("Two")
  88. .subscribe { print($0) }
  89. ```
  90. ```
  91. next(One)
  92. next(Three)
  93. completed
  94. ```
  95. #### ignoreWhen
  96. Ignore elements according to closure.
  97. ```swift
  98. Observable<Int>
  99. .of(1,2,3,4,5,6)
  100. .ignoreWhen { $0 > 2 && $0 < 6 }
  101. .subscribe { print($0) }
  102. ```
  103. ```
  104. next(1)
  105. next(2)
  106. next(6)
  107. completed
  108. ```
  109. #### once
  110. Send a next element exactly once to the first subscriber that takes it. Further subscribers get an empty sequence.
  111. ```swift
  112. let obs = Observable.once("Hello world")
  113. print("First")
  114. obs.subscribe { print($0) }
  115. print("Second")
  116. obs.subscribe { print($0) }
  117. ```
  118. ```
  119. First
  120. next(Hello world)
  121. completed
  122. Second
  123. completed
  124. ```
  125. #### distinct
  126. Pass elements through only if they were never seen before in the sequence.
  127. ```swift
  128. Observable.of("a","b","a","c","b","a","d")
  129. .distinct()
  130. .subscribe { print($0) }
  131. ```
  132. ```
  133. next(a)
  134. next(b)
  135. next(c)
  136. next(d)
  137. completed
  138. ```
  139. #### mapTo
  140. Replace every element with the provided value.
  141. ```swift
  142. Observable.of(1,2,3)
  143. .mapTo("Nope.")
  144. .subscribe { print($0) }
  145. ```
  146. ```
  147. next(Nope.)
  148. next(Nope.)
  149. next(Nope.)
  150. completed
  151. ```
  152. #### mapAt
  153. Transform every element to the value at the provided key path.
  154. ```swift
  155. struct Person {
  156. let name: String
  157. }
  158. Observable
  159. .of(
  160. Person(name: "Bart"),
  161. Person(name: "Lisa"),
  162. Person(name: "Maggie")
  163. )
  164. .mapAt(\.name)
  165. .subscribe { print($0) }
  166. ```
  167. ```
  168. next(Bart)
  169. next(Lisa)
  170. next(Maggie)
  171. completed
  172. ```
  173. #### not
  174. Negate booleans.
  175. ```swift
  176. Observable.just(false)
  177. .not()
  178. .subscribe { print($0) }
  179. ```
  180. ```
  181. next(true)
  182. completed
  183. ```
  184. #### and
  185. Verifies that every value emitted is `true`
  186. ```swift
  187. Observable.of(true, true)
  188. .and()
  189. .subscribe { print($0) }
  190. Observable.of(true, false)
  191. .and()
  192. .subscribe { print($0) }
  193. Observable<Bool>.empty()
  194. .and()
  195. .subscribe { print($0) }
  196. ```
  197. Returns a `Maybe<Bool>`:
  198. ```
  199. success(true)
  200. success(false)
  201. completed
  202. ```
  203. #### cascade
  204. Sequentially cascade through a list of observables, dropping previous subscriptions as soon as an observable further down the list starts emitting elements.
  205. ```swift
  206. let a = PublishSubject<String>()
  207. let b = PublishSubject<String>()
  208. let c = PublishSubject<String>()
  209. Observable.cascade([a,b,c])
  210. .subscribe { print($0) }
  211. a.onNext("a:1")
  212. a.onNext("a:2")
  213. b.onNext("b:1")
  214. a.onNext("a:3")
  215. c.onNext("c:1")
  216. a.onNext("a:4")
  217. b.onNext("b:4")
  218. c.onNext("c:2")
  219. ```
  220. ```
  221. next(a:1)
  222. next(a:2)
  223. next(b:1)
  224. next(c:1)
  225. next(c:2)
  226. ```
  227. #### pairwise
  228. Groups elements emitted by an Observable into arrays, where each array consists of the last 2 consecutive items; similar to a sliding window.
  229. ```swift
  230. Observable.from([1, 2, 3, 4, 5, 6])
  231. .pairwise()
  232. .subscribe { print($0) }
  233. ```
  234. ```
  235. next((1, 2))
  236. next((2, 3))
  237. next((3, 4))
  238. next((4, 5))
  239. next((5, 6))
  240. completed
  241. ```
  242. #### nwise
  243. Groups elements emitted by an Observable into arrays, where each array consists of the last N consecutive items; similar to a sliding window.
  244. ```swift
  245. Observable.from([1, 2, 3, 4, 5, 6])
  246. .nwise(3)
  247. .subscribe { print($0) }
  248. ```
  249. ```
  250. next([1, 2, 3])
  251. next([2, 3, 4])
  252. next([3, 4, 5])
  253. next([4, 5, 6])
  254. completed
  255. ```
  256. #### retry
  257. Repeats the source observable sequence using given behavior in case of an error or until it successfully terminated.
  258. There are four behaviors with various predicate and delay options: `immediate`, `delayed`, `exponentialDelayed` and
  259. `customTimerDelayed`.
  260. ```swift
  261. // in case of an error initial delay will be 1 second,
  262. // every next delay will be doubled
  263. // delay formula is: initial * pow(1 + multiplier, Double(currentAttempt - 1)), so multiplier 1.0 means, delay will doubled
  264. _ = sampleObservable.retry(.exponentialDelayed(maxCount: 3, initial: 1.0, multiplier: 1.0), scheduler: delayScheduler)
  265. .subscribe(onNext: { event in
  266. print("Receive event: \(event)")
  267. }, onError: { error in
  268. print("Receive error: \(error)")
  269. })
  270. ```
  271. ```
  272. Receive event: First
  273. Receive event: Second
  274. Receive event: First
  275. Receive event: Second
  276. Receive event: First
  277. Receive event: Second
  278. Receive error: fatalError
  279. ```
  280. #### repeatWithBehavior
  281. Repeats the source observable sequence using given behavior when it completes. This operator takes the same parameters as the [retry](#retry) operator.
  282. There are four behaviors with various predicate and delay options: `immediate`, `delayed`, `exponentialDelayed` and `customTimerDelayed`.
  283. ```swift
  284. // when the sequence completes initial delay will be 1 second,
  285. // every next delay will be doubled
  286. // delay formula is: initial * pow(1 + multiplier, Double(currentAttempt - 1)), so multiplier 1.0 means, delay will doubled
  287. _ = completingObservable.repeatWithBehavior(.exponentialDelayed(maxCount: 3, initial: 1.0, multiplier: 1.2), scheduler: delayScheduler)
  288. .subscribe(onNext: { event in
  289. print("Receive event: \(event)")
  290. })
  291. ```
  292. ```
  293. Receive event: First
  294. Receive event: Second
  295. Receive event: First
  296. Receive event: Second
  297. Receive event: First
  298. Receive event: Second
  299. ```
  300. #### catchErrorJustComplete
  301. Completes a sequence when an error occurs, dismissing the error condition
  302. ```swift
  303. let _ = sampleObservable
  304. .do(onError: { print("Source observable emitted error \($0), ignoring it") })
  305. .catchErrorJustComplete()
  306. .subscribe {
  307. print ("\($0)")
  308. }
  309. ```
  310. ```
  311. next(First)
  312. next(Second)
  313. Source observable emitted error fatalError, ignoring it
  314. completed
  315. ```
  316. #### pausable
  317. Pauses the elements of the source observable sequence unless the latest element from the second observable sequence is `true`.
  318. ```swift
  319. let observable = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
  320. let trueAtThreeSeconds = Observable<Int>.timer(3, scheduler: MainScheduler.instance).map { _ in true }
  321. let falseAtFiveSeconds = Observable<Int>.timer(5, scheduler: MainScheduler.instance).map { _ in false }
  322. let pauser = Observable.of(trueAtThreeSeconds, falseAtFiveSeconds).merge()
  323. let pausedObservable = observable.pausable(pauser)
  324. let _ = pausedObservable
  325. .subscribe { print($0) }
  326. ```
  327. ```
  328. next(2)
  329. next(3)
  330. ```
  331. More examples are available in the project's Playground.
  332. #### pausableBuffered
  333. Pauses the elements of the source observable sequence unless the latest element from the second observable sequence is `true`. Elements emitted by the source observable are buffered (with a configurable limit) and "flushed" (re-emitted) when the observable resumes.
  334. Examples are available in the project's Playground.
  335. #### apply
  336. Apply provides a unified mechanism for applying transformations on Observable
  337. sequences, without having to extend ObservableType or repeating your
  338. transformations. For additional rationale for this see
  339. [discussion on github](https://github.com/RxSwiftCommunity/RxSwiftExt/issues/73)
  340. ```swift
  341. // An ordinary function that applies some operators to its argument, and returns the resulting Observable
  342. func requestPolicy(_ request: Observable<Void>) -> Observable<Response> {
  343. return request.retry(maxAttempts)
  344. .do(onNext: sideEffect)
  345. .map { Response.success }
  346. .catchError { error in Observable.just(parseRequestError(error: error)) }
  347. // We can apply the function in the apply operator, which preserves the chaining style of invoking Rx operators
  348. let resilientRequest = request.apply(requestPolicy)
  349. ```
  350. #### filterMap
  351. A common pattern in Rx is to filter out some values, then map the remaining ones to something else. `filterMap` allows you to do this in one step:
  352. ```swift
  353. // keep only even numbers and double them
  354. Observable.of(1,2,3,4,5,6)
  355. .filterMap { number in
  356. (number % 2 == 0) ? .ignore : .map(number * 2)
  357. }
  358. ```
  359. The sequence above keeps even numbers 2, 4, 6 and produces the sequence 4, 8, 12.
  360. #### errors, elements
  361. These operators only apply to observable serquences that have been materialized with the `materialize()` operator (from RxSwift core). `errors` returns a sequence of filtered error events, ommitting elements. `elements` returns a sequence of filtered element events, ommitting errors.
  362. ```swift
  363. let imageResult = _chooseImageButtonPressed.asObservable()
  364. .flatMap { imageReceiver.image.materialize() }
  365. .share()
  366. let image = imageResult
  367. .elements()
  368. .asDriver(onErrorDriveWith: .never())
  369. let errorMessage = imageResult
  370. .errors()
  371. .map(mapErrorMessages)
  372. .unwrap()
  373. .asDriver(onErrorDriveWith: .never())
  374. ```
  375. #### fromAsync
  376. Turns simple asynchronous completion handlers into observable sequences. Suitable for use with existing asynchronous services which call a completion handler with only one parameter. Emits the result produced by the completion handler then completes.
  377. ```swift
  378. func someAsynchronousService(arg1: String, arg2: Int, completionHandler:(String) -> Void) {
  379. // a service that asynchronously calls
  380. // the given completionHandler
  381. }
  382. let observableService = Observable
  383. .fromAsync(someAsynchronousService)
  384. observableService("Foo", 0)
  385. .subscribe(onNext: { (result) in
  386. print(result)
  387. })
  388. .disposed(by: disposeBag)
  389. ```
  390. #### zip(with:)
  391. Convenience version of `Observable.zip(_:)`. Merges the specified observable sequences into one observable sequence by using the selector function whenever all
  392. of the observable sequences have produced an element at a corresponding index.
  393. ```swift
  394. let first = Observable.from(numbers)
  395. let second = Observable.from(strings)
  396. first.zip(with: second) { i, s in
  397. s + String(i)
  398. }.subscribe(onNext: { (result) in
  399. print(result)
  400. })
  401. ```
  402. ```
  403. next("a1")
  404. next("b2")
  405. next("c3")
  406. ```
  407. #### merge(with:)
  408. Convenience version of `Observable.merge(_:)`. Merges elements from the observable sequence with those of a different observable sequences into a single observable sequence.
  409. ```swift
  410. let oddStream = Observable.of(1, 3, 5)
  411. let evenStream = Observable.of(2, 4, 6)
  412. let otherStream = Observable.of(1, 5, 6)
  413. oddStream.merge(with: evenStream, otherStream)
  414. .subscribe(onNext: { result in
  415. print(result)
  416. })
  417. ```
  418. ```
  419. 1 2 1 3 4 5 5 6 6
  420. ```
  421. #### ofType
  422. The ofType operator filters the elements of an observable sequence, if that is an instance of the supplied type.
  423. ```swift
  424. Observable.of(NSNumber(value: 1),
  425. NSDecimalNumber(string: "2"),
  426. NSNumber(value: 3),
  427. NSNumber(value: 4),
  428. NSDecimalNumber(string: "5"),
  429. NSNumber(value: 6))
  430. .ofType(NSDecimalNumber.self)
  431. .subscribe { print($0) }
  432. ```
  433. ```
  434. next(2)
  435. next(5)
  436. completed
  437. ```
  438. This example emits 2, 5 (`NSDecimalNumber` Type).
  439. #### withUnretained
  440. The `withUnretained(_:resultSelector:)` operator provides an unretained, safe to use (i.e. not implicitly unwrapped), reference to an object along with the events emitted by the sequence.
  441. In the case the provided object cannot be retained successfully, the seqeunce will complete.
  442. ```swift
  443. class TestClass: CustomStringConvertible {
  444. var description: String { return "Test Class" }
  445. }
  446. Observable
  447. .of(1, 2, 3, 5, 8, 13, 18, 21, 23)
  448. .withUnretained(testClass)
  449. .do(onNext: { _, value in
  450. if value == 13 {
  451. // When testClass becomes nil, the next emission of the original
  452. // sequence will try to retain it and fail. As soon as it fails,
  453. // the sequence will complete.
  454. testClass = nil
  455. }
  456. })
  457. .subscribe()
  458. ```
  459. ```
  460. next((Test Class, 1))
  461. next((Test Class, 2))
  462. next((Test Class, 3))
  463. next((Test Class, 5))
  464. next((Test Class, 8))
  465. next((Test Class, 13))
  466. completed
  467. ```
  468. #### [count](http://reactivex.io/documentation/operators/count.html)
  469. Emits the number of items emitted by an Observable once it terminates with no errors. If a predicate is given, only elements matching the predicate will be counted.
  470. ```swift
  471. Observable.from([1, 2, 3, 4, 5, 6])
  472. .count { $0 % 2 == 0 }
  473. .subscribe()
  474. ```
  475. ```
  476. next(3)
  477. completed
  478. ```
  479. #### partition
  480. Partition a stream into two separate streams of elements that match, and don't match, the provided predicate.
  481. ```swift
  482. let numbers = Observable
  483. .of(1, 2, 3, 4, 5, 6)
  484. let (evens, odds) = numbers.partition { $0 % 2 == 0 }
  485. _ = evens.debug("even").subscribe() // emits 2, 4, 6
  486. _ = odds.debug("odds").subscribe() // emits 1, 3, 5
  487. ```
  488. #### bufferWithTrigger
  489. Collects the elements of the source observable, and emits them as an array when the trigger emits.
  490. ```swift
  491. let observable = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
  492. let signalAtThreeSeconds = Observable<Int>.timer(3, scheduler: MainScheduler.instance).map { _ in () }
  493. let signalAtFiveSeconds = Observable<Int>.timer(5, scheduler: MainScheduler.instance).map { _ in () }
  494. let trigger = Observable.of(signalAtThreeSeconds, signalAtFiveSeconds).merge()
  495. let buffered = observable.bufferWithTrigger(trigger)
  496. buffered.subscribe { print($0) }
  497. // prints next([0, 1, 2]) @ 3, next([3, 4]) @ 5
  498. ```
  499. A live demonstration is available in the Playground.
  500. Reactive Extensions details
  501. ===========
  502. #### UIViewPropertyAnimator.animate
  503. The `animate(afterDelay:)` operator provides a Completable that triggers the animation upon subscription and completes when the animation ends.
  504. ```swift
  505. button.rx.tap
  506. .flatMap {
  507. animator1.rx.animate()
  508. .andThen(animator2.rx.animate(afterDelay: 0.15))
  509. .andThen(animator3.rx.animate(afterDelay: 0.1))
  510. }
  511. ```
  512. #### UIViewPropertyAnimator.fractionComplete
  513. The `fractionComplete` binder provides a reactive way to bind to `UIViewPropertyAnimator.fractionComplete`.
  514. ```swift
  515. slider.rx.value.map(CGFloat.init)
  516. .bind(to: animator.rx.fractionComplete)
  517. ```
  518. #### UIScrollView.reachedBottom
  519. `reachedBottom` provides a sequence that emits every time the `UIScrollView` is scrolled to the bottom, with an optional offset.
  520. ```swift
  521. tableView.rx.reachedBottom(offset: 40)
  522. .subscribe { print("Reached bottom") }
  523. ```
  524. ## License
  525. This library belongs to _RxSwift Community_.
  526. RxSwiftExt is available under the MIT license. See the LICENSE file for more info.