SignalKit is a reactive Swift framework with focus on clean and readable API.

Overview

SignalKit


Carthage compatible

Abstract

SignalKit is a lightweight event and binding framework. The core of SignalKit is the Observable protocol. Each implementation of the Observable protocol defines the type of the observation thus an Observable can sendNext only one type of event. For example an Observable of type String can only sendNext String values.

Another key protocol is SignalType which implements Observable and Disposable. Each SignalType implementation has a property disposableSource: Disposable? which points to a Disposable that comes before the current signal.

Because SignalType implements Disposable we can use the disposableSource property to chain signal operations like map, filter and combineLatest together. Each operation returns either a new SignalType or a Disposable. When we call dispose() on a SignalType it will dispose the whole chain of operations.

To store the chain of operations we can use a stored property or the disposeWith(container: DisposableBag) -> Disposable method on the Disposable protocol. When DisposableBag is deinitialized it will dispose all of its items for us.

let disposableBag = DisposableBag()
let userName = Signal<String>()

userName.next { print("name: \($0)") }
	.disposeWith(disposableBag)

userName.sendNext("John") // prints "name: John"

SignalKit Primary Protocols

Events And Bindings

SignalKit comes with an elegant way to observe for different event sources like KVO, Target Action and NSNotificationCenter via an unified API by simply calling observe() method. The observe method is a protocol extension on the NSObjectProtocol which returns a SignalEvent with sender Self. Then we use Protocol Oriented Programming to add extensions to a SignalEventType protocol where Sender is from a given type.

SignalKit Primary Protocols

Key Value Observing

Let's say we want to observe an instance of class Person: NSObject for it's name property changes with KVO. This is super easy with SignalKit, just call observe() on the instance and it will return the available events for this type. Then choose keyPath(path: String, value: T) where for the value parameter pass the initial value of the property. SignalKit will use this initial value type to perform an optional type cast on the values sent by KVO.

let person = Person(name: "John")

person.observe()
    .keyPath("name", value: person.name)
    .next { print("Hello \($0)") }
    .disposeWith(disposableBag)

Target Action

SignalKit comes with SignalEventType extensions for controls which inherits from UIControl and UIBarButtonItem:

let control = UIControl()
let barButton = UIBarButtonItem()

control.observe().events([.TouchUpInside])
    .next { _ in print("Tap!") }
    .disposeWith(disposableBag)
        
barButton.observe().tapEvent
    .next { _ in print("Tap!") }
    .disposeWith(disposableBag)

NSNotificationCenter

SignalEventType also have a handy extensions for observing an instance of NSNotificationCenter for notifications:

let center = NSNotificationCenter.defaultCenter()

center.observe().notification(UIApplicationWillResignActiveNotification)
    .next{ _ in print("Resign Active") }
    .disposeWith(disposableBag)

Bindings

Bindings in SignalKit are implemented again with protocol extensions. We extend the SignalType where the ObservationType (from the generic Observable protocol) is from a certain type and add method to bind the value to a UI control like UILabel. Here is an example of binding the String value from the signal to the text property of UILabel:

let userName = Signal<String>()
let nameLabel = UILabel()

userName.bindTo(textIn: nameLabel)
    .disposeWith(disposableBag)

Signals

Signal

Signal is the primary object which you can use to send events and it is thread safe.

SignalValue

SignalValue is a signal that stores its last sent/current value. If we add a new observer to it it will send immediately its value to the newly added observer. If we change the value of the signal it will notify its observers for the change. SignalValue is also thread safe.

let name = SignalValue(value: "John")

name.next { print($0) }.disposeWith(bag) // prints "John"

name.value = "Jonathan" // prints "Jonathan"

CollectionEvent

We can use a signal of type Signal<CollectionEvent> to send the changes that occur in our data source. Then we can bind the signal to UITableView or UICollectionView and the changes that we send will be reflected in the table/collection view:

// UsersListViewController
...
viewModel.usersChangeSignal.bindTo(tableView, rowAnimation: .Fade).disposeWith(bag)

// UsersListViewModel
let usersChangeSignal = Signal<CollectionEvent>()
var users = [User]()
...

var event = CollectionEvent()

users.insert(User(name: "John"), atIndex: 0)
event.itemInsertedAt(0, inSection: 0)

