SwiftEvents 2.1.2

SwiftEvents 2.1.2

Maintained by Denis Simon.



  • By
  • Denis Simon

SwiftEvents

Swift Platform

SwiftEvents is a lightweight library for creating and observing events.

It includes:

  • Observable<T> for data binding that can be used in MVVM. Observable is implemented using Event class.
  • Event<T> for closure based delegation and one-to-many notifications.

Features:

  • Type safety: the concrete type value is delivered to the subscriber without the need for downcasting

  • Thread safety: you can subscribe / bind, trigger, unsubscribe / unbind from any thread without issues such as data races

  • Memory safety: automatic preventing retain cycles, without strictly having to specify [weak self] in closure when subscribing/binding. Whether you specified [weak self] or not, it’s sometimes forgotten to specify - safety against memory leaks will be ensured automatically. As well as automatic removal of subscribers/observers when they are deallocated

  • Comprehensive unit test coverage.

Installation

CocoaPods

To install SwiftEvents using CocoaPods, add this line to your Podfile:

pod 'SwiftEvents', '~> 1.1.2'

Carthage

To install SwiftEvents using Carthage, add this line to your Cartfile:

github "denissimon/SwiftEvents"

Swift Package Manager

To install SwiftEvents using the Swift Package Manager, add it to your Package.swift file:

dependencies: [
    .Package(url: "https://github.com/denissimon/SwiftEvents.git", from: "1.1.2")
]

Manually

Just drag SwiftEvents.swift to the project tree.

Usage

Data binding

  1. Replace the Type of property to observe with the Observable<Type>
  2. Bind to the Observable

Example:

class ViewModel {
    
    var infoLabel: Observable<String> = Observable("init value")

    func set(newValue: String) {
        infoLabel.value = newValue
    }
}
class View: UIViewController {
    
    var viewModel = ViewModel()

    @IBOutlet weak var infoLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        infoLabel.text = viewModel.infoLabel.value
        
        viewModel.infoLabel.bind(self) { (self, value) in self.updateInfoLabel(value) }
    }

    private func updateInfoLabel(_ value: String) {
        infoLabel.text = value
    }
}

Note: here a capture list is intentionally not specified in closure before (self, value) because under the hood SwiftEvents will construct a new closure with [weak target] included there. This way a strong reference cycle will be avoided.

In the above example, every time ViewModel changes the value of the observable property infoLabel, View is notified and updates the infoLabel.text.

You can use the infix operator <<< to set a new value for an observable property:

infoLabel <<< newValue

As with Event, an Observable can have multiple observers.

Delegation

Delegation can be implemented not only using protocols, but also based on closure. Such a one-to-one connection can be done in two steps:

  1. Create an Event for the publisher
  2. Subscribe to the Event

Example:

class MyModel {
    
    let didDownload = Event<UIImage?>()
    
    func downloadImage(for url: URL) {
        service.download(url: url) { [weak self] image in
            self?.didDownload.trigger(image)
        }
    }
}
class MyViewController: UIViewController {

    let model = MyModel()
    var image: UIImage?

    override func viewDidLoad() {
        super.viewDidLoad()
        
        model.didDownload.subscribe(self) { (self, image) in self.updateImage(image) }
    }
    
    private func updateImage(_ image: UIImage?) {
        self.image = image
    }
}

You can use Event with any complex type, including custom types and multiple values like (UIImage, Int). You can also create several events (didDownload, onNetworkError etc), and trigger only what is needed.

Notifications

If notifications must be one-to-many, or two objects that need to be connected are too far apart, SwiftEvents can be used like NotificationCenter.

Example:

public class EventService {
    
    public static let get = EventService()
    
    private init() {}

    public let onDataUpdate = Event<String?>()
}
class Controller1 {    
    init() {
        EventService.get.onDataUpdate.subscribe(self) { (self, data) in
            print("Controller1: '\(data)'")
        }
    }
}
class Controller2 {
    init() {
        EventService.get.onDataUpdate.subscribe(self) { (self, data) in
            print("Controller2: '\(data)'")
        }
    }
}
class DataModel {    
    func requestData() {
        // requesting code goes here
        data = "some data"
        EventService.get.onDataUpdate.trigger(data)
    }
}
let sub1 = Controller1()
let sub2 = Controller2()
let pub = DataModel()
pub.requestData()
// => Controller1: 'some data'
// => Controller2: 'some data'

More examples

More usage examples can be found in this demo app.

Advanced features

Manual removal of a subscriber / observer

someEvent.subscribe(self) { (self, value) in self.setValue(value) }
someEvent.unsubscribe(self)

someObservable.bind(self) { (self, value) in self.setValue(value) }
someObservable.unbind(self)

Removal of all subscribers / observers

someEvent.unsubscribeAll()
someObservable.unbindAll()

The number of subscribers to the Event

let subscribersCount = someEvent.subscribersCount

The number of times the Event was triggered

let triggersCount = someEvent.triggersCount

Reset of triggersCount

someEvent.resetTriggersCount()

queue: DispatchQueue

By default, the provided handler is executed on the thread that triggers the Event / Observable. To change this default behaviour, you can set this parameter when subscribing/binding:

// This executes the handler on the main queue
someEvent.subscribe(self, queue: .main) { (self, image) in self.updateImage(image) }
someObservable.bind(self, queue: .main) { (self, image) in self.updateImage(image) }

One-time notification

To ensure that the handler will be executed only once:

someObservable.bind(self) { (self, data) in
    self.useData(data)
    self.someObservable.unbind(self)
}

N-time notifications

To ensure that the handler will be executed no more than n times:

someEvent.subscribe(self) { (self, data) in
    self.useData(data)
    if self.someEvent.triggersCount == n {
        self.someEvent.unsubscribe(self)
    }
}

Alias methods

There are alias methods for Event addSubscriber, removeSubscriber and removeAllSubscribers, which do the same thing as subscribe, unsubscribe and unsubscribeAll respectively.

License

Licensed under the MIT license