Integrating RxSwift Into Your Brain and Code Base

  • closures
  • target/action (UIControl aka IBAction)
  • delegates
  • notifications (NotificationCenter)
  • KVO
  • setting variables
  • sub-classing abstract base classes

Replacing IBActions

For our first step, we will start with something easy — replacing IBActions with observables. This is as simple as:

  1. Make sure that the input that triggers the action is an IBOutlet in the view.
  2. Make sure that RxSwift and RxCocoa have been imported into the view’s file.
  3. Make sure the view has a DisposeBag
  4. In the view’s initializer (for a UIView this would be the awakeFromNib method, for a UIViewController it would be the viewDidLoad method) subscribe to an Observable on the input that just calls the action.
browseForPeersButton.rx.tap
.bind(onNext: { [weak self] in self?.browseForPeers() })
.disposed(by: disposeBag)

Replacing Delegates: Removing App Specific Protocols

This is a much larger topic and there are a lot of delegates in the code we are working with, so I will break it up into stages. We see two basic sorts of delegates in this code — those provided by libraries (for e.g., MCBrowserViewControllerDelegate) and those created custom for this app (e.g., SessionContainerDelegate.) For the first sort we are forced to use the DelegateProxy class provided by RxCocoa; for the latter sort I recommend doing away with them completely.

  1. Create a private PublishSubject for each function in the delegate protocol.
  2. Create a computed property that returns each of the publish subjects as an observable.
  3. Remove the delegate property and replace calls to it to onNext calls to the correct publish subject.
  4. In the object that conforms to the delegate, remove the protocol conformance and replace with binders to each observable created in step 2.
var didCreateChatRoom: Observable<(displayName: String, serviceType: String)> {
return _didCreateChatRoom.asObservable()
}
private let _didCreateChatRoom = PublishSubject<(displayName: String, serviceType: String)>()

Replacing Delegates: Wrapping iOS provided protocols

Sadly, creating wrappers using the DelegateProxy class provided by RxCocoa is outside of the scope of this article. I will have to give it its own treatment later. However, once you have them, replacing the protocol conformance with observables is something that we will cover. The steps are:

  1. Remove the protocol conformance tag from the class/extension.
  2. At the point where the delegate was getting set, replace that setter with the needed subscriptions to the various observables.
imagePicker.rx.didCancel
.bind(onNext: { [weak self] in
self?.imagePickerControllerDidCancel()
})
.disposed(by: disposeBag)
imagePicker.rx.didFinishPickingMediaWithInfo
.bind(onNext: { [weak self] info in
self?.didFinishPickingMediaWithInfo(info)
})
.disposed(by: disposeBag)

Replacing Notification Observers

There are a number of problems with the sample code surrounding keyboard display and dismissal. I assume that the bugs exist because of changes in how the keyboard notifications work. Rather than just a mechanical replacement, we are forced to do a rewrite of the moveToolBar function.

Fixing a photo capture bug

During routine testing, I noticed that the app was unable to capture and send photos. I realized the problem was two fold. First, the operating system now requires the app to explicitly ask for permission to access photos, and second, the UIImagePicker delegate proxy I cribbed from the RxSwift sample code is out of date. To fix these, I wrote my own UIImagePicker delegate proxy, included the required permission strings, and added the request authorization method. This later is a good example of how to wrap an OS function that requires a callback and returns Void.

extension Reactive where Base: PHPhotoLibrary {
static var requestAuthorization: Observable<PHAuthorizationStatus> {
return Observable.create { observer in
Base.requestAuthorization { status in
observer.onNext(status)
observer.onCompleted()
}
return Disposables.create()
}
}
}

Replacing KVO

This app uses KVO inside the ProgressObserver class to observe the Progress object. It observes two properties of the progress object, cancelled and completedUnitCount. Whenever either of them is updated, the appropriate bound observer will be called. You will see that now each observed key-value gets its own closure rather than having to route all of them through a single function and since the dispose bag takes care of cleanup for us, we can do away with the deinit function.

Interlude

Wow, that was a lot of work and a pretty big word count for some fairly mechanical code refactoring. I did some runs of the app throughout the refactoring but by and large I knew that behavior wasn’t changing so I wasn’t concerned. It would be nice to test the logic, but the logic was so interwoven with the effects and so much of the code was about translating from one observation system to another that, up to this point, writing the tests would have been very difficult.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Daniel Tartaglia

Daniel Tartaglia

175 Followers

I started programming as a hobby in the late ’70s and professionally in the late ’90s. I’ve been writing iOS apps since 2010 and using RxSwift since 2015.