usersChangeSignal.sendNext(event)

Operations

SignalKit comes with the following SignalType operations: SignalKit Primary Protocols

Extensions

Currently SignalKit comes with extensions for the the following UIKit components: SignalKit Primary Protocols

Keyboard

You can use the Keyboard structure to observe for keyboard events posted by the system. Then you will get back a structure of type KeyboardState which you can query for the keyboard end/start frame and other data that the system sends with the notification:

Keyboard.observe().willShow
    .next { print($0.endFrame) }
    .disposeWith(disposableBag)

Installation

SignalKit requires Swift 2.0 and Xcode 7

Carthage

Add the following line to your Cartfile

github "yankodimitrov/SignalKit" "master"

CocoaPods

Add the following line to your Podfile

pod “SignalKit”

##License SignalKit is released under the MIT license. See the LICENSE.txt file for more info.

Comments
  • KVO doesn't work

    KVO doesn't work

    I have tried create custom class

    class CustomObject: NSObject {
        var age: Int
    
        init(age: Int) {
            self.age = age
        }
    }
    

    then create instance

    self.object = CustomObject(age: 1)
    

    then observe the age property

    self.object.observe().keyPath("age", value: self.object.age).next { print($0) }
    

    and finally change age

    self.object.age += 1
    self.object.age += 1
    self.object.age += 1
    self.object.age += 1
    self.object.age += 1
    

    But the value is printed only once.

    opened by kafejo 6
  • Option to emit the initial observable value

    Option to emit the initial observable value

    First of all, great library, really simple API!

    For example:

    var o1 = ObservableValue("Rose")
    s = observe(o1)
        .next { println($0) }
    

    If I don't call dispatch, the next block will never be invoked. Is it possible to have an option to invoke the next block when attached?

    opened by d6u 5
  • and() or() not() for SignalKit.Signal<(A, B, C)>

    and() or() not() for SignalKit.Signal<(A, B, C)>

    This project is awesome. The syntax is much more vivid then ReactiveCocoa. But I just found some inconvenience.

    Since we can combine several signals(Boolean wrapped in NSNumber) together with ReactiveCocoa:

    RAC(getCodeButton, "enabled") <-- RACSignal.combineLatest([validMobileSignal, isCountingSignal]).and()
    

    While with SignalKit:

    combineLatest(mobileSignal, passwordSignal, smsCodeSignal)
        .map { $0.0 == $0.1 == $0.2 == true }
        .bindTo( isEnabled(fetchCodeButton) )
    

    What if we add some convenient funcs like what ReactiveCocoa did.

    combineLatest(mobileSignal, passwordSignal, smsCodeSignal).and()
        .bindTo( isEnabled(fetchCodeButton) )
    
    enhancement Swift 2.0 
    opened by lexrus 4
  • The correct way to do an

    The correct way to do an "initial" sendNext to get combineLatestWith to work

    I have two signals, one from color picker and second from UISlider (to pick color alpha):

     let pickedColor = Signal<UIColor>()
     let pickedAlpha = Signal<Float>()
    

    I'd like to combine these two signals, so the obvious way is to use combineLatestWith:

    let _ = pickedColor.combineLatestWith(pickedAlpha).next { color, alpha  in
        print("color: \(color)\talpha: \(alpha)")
    }.disposeWith(disposableBag)
    

    The values are not printed, until both controls do their first sendNext, which is expected.

    I cannot wrap my head around the "nice" way to make that initial signals from both controls. Currently, what I do the next line after setting up combineLatestWith is:

    pickedColor.sendNext(colorPicker.color)
    pickedAlpha.sendNext(alphaSlider.value)
    

    ...but, I feel like I'm missing something obvious from FRP.

    opened by evgeniyd 3
  • Undeclared CollectionChangeSet type in 3.0 release

    Undeclared CollectionChangeSet type in 3.0 release

    When I try build 3.0 version from master, I get following errors

    Use of undeclared CollectionChangeSet type
    Use of undeclared ListOperationType
    

    I really like this framework API and I would like to use it in our project. Could you fix this issue please? Thank you!

    opened by kafejo 2
  • Nested ObservableArray not calling the correct ArrayBindingObserver initialiser in .observe()

    Nested ObservableArray not calling the correct ArrayBindingObserver initialiser in .observe()

    When I take the following sample code from the Readme.md performBatchUpdate doesn't work.

    let sectionOne = ObservableArray([1, 2])
    let sectionTwo = ObservableArray([3, 4])
    let list = ObservableArray<ObservableArray<Int>>([sectionOne, sectionTwo])
    
    list.observe()
       .bindTo(tableView: tableView, dataSource: dataSource)
       .addTo(signalsBag)
    
    list.performBatchUpdate { collection in
       collection[0].append(22)
       collection[0].removeAtIndex(0)
       collection.removeAtIndex(1)
    }
    

    I traced this to an issue with the wrong initialiser being called for ArrayBindingObserver

    list.observe() is defined in ObservableArray.swift Ln: 165, as:

    public func observe() -> ArrayBindingObserver<ElementType> {
       return ArrayBindingObserver(array: self)
    }
    

    For the case above ElementType is ObservableArray<Int>, so the type of self is ObservableArray<ObservableArray<Int>>. Swift doesn't seem to be able to correctly match the correct initialiser, so observe() ends up calling the convenience initialiser in ArrayBindingObserver.swift Ln: 27:

    public convenience init(array: ObservableArray<T>) {       
       let sectionsArray = ObservableArray<ObservableArray<T>>([array])
       self.init(array: sectionsArray)
    }
    

    But T here is already ObservableArray<Int> and so it gets wrapped in an additional Observable<T> leading to a type of ObservableArray<ObservableArray<ObservableArray<Int>>>

    It should be calling:

    public init(array: ObservableArray<ObservableArray<T>>) {
       self.array = array        
       observeForArrayEvent()
       setupSectionObservers()
    }
    
    opened by toriaezunama 2
  • Pod doesn't include ObservableType

    Pod doesn't include ObservableType

    Nice initiative, this is exactly what I'm looking for now, thank you!

    Something I noticed: For some unknown reason, if I install the project from cocoapods, the file ObservableType is missing. Does anyone have any ideas why?:)

    image

    opened by itchingpixels 2
  • Add `deinit` and propagated `dispose` method to `DisposableBag` class

    Add `deinit` and propagated `dispose` method to `DisposableBag` class

    It will prevent memory leaks.

    Something like this:

    public final class DisposableBag: Disposable {
    
        private lazy var bag = Bag<Disposable>()
    
        internal var count: Int {
            return bag.count
        }
    
        public init() {}
    
        deinit {
    
            dispose()
        }
    
        public func addDisposable(disposable: Disposable) -> Disposable {
    
            let token = self.bag.insert(disposable)
    
            return DisposableAction { [weak self] in
    
                self?.bag.removeItemWithToken(token)
            }
        }
    
        public func removeAll() {
    
            bag.removeItems()
        }
    
        public func dispose() {
    
            for disposable in self.bag {
                disposable.1.dispose()
            }
    
            self.removeAll()
        }
    }
    
    
    opened by shergin 1
  • UIKit generic type extensions

    UIKit generic type extensions

    Currently the UIKit bindings are based on free functions. Instead we can move them in generic type extensions on the Signal type.

    For example:

    // In Swift 1.2
    observe(viewModel.name)
        .bindTo( textIn(nameLabel) )
    

    will become :

    // In Swift 2.0
    observe(viewModel.name)
        .bindTo(textIn: nameLabel)
    

    :tada:

    enhancement Swift 2.0 API changes 
    opened by yankodimitrov 0
  • how would i create a Timer signal

    how would i create a Timer signal

    Any tips for creating a timer signal? Here's what I tried throwing together based on examples and a gesture recognizer signal I made that does work, but it doesn't fire.

    public extension Signal {
        // in case of a Timer, we dont start with an object on which we bootstrap a signal
        // so add this class method on Signal itself for creating a timer signal (can we make this a convenience init instead?)
        public class func repeatingTimer(withInterval interval: TimeInterval) -> Signal<Timer> {
            let signal = Signal<Timer>()
            let observer = IntervalTimerOperator(interval: interval)
            
            observer.callback = { [weak signal] timer in
                signal?.send(timer)
            }
            
            signal.disposableSource = observer
            
            return signal
        }
    }
    
    final class IntervalTimerOperator: NSObject, Disposable {
        private var timer: Timer!
        private var isDisposed = false
        
        internal var callback: ((_ timer: Timer) -> Void)?
        
        init(interval: TimeInterval, repeats: Bool = true) {
            super.init()
            self.timer = Timer(timeInterval: interval, target: self, selector: #selector(fire(_:)), userInfo: nil, repeats: repeats)
        }
        
        func fire(_: Timer) {
            callback?(timer)
        }
        
        func dispose() {
            guard !isDisposed else { return }
            timer.invalidate()
            isDisposed = true
        }
    }
    
    opened by jpmhouston 2
Releases(v4.0.0)
  • v4.0.0(Mar 7, 2016)

    What's new

    • Rewritten;
    • Removed unnecessary code;
    • API improvements;
    • New Signal implementation;
    • SignalValue;
    • CollectionEvent;
    • ControlEventObserver;
    • NotificationObserver;
    • KeyPathObserver;
    • ActionTarget;
    • Replaced TableViewBinding with new implementation;
    • Replaced CollectionViewBinding with new implementation;

    API changes:

    • Renamed dispatch(...) to sendNext(...);
    • Renamed dispatch(:onQueue:) to sendNext(:onQueue:)
    • Renamed deliverOn(...) to observeOn(...);
    • Renamed all(...) to allEqual(...);
    • Renamed some(...) to someEqual(...);
    • Removed combineLatest(...) for 3 input signals;
    • Removed all(...) for 3 input values;
    • Removed some(...) for 3 input values;
    • LockType is gone;
    • ObservableCollectionType is gone;
    • TokenGeneratorType is gone;
    • ObservableProperty is gone;
    • CollectionChangeSet is gone;
    • ListOperation is gone;
    • ControlSignal is gone;
    • KVOSignal is gone;
    • NotificationSignal is gone;
    • NSIndexSet extensions are gone;
    • CollectionChangeSetHandler is gone;
    • CompoundObserver is gone;
    • Dispatcher is gone;
    • DisposableActionTarget is gone;
    • IncrementalKeyGenerator is gone;
    • SpinLock is gone;

    :tada:

    Source code(tar.gz)
    Source code(zip)
  • v3.0.1(Jan 29, 2016)

  • v3.0.0(Jan 22, 2016)

    What's new

    • UIBarItem extension for binding a Boolean value to the enabled property of UIBarItem;
    • UIBarButtonItem extension to observe for the button tap event;
    • ObservableCollectionType protocol;
    • New UITableView and UICollectionView bindings using the ObservableCollectionType;
    • Observable protocol extension to dispatch the item on a given SignalQueue;

    API changes:

    • ObservableArray is gone;
    • Renamed the Disposable protocol extension method addTo(...) to disposeWith(...);

    :tada:

    Source code(tar.gz)
    Source code(zip)
  • v2.0.0(Oct 1, 2015)

    What's new:

    • ObservableArray
    • UITableView and UICollectionView bindings
    • 100% test coverage

    API changes:

    • SignalBag is now DisposableBag

    :tada:

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Aug 18, 2015)

    • Rewritten with Swift 2.0 (XCode 7 beta 5)
    • Protocol Oriented Programming
    • New improved API
    • New signal operations
    • Overall improvements

    :tada:

    Source code(tar.gz)
    Source code(zip)
