Skip to content

Instantly share code, notes, and snippets.

Avatar

Piotr Zmudzinski pzmudzinski

View GitHub Profile
View CombineShoppingCart.md

Reactive Shopping Cart with Swift and Combine

Overview

Reactive programming was quite popular among iOS/OSX developers in recent years, although there was no "official" API. Community created wonderful RxSwit project, being one of most popular 3rd party tools used in Apple ecosystem. Things has changed little bit once Apple released their own Combine framework which introduced similar API and Rx-ish thinking.

Today, we are going to try it out via practical example of shopping cart functionality. We will be discussing how to model internal and external events happening in our buying process and thinking about posssible future extensions. Article assumes you have basic knowledge about swift and RxSwift or Combine.

Basics

@pzmudzinski
pzmudzinski / CombineShoppingCartTests.swift
Last active Mar 26, 2020
CombineShoppingCartTests.swift
View CombineShoppingCartTests.swift
//
// CombineShoppingCartTests.swift
// CombineShoppingCartTests
//
// Created by Piotr on 25/03/2020.
// Copyright © 2020 Piotr. All rights reserved.
//
import XCTest
import Combine
View ShoppingCart.swift
//
// ShoppingCart.swift
// CombineShoppingCart
//
// Created by Piotr on 25/03/2020.
// Copyright © 2020 Piotr. All rights reserved.
//
import Combine
@pzmudzinski
pzmudzinski / auth.js
Created Oct 4, 2019
Custom jest expect extension for GraphQL auth tests
View auth.js
expect.extend({
toBeForbidden(received) {
const forbiddenErrorCode = 'FORBIDDEN';
if (!received.errors || received.errors.length === 0) {
return {
message: `No errors in response`,
pass: false
}
View rebound.swift
extension Observable {
func rebound(after interval: RxTimeInterval, map: @escaping (Element) -> Element = { return $0 }) -> Observable<Element> {
return flatMap { element in
return Observable.just(map(element))
.delay(interval, scheduler: MainScheduler.instance)
.startWith(element)
}
}
}
View gist:ff459845a734b84a50e299c479417b7f
let errorOrCompletedColor = viewModel.triggerDiscount
.executionObservables
.flatMapLatest { $0 }
.materialize()
.map { event -> UIColor in
switch event {
case .error(_):
return TriggerDiscountView.errorColor
case .next(_), .completed:
return TriggerDiscountView.successColor
View testDeleteCommandRemovesItemFromCollection.swift
func testDeleteCommandRemovesItemFromCollection() {
let itemsObserver = testScheduler.createObserver([Book].self)
viewModel.items.bind(to: itemsObserver)
.disposed(by: disposeBag)
testScheduler.createHotObservable(
[Recorded.next(100, IndexPath(row: 0, section: 0))]
)
.bind(to: viewModel.deleteCommand)
View BooksViewModelTest.swift
class BooksViewModelTest: XCTestCase {
var viewModel: BooksViewModel!
var testScheduler: TestScheduler!
var disposeBag: DisposeBag!
override func setUp() {
viewModel = BooksViewModel(api: DummyApi())
testScheduler = TestScheduler(initialClock: 0)
disposeBag = DisposeBag()
View Podfile
pod 'RxBlocking', '~> 4.0'
pod 'RxTest', '~> 4.0'
View setupActions.swift
api.getBooks()
.map { books -> BookCollectionAction in return .collectionRefreshed(withBooks: books) }
.bind(to: collectionActions)
// ...
let bookMarkedForDeletion = deleteCommand
.withLatestFrom(items) { index, items in return items[index.row] }
// ...
You can’t perform that action at this time.