A micro-library for creating and observing events.

Related tags

Event Signals
Overview

Signals

Build Status Cocoapods Compatible Carthage Compatible License Platform Twitter

Signals is a library for creating and observing events. It replaces delegates, actions and NSNotificationCenter with something much more powerful and elegant.

Features

  • Attach-and-forget observation
  • Type-safety
  • Filtered observation
  • Delayed and queued observation
  • Comprehensive Unit Test Coverage

Requirements

  • iOS 7.0 / watchOS 2.0 / Mac OS X 10.9
  • Swift 4.2

Installation

To use Signals with a project targeting iOS 7, simply copy Signals.swift into your project.

CocoaPods

To integrate Signals into your project add the following to your Podfile:

platform :ios, '8.0'
use_frameworks!

pod 'Signals', '~> 6.0'

Carthage

To integrate Signals into your project using Carthage add the following to your Cartfile:

github "artman/Signals" ~> 6.0

Swift Package Manager

To integrate Signals into your project using SwiftPM add the following to your Package.swift:

dependencies: [
    .package(url: "https://github.com/artman/Signals", from: "6.0.0"),
],

Quick start

Make events on a class observable by creating one or more signals:

class NetworkLoader {

    // Creates a number of signals that can be subscribed to
    let onData = Signal<(data:NSData, error:NSError)>()
    let onProgress = Signal<Float>()

    ...

    func receivedData(receivedData:NSData, receivedError:NSError) {
        // Whenever appropriate, fire off any of the signals
        self.onProgress.fire(1.0)
        self.onData.fire((data:receivedData, error:receivedError))
    }
}

Subscribe to these signals from elsewhere in your application

let networkLoader = NetworkLoader("http://artman.fi")

networkLoader.onProgress.subscribe(with: self) { (progress) in
    print("Loading progress: \(progress*100)%")
}

networkLoader.onData.subscribe(with: self) { (data, error) in
    // Do something with the data
}

Adding subscriptions to Signals is an attach-and-forget operation. If the subscribing object is deallocated, the Signal cancels the subscription, so you don't need to explicitly manage the cancellation of your subsciptions.

Singals aren't restricted to one subscriber. Multiple objects can subscribe to the same Signal.

You can also subscribe to events after they have occurred:

networkLoader.onProgress.subscribePast(with: self) { (progress) in
    // This will immediately fire with last progress that was reported
    // by the onProgress signal
    println("Loading progress: \(progress*100)%")
}

Advanced topics

Signal subscriptions can apply filters:

networkLoader.onProgress.subscribe(with: self) { (progress) in
    // This fires when progress is done
}.filter { $0 == 1.0 }

You can sample up subscriptions to throttle how often you're subscription is executed, regardless how often the Signal fires:

networkLoader.onProgress.subscribe(with: self) { (progress) in
    // Executed once per second while progress changes
}.sample(every: 1.0)

By default, a subscription executes synchronously on the thread that fires the Signal. To change the default behaviour, you can use the dispatchOnQueue method to define the dispatch queue:

networkLoader.onProgress.subscribe(with: self) { (progress) in
    // This fires on the main queue
}.dispatchOnQueue(DispatchQueue.main)

If you don't like the double quotes when you fire signals that take tuples, you can use the custom => operator to fire the data:

// If you don't like the double quotes when firing signals that have tuples
self.onData.fire((data:receivedData, error:receivedError))

// You can use the => operator to fire the signal
self.onData => (data:receivedData, error:receivedError)

// Also works for signals without tuples
self.onProgress => 1.0

Replacing actions

Signals extends all classes that extend from UIControl (not available on OS X) and lets you use Signals to listen to control events for increased code locality.

let button = UIButton()
button.onTouchUpInside.observe(with: self) {
    // Handle the touch
}

let slider = UISlider()
slider.onValueChanged.observe(with: self) {
    // Handle value change
}

Replacing delegates

Signals is simple and modern and greatly reduce the amount of boilerplate that is required to set up delegation.

Would you rather implement a callback using a delegate:

  • Create a protocol that defines what is delegated
  • Create a delegate property on the class that wants to provide delegation
  • Mark each class that wants to become a delegate as comforming to the delegate protocol
  • Implement the delegate methods on the class that want to become a delegate
  • Set the delegate property to become a delegate of the instance
  • Check that your delegate implements each delegate method before invoking it

Or do the same thing with Signals:

  • Create a Signal for the class that wants to provide an event
  • Subscribe to the Signal

Replace NotificationCenter

When your team of engineers grows, NotificationCenter quickly becomes an anti-pattern. Global notifications with implicit data and no compiler safety easily make your code error-prone and hard to maintain and refactor.

