desc "Deploy a new version to the App Store" lane :releasedo remove86framework gym add86framework deliver(force:true) # frameit end
desc "Remove x86 framework" lane :remove86frameworkdo sh(%( cd ../Pods/HyphenateLite/iOS_Pods_IMSDK* pwd if [ ! -d "./bak" ]; then mkdir ./bak fi if [ -d "./bak/HyphenateLite.framework" ]; then rm -rf ./bak/HyphenateLite.framework fi cp -r HyphenateLite.framework ./bak lipo HyphenateLite.framework/HyphenateLite -thin armv7 -output HyphenateLite_armv7 lipo HyphenateLite.framework/HyphenateLite -thin arm64 -output HyphenateLite_arm64 lipo -create HyphenateLite_armv7 HyphenateLite_arm64 -output HyphenateLite mv HyphenateLite HyphenateLite.framework/ )) end
desc "Add x86 framework back" lane :add86frameworkdo sh(%( cd ../Pods/HyphenateLite/iOS_Pods_IMSDK* pwd if [ -d "./HyphenateLite.framework" ]; then rm -rf ./HyphenateLite.framework fi cp -r ./bak/HyphenateLite.framework HyphenateLite.framework )) end
let textSearch = searchInput.flatMap { text in returnApiController.shared.currentWeather(city: text ??"Error") .do(onNext: { data in iflet text = text { self.cache[text] = data } }, onError: { [weakself] e in guardlet strongSelf =selfelse { return } DispatchQueue.main.async { strongSelf.showError(error: e) } }) .retryWhen(retryHandler) .catchError { error in iflet text = text, let cachedData =self.cache[text] { returnObservable.just(cachedData) } else { returnObservable.just(ApiController.Weather.empty) } } }
错误的重试机制
关于重试上面已经设计到了 .retryWhen(retryHandler),具体处理实现如下:
1 2 3 4 5 6 7 8 9 10 11
let retryHandler: (Observable<Error>) -> Observable<Int> = { e in return e.enumerated().flatMap { (attempt, error) -> Observable<Int> in if attempt >= maxAttempts -1 { returnObservable.error(error) } elseiflet casted = error as?ApiController.ApiError, casted == .invalidKey { returnApiController.shared.apiKey.filter {$0!=""}.map { _inreturn1 } } print("== retrying after \(attempt +1) seconds ==") returnObservable<Int>.timer(Double(attempt +1), scheduler: MainScheduler.instance).take(1) } }
或者简化版:.retry(3)
自定义错误
创建自定义错误遵循一般的 Swift 原则, 所以没有一个好的 Swift 程序员不会知道, 但它仍然是好的, 看看如何处理错误和创建定制的操作符。
创建自定义错误
先定义错误枚举,然后在结果过滤中抛出定义的错误即可。
1 2 3 4 5
enumApiError: Error { case cityNotFound case serverFailure case invalidKey }
fruit 是在主线程上生成的, 但是将它移动到后台线程是很好的。要在后台线程中创建 fruit , 必须使用 subscribeOn。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
let fruit =Observable<String>.create { observer in observer.onNext("[apple]") sleep(2) observer.onNext("[pineapple]") sleep(2) observer.onNext("[strawberry]") returnDisposables.create() }
fruit .subscribeOn(globalScheduler) .dump() .observeOn(MainScheduler.instance) .dumpingSubscription() .disposed(by: bag)
00s | [D] [dog] received on Main Thread 00s | [S] [dog] received on Anonymous Thread 03s | [D] [cat] received on Animals Thread 03s | [S] [cat] received on Anonymous Thread 06s | [D] [tiger] received on Animals Thread 06s | [S] [tiger] received on Anonymous Thread 09s | [D] [fox] received on Animals Thread 09s | [S] [fox] received on Anonymous Thread 12s | [D] [leopard] received on Animals Thread 12s | [S] [leopard] received on Anonymous Thread
let globalScheduler =ConcurrentDispatchQueueScheduler(queue: DispatchQueue.global()) let bag =DisposeBag() let animal =BehaviorSubject(value: "[dog]")
let fruit =Observable<String>.create { observer in observer.onNext("[apple]") sleep(2) observer.onNext("[pineapple]") sleep(2) observer.onNext("[strawberry]") returnDisposables.create() }
00s | [D] [apple] received on Anonymous Thread 00s | [S] [apple] received on Main Thread 02s | [D] [pineapple] received on Anonymous Thread 02s | [S] [pineapple] received on Main Thread 04s | [D] [strawberry] received on Anonymous Thread 04s | [S] [strawberry] received on Main Thread
functestToArray() { let scheduler =ConcurrentDispatchQueueScheduler(qos: .default) let toArrayObservable =Observable.of(1, 2).subscribeOn(scheduler) XCTAssertEqual(try! toArrayObservable.toBlocking().toArray(), [1, 2]) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
functestToArrayMaterialized() { let scheduler =ConcurrentDispatchQueueScheduler(qos: .default)
let toArrayObservable =Observable.of(1, 2).subscribeOn(scheduler)
let result = toArrayObservable .toBlocking() .materialize()
switch result { case .completed(elements: let elements): XCTAssertEqual(elements, [1, 2]) case .failed(_, error: let error): XCTFail(error.localizedDescription) } }
funcresponse(request: URLRequest) -> Observable<(HTTPURLResponse, Data)> { returnObservable.create { observer in // content goes here
let task =self.base.dataTask(with: request) { (data, response, error) in guardlet response = response, let data = data else { observer.on(.error(error ??RxURLSessionError.unknown)) return }
let s =URLSession.shared.rx.data(request: request) .observeOn(MainScheduler.instance) .subscribe(onNext: { imageData in self.gifImageView.animate(withGIFData: imageData) self.activityIndicator.stopAnimating() }) disposable.setDisposable(s) }
classiGifTests: XCTestCase { let obj = ["array":["foo","bar"], "foo":"bar"] as [String : Any] let request =URLRequest(url: URL(string: "http://raywenderlich.com")!) let errorRequest =URLRequest(url: URL(string: "http://rw.com")!) overridefuncsetUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. stub(condition: isHost("raywenderlich.com")) { _in returnOHHTTPStubsResponse(jsonObject: self.obj, statusCode: 200, headers: nil) } stub(condition: isHost("rw.com")) { _in returnOHHTTPStubsResponse(error: RxURLSessionError.unknown) } } overridefunctearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() OHHTTPStubs.removeAllStubs() } functestData() { let observable =URLSession.shared.rx.data(request: self.request) expect(observable.toBlocking().firstOrNil()).toNot(beNil()) }
functestString() { let observable =URLSession.shared.rx.string(request: self.request) let string ="{\"array\":[\"foo\",\"bar\"],\"foo\":\"bar\"}" expect(observable.toBlocking().firstOrNil()) == string }
functestJSON() { let observable =URLSession.shared.rx.json(request: self.request) let string ="{\"array\":[\"foo\",\"bar\"],\"foo\":\"bar\"}" let json =try?JSON(data: string.data(using: .utf8)!) expect(observable.toBlocking().firstOrNil()) == json }
functestError() { var erroredCorrectly =false let observable =URLSession.shared.rx.json(request: self.errorRequest) do { let_=try observable.toBlocking().first() assertionFailure() } catch (RxURLSessionError.unknown) { erroredCorrectly =true } catch { assertionFailure() } expect(erroredCorrectly) ==true } }
let textSearch = searchInput.flatMap { text in returnApiController.shared.currentWeather(city: text ??"Error") .catchErrorJustReturn(ApiController.Weather.dummy) }
let mapInput = mapView.rx.regionDidChangeAnimated .skip(1) .map { _inself.mapView.centerCoordinate }
let mapSearch = mapInput.flatMap { coordinate in returnApiController.shared.currentWeather(lat: coordinate.latitude, lon: coordinate.longitude) .catchErrorJustReturn(ApiController.Weather.dummy) }
let currentLocation = locationManager.rx.didUpdateLocations .map { locations in return locations[0] } .filter { location in return location.horizontalAccuracy < kCLLocationAccuracyHundredMeters }
let geoInput = geoLocationButton.rx.tap.asObservable() .do(onNext: { self.locationManager.requestWhenInUseAuthorization() self.locationManager.startUpdatingLocation() })
let geoLocation = geoInput.flatMap { return currentLocation.take(1) }
let geoSearch = geoLocation.flatMap { location in returnApiController.shared.currentWeather(lat: location.coordinate.latitude, lon: location.coordinate.longitude) .catchErrorJustReturn(ApiController.Weather.dummy) } let search =Observable.from([geoSearch, textSearch, mapSearch]) .merge() .asDriver(onErrorJustReturn: ApiController.Weather.dummy)
privatefuncerrorMessage() { alert(title: "No access to Camera Roll", text: "You can grant access to Combinestagram from the Settings app") .asObservable() .take(5.0, scheduler: MainScheduler.instance) .subscribe(onCompleted: { [weakself] in self?.dismiss(animated: true, completion: nil) _=self?.navigationController?.popViewController(animated: true) }) .disposed(by: bag) }
// Only one thing to point out here that’s different from the previous example of flatMap: // Changing ryan’s score here will have no effect. It will not be printed out. This is because flatMapLatest has already switched to the latest observable, for charlotte.
这一章将向你展示几种不同的组合序列的方法, 以及如何在每个序列中组合数据。你将使用的一些操作员与 Swift 集合操作员非常相似。它们有助于结合异步序列中的元素, 就像使用 Swift 数组一样。
前缀和串联
从 startWith(_:) 运算符赋值给定初始值的可观察序列。此值必须与可观察元素的类型相同。
1 2
let numbers =Observable.of(2, 3, 4) let observable = numbers.startWith(1) observable.subscribe(onNext: { value inprint(value) })
类似地 .concat(_:) 可以合并两个序列。
1 2 3
let first =Observable.of(1, 2, 3) let second =Observable.of(4, 5, 6) let observable =Observable.concat([first, second]) observable.subscribe(onNext: { value inprint(value) })
Right: Madrid Right: Barcelona Left: Berlin Left: Munich Left: Frankfurt Right: Valencia
Combining 元素
RxSwift中一组基本的运算符是 combineLatest 。他们组合来自几个序列的值:
Every time one of the inner (combined) sequences emits a value, it calls a closure you provide. You receive the last value from each of the inner sequences. This has many concrete applications, such as observing several text fields at once and combining their value, watching the status of multiple sources, and so on.
let left =PublishSubject<String>() let right =PublishSubject<String>()
let observable =Observable.combineLatest(left, right, resultSelector: { lastLeft, lastRight in "\(lastLeft)\(lastRight)" }) let disposable = observable.subscribe(onNext: { value in print(value) })
print("> Sending a value to Left") left.onNext("Hello,") print("> Sending a value to Right") right.onNext("world") print("> Sending another value to Right") right.onNext("RxSwift") print("> Sending another value to Left") left.onNext("Have a good day,")
disposable.dispose()
Console 输出:
1 2 3 4 5 6 7
> Sending a value to Left > Sending a value to Right Hello, world > Sending another value to Right Hello, RxSwift > Sending another value to Left Have a good day, RxSwift
zip 总是按照顺序一对一对的组合。类似把两个数组按照相同的下标进行组合。
1 2 3 4 5 6 7 8 9 10 11 12 13
enumWeather { case cloudy case sunny } let left: Observable<Weather> =Observable.of(.sunny, .cloudy, .cloudy, .sunny) let right =Observable.of("Lisbon", "Copenhagen", "London", "Madrid", "Vienna")
let observable =Observable.zip(left, right) { weather, city in return"It's \(weather) in \(city)" } observable.subscribe(onNext: { value in print(value) })
Console 输出:
1 2 3 4
It's sunny in Lisbon It's cloudy in Copenhagen It's cloudy in London It's sunny in Madrid
RxSwift 和 Combine(以及 Swift 中的其他相应式编程框架)共享许多通用语言和非常相似的概念。
RxSwift是一个较旧的,完善的框架,具有一些自己的原始概念,运算符名称和类型多样性,这主要是由于其多平台跨语言标准,该标准也适用于Linux,这对于Server-Side Swift非常有用。它也是开源的,所以如果你愿意,你可以直接为其核心做出贡献,并确切地看到它的特定部分是如何工作的。它与所有支持 Swift 的 Apple 平台版本兼容,一直支持 iOS 8。
Combine 是 Apple 新的、闪亮的框架,涵盖了类似的概念,但专门针对 Swift 和 Apple 自己的平台量身定制。它与 Swift 标准库共享许多通用语言,因此即使新手也觉得 API 非常熟悉。它仅支持从iOS 13,macOS 10.15等开始的较新的Apple平台。不幸的是,截至今天,它还没有开源,并且不支持Linux。
let observable =Observable<Int>.range(start: 1, count: 10) observable .subscribe(onNext: { i in let n =Double(i) let fibonacci =Int(((pow(1.61803, n) - pow(0.61803, n)) / 2.23606).rounded()) print(fibonacci) })
/// Represents an object that is both an observable sequence as well as an observer. /// Each notification is broadcasted to all subscribed observers. publicfinalclassPublishSubject<Element>
/// Represents an object that is both an observable sequence as well as an observer. /// /// Each notification is broadcasted to all subscribed and future observers, subject to buffer trimming policies. publicclassReplaySubject<Element>
/// Represents a value that changes over time. /// Observers can subscribe to the subject to receive the last (or initial) value and all subsequent notifications. publicfinalclassBehaviorSubject<Element>