Owner
Yanko Dimitrov
Yanko Dimitrov
Mini-application iOS native avec Xcode et Swift exploitant l'architecture MVVM et le framework Combine d'Apple pour la mise en place de la programmation réactive fonctionnelle, le tout avec UIKit.

iOS (Swift 5): Test MVVM avec Combine et UIKit L'architecture MVVM et la programmation réactive fonctionnelle sont très utlisées dans le développement

Koussaïla BEN MAMAR 2 Nov 5, 2022
⚛️ A Reactive Data-Binding and Dependency Injection Library for SwiftUI x Concurrency.

SwiftUI Atom Properties A Reactive Data-Binding and Dependency Injection Library for SwiftUI x Concurrency ?? API Reference Introduction Examples Gett

Ryo Aoyama 199 Dec 17, 2022
SpaceX rocket listing app using RxSwift and CLEAN Architecture with MVVM

Jibble SpaceX rocket listing app using RxSwift and CLEAN Architecture with MVVM Demo Features Reactive Bindings URL / JSON Parameter Encoding Filter Y

Ammad Akhtar 0 Dec 5, 2021
Swift, UIkit, Anchorage, Clean Architecture, UICollectionViewDiffableDataSourcem, MVVM+Coordinators patterns

About the app iOS project realized with Swift, UIkit, Anchorage, Clean Architecture, UICollectionViewDiffableDataSource and MVVM + Coordinators patter