Replacing NotificationCenter with Signals will give you strong type safety enforced by the compiler that will help you maintain your code no matter how fast you move.

Communication

  • If you found a bug, open an issue or submit a fix via a pull request.
  • If you have a feature request, open an issue or submit a implementation via a pull request or hit me up on Twitter @artman
  • If you want to contribute, submit a pull request onto the master branch.

License

Signals is released under an MIT license. See the LICENSE file for more information

Comments
  • CocoaPods dependency issue

    CocoaPods dependency issue

    I'm still quite new to CocoaPods so I'm trying to figure out if it's me doing something wrong, but for some reason, CocoaPods doesn't think that Signals 6.0.0 support osx:

    - ERROR | [OSX] unknown: Encountered an unknown error (The platform of the target `App` (macOS 10.13) is not compatible with `Signals (6.0.0)`, which does not support `osx`.) during validation.
    

    But looking at your pod-spec I cannot find anything that could cause this 🤔

    This is my pod-spec btw.

    Pod::Spec.new do |s|
      s.name         = "Marionette"
      s.version      = %x(git describe --tags --abbrev=0).chomp
      s.summary      = "Swift library which provides a high-level API to control a WKWebView"
      s.description  = "Marionette is a Swift library which provides a high-level API to control a WKWebView. The goal is to have the API closely mirror that of Puppeteer."
      s.homepage     = "https://github.com/LinusU/Marionette"
      s.license      = "MIT"
      s.author       = { "Linus Unnebäck" => "[email protected]" }
    
      s.swift_version = "4.0"
      s.ios.deployment_target = "11.0"
      s.osx.deployment_target = "10.13"
    
      s.source       = { :git => "https://github.com/LinusU/Marionette.git", :tag => "#{s.version}" }
      s.source_files = "Sources"
    
      s.dependency "LinusU_JSBridge", "1.0.0-alpha.14"
      s.dependency "PromiseKit", "~> 6.0"
      s.dependency "Signals", "~> 6.0"
    end
    

    Maybe this should be reported to the CocoaPods repo? 🤔

    opened by LinusU 10
  • Changes for swift 2.3 and 3.0

    Changes for swift 2.3 and 3.0

    • Compatible with Swift 3.0, 2.3 and 2.2
    • Compatible with xcode 7.3 and 8 (tested using beta 6)

    I'm not sure how to make travis to check against all swift/xcode versions, so I kept it running against xcode-7.3/swift-2.2 and tested the other cases locally

    Great project, btw. I love Signals 😄

    opened by snit-ram 10
  • Retaining old data fired on Signals should be opt-in

    Retaining old data fired on Signals should be opt-in

    lastDataFired is a strong reference to the last object that was sent through the Signal. If sending objects with a wide reference graph, this can lead to some unexpected memory "leaks".

    Ideally the lastDataFired should be a weak reference, but that is not possible with Swift non-class types.

    This can already be achieved by setting fireCount to 0 and the lastDataFired to nil, but an explicit API call to do some clean up is probably a better design.

    enhancement 
    opened by netizen01 6
  • Release a 6.0.0 Cocoapods

    Release a 6.0.0 Cocoapods

    It looks like 6.0.0 was completed and tagged, but never released through CocoaPods. Could we please get that pushed so we can use it? Thanks in advance!

    opened by stephanheilner 5
  • Set the dispatch queue explicitly

    Set the dispatch queue explicitly

    A signal dispatches listener calls synchronously on the posting thread by default. Added the dispatchOnQueue method to specify the queue explicitly. Updated the readme file and implemented unit tests.

    opened by sgl0v 5
  • Release 5.XXX to CocoaPods?

    Release 5.XXX to CocoaPods?

    @artman can you push a new version to cocoa pods? I have some repos that use this (awesome) library as a dependency - would love to get all of them updated to 4.2 but need this to go first!

    Thanks!

    opened by netizen01 4
  • Specify full version in SPM instructions

    Specify full version in SPM instructions

    Recent Swift versions require the version specifier in "from" to be a full version, otherwise it'll give the following error:

    error: manifest parse error(s):
    Invalid version string: 6.0
    
    opened by LinusU 3
  • Add

    Add "action" signal to UIBarButtonItem

    This adds a signal for listening to when the action of a UIBarButtonItem is activated. The listening is performed by setting .target and .action, which seems to be the only way to listen to this event.

    It's very convenient to have when building programmatic UIs, example:

    class MainPage: UIViewController {
        lazy var addButton = UIBarButtonItem(barButtonSystemItem: .add, target: nil, action: nil)
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            navigationItem.title = "Stuff"
            navigationItem.setRightBarButton(addButton, animated: false)
            navigationController?.navigationBar.prefersLargeTitles = true
    
            addButton.onAction.subscribe(with: self) { _ in
                print("The add button was tapped")
            }
        }
    
        // ...
    }
    
    opened by LinusU 3
  • How to fire() without arguments?

    How to fire() without arguments?

    Sorry if this is obvious but I'm new to Swift.

    I'd like fire a Signal without passing any argument.

    I've resorted to using Signal<Int?>() and .fire(nil) but is there more elegant way of handling this?

    opened by PierBover 3
  • `carthage update` fails due to ENABLE_TESTABILTY not being set

    `carthage update` fails due to ENABLE_TESTABILTY not being set

    With Signals 2.1.0 and Xcode 7.0.1, carthage update fails with this error:

    Tests.swift:11:18: error: module 'Signals' was not compiled for testing
    A shell task failed with exit code 65:
    

    This can be fixed by setting ENABLE_TESTABILITY to YES in the project file for release builds.

    If it helps, I can send a PR with this change. Thanks!

    opened by raj-room 3
  • Require only App-Extension-Safe API

    Require only App-Extension-Safe API

    This PR includes #57 also, since I needed both ☺️

    I'd be happy to rebase if you want to merge that one first.

    This patch adds a flag that tells Xcode that we are only using APIs that are safe to use inside an app extension. This is needed to be able to use this library inside an app extension at all, read more here:

    https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html#//apple_ref/doc/uid/TP40014214-CH2-SW6

    opened by LinusU 2
  • Expose signal for .primaryActionTriggered (on tvOS)

    Expose signal for .primaryActionTriggered (on tvOS)

    tvOS makes more extensive use of the .primaryActionTriggered UIControl.Event - this exposes a onPrimaryActionTriggered signal for UIControls - currently on tvOS only.

    In theory this action can be used on iOS (I believe pressing the done button on the keyboard triggers this event in text fields for example) as well - but it is only available on iOS versions 9.0 and above and the library currently has a minimum iOS version of 8.0. This in theory could be cleaned up (removing the various #if os(tvOS) compiler conditional bits and returning the eventToKey to not use a closure to initialize) by bumping the min os version. Personally I doubt iOS 8 support has any value - but can't make that call for you.

    opened by Marus 0
  • Newb thread question

    Newb thread question

    I understand the dispatchOnQueue method allows a subscriber to specify the thread on which to run its callback code.

    However, I need to fire events from code which is running on a background thread. As it happens, all subscribers to these events are in the main thread. It feels like using dispatchOnQueue in this case would require the subscribers to know about the origin of the event, leading to tightly-coupled code.

    So I assume the obvious solution is to fire the events on the main thread in the first place:

    // Send a signal on the main thread.
    DispatchQueue.main.async {
        self.onRawStderrorReceived.fire(stringData)
    }
    

    Does this approach sound correct? The reason I am asking is that I keep getting Thread 1: EXC_BAD_ACCESS (code=1, address=0xfffffffffffffff0) errors thrown at that point (Xcode actually highlights the comment, oddly).

    opened by jeff-h 0
  • Add

    Add "Property" struct

    This adds an observable property inspired by Property from ReactiveSwift, and BehaviorSubject from RxSwift.

    This implementation of the observable property has an advantage over analogs from RxSwift and ReactiveSwift because it supports defining different Access Levels to setter and getter, which is impossible to achieve in those libraries.

    Aiming to maintain the philosophy of simplicity of the Signal, this implementation of the Property doesn't replicate all the functional features inherent to the FRP libraries, such as mapping the value or combining with other properties. Instead, it focuses on the observation feature only.

    Signals is a library for creating and observing events. It replaces delegates, actions and NSNotificationCenter with something much more powerful and elegant.

    With this addition, the Signals would also offer a replacement for the Key-Value-Observation.

    class MyClass {
        private(set) var property = Property(value: "abc")
    
        func mutateProperty() {
            property.value = "xyz"
        }
    }
    
    let object = MyClass()
    print("\(object.property.value)") // getter returns "abc"
    
    // Attempt to change the value from outside the class:
    object.property.value = "qwe" // Compiler error - setter is private
    
    // Subscribing for new values
    object.property.observe(with: self) { value in
        print("\(value)")
    }
    
    object.mutateProperty() // triggers the notification with the new value "xyz"
    
    opened by nalexn 1
  • ResetFireCount method. Fix clearLastData documentation.

    ResetFireCount method. Fix clearLastData documentation.

    Resetting the fireCount was removed from clearLastData in e29f6ec. The inline documentation to reflect that has been updated. Resetting the fireCount is still useful.

    opened by zachstegall 2
Releases(6.1.0)
Owner
Tuomas Artman
Co-founder @ Linear. Previously staff engineer @ Uber
Tuomas Artman
Swift Apps in a Swoosh! A modern framework for creating iOS apps, inspired by Redux.

Katana is a modern Swift framework for writing iOS applications' business logic that are testable and easy to reason about. Katana is strongly inspire

Bending Spoons 2.2k Jan 1, 2023
A Swift based Future/Promises Library for IOS and OS X.

FutureKit for Swift A Swift based Future/Promises Library for IOS and OS X. Note - The latest FutureKit is works 3.0 For Swift 2.x compatibility use v

null 759 Dec 2, 2022
A library for reactive and unidirectional Swift applications

ReactorKit is a framework for a reactive and unidirectional Swift application architecture. This repository introduces the basic concept of ReactorKit

ReactorKit 2.5k Dec 28, 2022
A New, Modern Reactive State Management Library for Swift and SwiftUI (The iOS implementation of Recoil)

RecoilSwift RecoilSwift is a lightweight & reactive swift state management library. RecoilSwift is a SwiftUI implementation of recoil.js which powered

Holly Li 160 Dec 25, 2022
A simple and predictable state management library inspired by Flux + Elm + Redux.

A simple and predictable state management library inspired by Flux + Elm + Redux. Flywheel is built on top of Corotuines using the concepts of structured concurrency. At the core, lies the State Machine which is based on actor model.

Abhi Muktheeswarar 35 Dec 29, 2022
Define and chain Closures with Inputs and Outputs

Closure Define and chain Closures with Inputs and Outputs Examples No Scoped State let noStateCount = Closure<String, String> { text in String(repea

Zach Eriksen 3 May 18, 2022
Write great asynchronous code in Swift using futures and promises

BrightFutures How do you leverage the power of Swift to write great asynchronous code? BrightFutures is our answer. BrightFutures implements proven fu

Thomas Visser 1.9k Dec 20, 2022
Tempura - A holistic approach to iOS development, inspired by Redux and MVVM

Tempura is a holistic approach to iOS development, it borrows concepts from Redux (through Katana) and MVVM. ?? Installation Requirements CocoaPods ??

Bending Spoons 692 Dec 17, 2022
SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms

SwiftUI-compatible framework for building browser apps with WebAssembly At the moment Tokamak implements a very basic subset of SwiftUI. Its DOM rende

TokamakUI 2k Dec 30, 2022
VueFlux is the architecture to manage state with unidirectional data flow for Swift, inspired by Vuex and Flux.

Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux Introduction VueFlux is the architecture to manage state with unidi

Ryo Aoyama 324 Dec 17, 2022
SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms

SwiftUI-compatible framework for building browser apps with WebAssembly At the moment Tokamak implements a very basic subset of SwiftUI. Its DOM rende

TokamakUI 2k Dec 29, 2022
Very simple Observable and Publisher implementation for iOS apps.

Very simple Observable and Publisher implementation for iOS apps.

Igor Kulman 7 Jun 11, 2022
Interactive notification pop-over (aka "Toast) modeled after the iOS AirPods and Apple Pencil indicator.

Interactive notification pop-over (aka "Toast) modeled after the iOS AirPods and Apple Pencil indicator. Installation The recommended way is to use Co

Philip Kluz 108 Nov 11, 2022
A micro-framework for observing file changes, both local and remote. Helpful in building developer tools.

KZFileWatchers Wouldn't it be great if we could adjust feeds and configurations of our native apps without having to sit back to Xcode, change code, r

Krzysztof Zabłocki 1k Dec 19, 2022
KVO for Swift - Value Observing and Events

Value Observing and Events for Swift Swift lacks the powerful Key Value Observing (KVO) from Objective-C. But thanks to closures, generics and propert

Leszek Ślażyński 1.2k Dec 9, 2022
Modern thread-safe and type-safe key-value observing for Swift and Objective-C

Now Archived and Forked PMKVObserver will not be maintained in this repository going forward. Please use, create issues on, and make PRs to the fork o

Postmates Inc. 708 Jun 29, 2022
MicrofrontendGenerator - Script for creating micro frontends for Mobile in a simple and easy way

Introdução Template para a criação de SDK iOS. Existem duas opções de template:

Julio Fernandes Jr 4 Nov 2, 2022
null 13 Oct 28, 2022
A Swift micro library for generating Sunrise and Sunset times.

Solar A Swift helper for generating Sunrise and Sunset times. Solar performs its calculations locally using an algorithm from the United States Naval

Chris Howell 493 Dec 25, 2022
A Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and other native frameworks.

ZamzamKit ZamzamKit is a Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and othe

Zamzam Inc. 261 Dec 15, 2022