Rasat 2.0.0

Rasat 2.0.0

Maintained by Goksel Koksal.



Rasat 2.0.0

Rasat 🗼

Carthage CocoaPods CI Status Platform Language License

A microlibrary for pub-sub/observer pattern implementation in Swift. 👁

Components

Channel 📻

Channel is simply an event bus that you can broadcast messages on.

enum UserEvent {
  case loggedIn(id: String)
  case loggedOut(id: String)
}

// Define a channel:
let userChannel = Channel<UserEvent>()

// Broadcast a message:
userChannel.broadcast(.loggedIn(id: "gokselkk"))

Observable 👁

Observables let you listen to messages on a channel.

// Listen for changes:
let subscription = userChannel.observable.subscribe { event in
  self.handleUserEvent(event)
}

// End the subscription when needed:
subscription.dispose()

Subject 📦

Subjects encapsulate a value and broadcast when changed.

enum Theme {
  case light, dark
}

// Define a subject:
let themeSubject = Subject(Theme.light)

// Listen for changes:
themeSubject.observable.subscribe { theme in
  self.updateTheme(theme)
}

// Update the value:
themeSubject.value = .dark

Example 🏯

Listening for theme changes in a view controller:

class ThemeManager {

  var observable: Observable<Theme> {
    return channel.observable
  }
  
  private let channel = Channel<Theme>()
  
  func updateTheme(_ theme: Theme) {
    channel.broadcast(theme)
  }
}

class FeedViewController: UIViewController {
  
  var themeManager: ThemeManager!
  private let disposeBag = DisposeBag()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    disposeBag += themeManager.observable.subscribe { theme in
      self.updateTheme(theme)
    }
}

Note: DisposeBag is a collection of subscriptions (or disposables) that needs to live during the lifecycle of this view controller. In this example, subscriptions get disposed along with the dispose bag when this view controller gets deallocated.

Motivation

Apple frameworks use delegation and observer pattern heavily to pass information around.

  • Delegation pattern works well for 1-to-1, two-way communication. It's used to delegate work to some other component.
  • Observer pattern works well for 1-to-many, one-way communication. It's used to observe changes on an object.

Example usages of observer pattern would be:

  • UIApplication posting willEnterForegroundNotification.
  • UIResponder posting keyboardDidShowNotification.

Not to forget, all of this happens on NotificationCenter.default instance. That's where NotificationCenter falls short.

  • Any notification from any source can be posted on it.
  • Both post(...) and userInfo API are not type-safe. This results in boilerplate code.
  • API doesn't promote separation of concerns.

Rasat aims to fix these problems and provide even more.

Type Safety

Channel is type-safe. Type safety also restricts developers to create different channels for each type of message, therefore, promoting separation of concerns.

For example, keyboard events would have been implemented in this way:

enum KeyboardEvent {
  case didShow(frame: CGRect)
  case didDismiss(frame: CGRect)
}

let keyboardChannel = Channel<KeyboardEvent>()
// ...
let subscription = keyboardChannel.observable.subscribe { event in
  // Handle event here.
}

Observe-only API with Observable

NotificationCenter provides a unified API for broadcasting and observation. If you have a NotificationCenter instance, you can either broadcast or observe without any restriction.

This leads to a couple of problems:

  • It is error-prone. Some objects are only expected to observe, not broadcast. There's no way to implement this restriction using vanilla NotificationCenter API.
  • It reduces readability. Makes it hard to distinguish between broadcasters and observers.

For example:

let center = NotificationCenter.default

// The object that posts notifications on given center:
let broadcaster = Broadcaster(notificationCenter: center)

// The object that observes notifications on given center: 
let observer = Observer(notificationCenter: center)

At this point, we just hope that the observer doesn't post anything on the given notification center and only observes the notifications posted by the broadcaster.

This could be presented in a safer, more convenient way using Channel & Observable pair:

let channel = Channel<Message>()
let broadcaster = Broadcaster(channel: channel)
let observer = Observer(observable: channel.observable)

Or, even better, Broadcaster can create an internal channel and make only its observable public:

let broadcaster = Broadcaster()
let observer = Observer(observable: broadcaster.observable)

Installation

Using CocoaPods

Add the following line to your Podfile:

pod 'Rasat'

Using Carthage

Add the following line to your Cartfile:

github "gokselkoksal/Rasat"

Manually

Drag and drop Sources folder to your project.

It's highly recommended to use a dependency manager like CocoaPods or Carthage.

License

Rasat is available under the MIT license.