Luca Berardinelli 4 Dec 29, 2022
MVVM-Clean with DI implementation sample project

MVVM-Clean with Dependency Provider Introduction This project contains MVVM-Clean architecture and Dependency injection Folder Structure Every module

Faraz Ahmed Khan 3 Oct 20, 2022
A sample project exploring MVVM pattern with SwiftUI/Combine, using Unsplash API (via Picsum.photos API)

CombineUnsplash A sample project exploring MVVM pattern with SwiftUI/Combine, using Unsplash API (via Picsum.photos API) with detail example. Resource

Vinh Nguyen 26 Dec 13, 2022
This little app aims to help teach me how to implement more then one API in one single application in a reusable and properly structured manner.

LilAPI App News & Weather This little API app combines two of Jordan Singers Lil Software API's into one app. The goal with this app was to learn how

hallux 1 Oct 13, 2021
ClearScore - This application simulates a credit check on a user by making a call to an API and displaying the data

ClearScore Description This application simulates a credit check on a user by ma

Sashen Pillay 0 Jan 11, 2022
This repository shows how handle Rest API's in SwiftUI and Combine

SwiftUI-Combine-Networking This repository shows how handle Rest API's in SwiftUI and Combine Endpoints enum includes paths which will be added the en

Abdullah Kardaş 5 Jan 1, 2023
Movies app written in Swift 5 using the Custom API created on the Mocky website

