Alba is a tiny yet powerful library which allows you to create sophisticated, decoupled and complex architecture using functional-reactive paradigms. Alba is designed to work mostly with reference semantics instances (classes).
let publisher = Publisher<UUID>()
publisher.publish(UUID())
In order to subscribe, you should use Subscribe
instances. The easiest way to get them is by using .proxy
property on publishers:
final class NumbersPrinter {
init(numbersPublisher: Subscribe<Int>) {
numbersPublisher.subscribe(self, with: NumbersPrinter.print)
}
func print(_ uuid: Int) {
print(uuid)
}
}
let printer = NumbersPrinter(numbersPublisher: publisher.proxy)
publisher.publish(10) // prints "10"
If you're surprised by how NumbersPrinter.print
looks - that's because this allows Alba to do some interesting stuff with reference cycles. Check out the implementation for details.
The cool thing about publisher proxies is the ability to do interesting things on them, for example, filter and map:
let stringPublisher = Publisher<String>()
final class Listener {
init(publisher: Subscribe<String>) {
publisher
.flatMap({ Int($0) })
.filter({ $0 > 0 })
.subscribe(self, with: Listener.didReceive)
}
func didReceive(positiveNumber: Int) {
print(positiveNumber)
}
}
let listener = Listener(publisher: stringPublisher.proxy)
stringPublisher.publish("14aq") // nothing
stringPublisher.publish("-5") // nothing
stringPublisher.publish("15") // prints "15"
Cool, huh?
let publisher = Publisher<Int>()
publisher.proxy.listen { (number) in
print(number)
}
publisher.publish(112) // prints "112"
Be careful with listen
. Don't prefer it over subscribe
as it can introduce memory leaks to your application.
If you want to write your own Subscribe
extensions, you should use rawModify
method:
public func rawModify<OtherEvent>(subscribe: (ObjectIdentifier, EventHandler<OtherEvent>) -> (), entry: @autoclosure @escaping ProxyPayload.Entry) -> Subscribe<OtherEvent>
Here is, for example, how you can implement map
:
public extension Subscribe {
public func map<OtherEvent>(_ transform: @escaping (Event) -> OtherEvent) -> Subscribe<OtherEvent> {
return rawModify(subscribe: { (identifier, handle) in
let handler: EventHandler<Event> = { event in
handle(transform(event))
}
self._subscribe(identifier, handler)
}, entry: .transformation(label: "mapped", .transformed(fromType: Event.self, toType: OtherEvent.self)))
}
}
entry
here is purely for debugging purposes -- you're describing the intention of your method.
One of the main drawbacks of the functional-reactive style is an elevated level of indirection -- you can't easily detect the information flow in your application. Alba aims to solve this problem with the help of a handy feature called Inform Bureau. Inform Bureau collects information about every subscription and publishing inside your application, so it's easy for you to detect what's actually going on (and to detect any problems easily, of course).
Inform Bureau is an optional feature, so it should be enabled in your code in order to work. It's actually just one line of code -- make sure to put this in your AppDelegate
's init
(application(_:didFinishLaunchingWithOptions)
is too late):
Alba.InformBureau.isEnabled = true
Just this line will no have any immediate effect -- in order for Inform Bureau to become useful, you should also enable it's Logger
:
Alba.InformBureau.Logger.enable()
And that's it! Now you're going to see beautiful messages like these in your output:
(S) ManagedObjectContextObserver.changes (Publisher<(NSChangeSet, Int)>)
(S) --> mapped from (NSChangeSet, Int) to NSSpecificChangeSet<CleaningPoint>
(S) !-> subscribed by PointsListViewController:4929411136
(S) +AppDelegate.applicationWillTerminate (Publisher<UIApplication>)
(S) --> mapped from UIApplication to ()
(S) merged with:
(S) +AppDelegate.applicationDidEnterBackground (Publisher<UIApplication>)
(S) --> mapped from UIApplication to ()
(S) !-> subscribed by ContextSaver:6176536960
(P) ContextSaver.didSaveContext (Publisher<()>) published ()
Hint: (S)
are subscriptions events, and (P)
are publications.
Inform Bureau can be enabled with two lines of code. However, in order for it to be useful, there is a little amount of work required from you. First and foremost, you should create all your publishers with descriptive label
:
let didFailToSaveImage = Publisher<Error>(label: "ImageSaver.didFailToSaveImage")
You should name your publishers using the next convention: [type_name].[publisher_name]
If your publisher is declared as static
, then add +
to the beginning:
static let applicationWillTerminate = Publisher<UIApplication>(label: "+AppDelegate.applicationWillTerminate")
Alba's Inform Bureau takes full advantage of Apple's latest Unified Logging system. The support for this system comes via Alba.OSLogger
object. If you want your app to write Alba logs via os_log
, enable it after enabling InformBureau
:
Alba.InformBureau.isEnabled = true
Alba.OSLogger.enable()
In order for os_log
to work, you should also do this.
Now you can see Alba logs of your program in a Console application.
Alba is available through Carthage. To install, just write into your Cartfile:
github "dreymonde/Alba" ~> 0.3.3
You can also use SwiftPM. Just add to your Package.swift
:
import PackageDescription
let package = Package(
dependencies: [
.Package(url: "https://github.com/dreymonde/Alba.git", majorVersion: 0, minor: 3),
]
)
Alba is in early stage of development and is opened for any ideas. If you want to contribute, you can:
- Propose idea/bugfix in issues
- Make a pull request
- Review any other pull request (very appreciated!), since no change to this project is made without a PR.
Actually, any help is welcomed! Feel free to contact us, ask questions and propose new ideas. If you don't want to raise a public issue, you can reach me at [email protected].