Movie App shows you collections of TV streaming and other movies. Movie app writ

null 8 Dec 7, 2022
Aplikasi iOS Berita Internasional dengan SwiftUI, API dari newsapi.org, MVVM Design Pattern, dan Paw

iNews iNews adalah aplikasi iOS Berita Internasional yang datanya didapatkan dari News API. Dibuat menggunakan SwiftUI, MVVM Design Pattern, dan Paw.

DK 8 Aug 1, 2022
A travel guide app that implements MVVM design pattern, using mock API.

✈️ Travel Guide App ?? A travel guide app that implements MVVM design pattern, using mock API. ✨ Features ?? Project Features Written in Swift Impleme

Burak Ertaş 4 Nov 7, 2022
Model View Presenter Framework written in Swift.

BothamUI BothamUI is MVP (Model-View-Presenter) framework written in Swift. This project will help you setup all your presentation logic. BothamUI pro

Karumi 351 Sep 22, 2022
A small app that uses the private FlightUtilities.framework to show information about any flight.

FlightUtilities A small app that uses the private FlightUtilities.framework to show information about any flight given the airline code, flight code a

Patrick Balestra 31 Dec 20, 2022
Lightweight Framework for using Core Data with Value Types

Features Uses Swift Reflection to convert value types to NSManagedObjects iOS and Mac OS X support Use with structs Works fine with let and var based

Benedikt Terhechte 456 Nov 6, 2022
A framework to create proxies for XCBBuildService, which allows for custom Xcode build integrations.

XCBBuildServiceProxyKit XCBBuildServiceProxyKit is a framework that enables you to write a proxy for Xcode's XCBBuildService, which enables you to ext

Mobile Native Foundation 30 Dec 28, 2022
SwiftLint - A tool to enforce Swift style and conventions, loosely based on Swift Style Guide.

SwiftLint - A tool to enforce Swift style and conventions, loosely based on Swift Style Guide.

Realm 16.9k Dec 30, 2022
Sweet-swift - Make Swift Sweet by Gracefully Introducing Syntactic Sugar, Helper Functions and Common Utilities

Sweet Swift Make Swift Sweet by Gracefully Introducing Syntactic Sugar, Helper F

Yanzhan Yang 2 Feb 6, 2022
Explanations and samples about the Swift programming language

About Swift Contents Explanations and samples about: Swift Programming Language Swift Standard Library Target audience Developers familiar with object

Nicola Lancellotti - About 74 Dec 29, 2022