All Projects → UrbanCompass → Snail

UrbanCompass / Snail

Licence: mit
An observables framework for Swift

Programming Languages

swift
15916 projects

Projects that are alternatives of or similar to Snail

Mag.js
MagJS - Modular Application Glue
Stars: ✭ 157 (-3.68%)
Mutual labels:  framework
Zimfw
Zim: Modular, customizable, and blazing fast Zsh framework
Stars: ✭ 2,219 (+1261.35%)
Mutual labels:  framework
Vendure
A headless GraphQL ecommerce framework for the modern web
Stars: ✭ 2,961 (+1716.56%)
Mutual labels:  framework
Py Ipv8
Python implementation of the IPv8 layer
Stars: ✭ 157 (-3.68%)
Mutual labels:  framework
Revact
Full reactivity JS/TS framework. Lightweight replacement of React + MobX + React Router. Very small and fast !!
Stars: ✭ 159 (-2.45%)
Mutual labels:  framework
Stove
Domain Driven Design oriented application framework, meets CRUD needs
Stars: ✭ 160 (-1.84%)
Mutual labels:  framework
Playframework
Play Framework
Stars: ✭ 12,041 (+7287.12%)
Mutual labels:  framework
Mandarinets
Mandarine.TS is a typescript, decorator-driven framework that allows you to create server-side applications. Mandarine.TS provides a range of built-in solutions such as Dependency Injection, Components, ORM and more. Under its umbrella, Mandarine.TS has 4 modules: Core, Data, Security and MVC, these modules will offer you the requirements to build a Mandarine-powered application.
Stars: ✭ 161 (-1.23%)
Mutual labels:  framework
Zeebsploit
web scanner - exploitation - information gathering
Stars: ✭ 159 (-2.45%)
Mutual labels:  framework
Discord Player
🎧 Complete framework to simplify the implementation of music commands using discords.js v12
Stars: ✭ 161 (-1.23%)
Mutual labels:  framework
Fx
A dependency injection based application framework for Go.
Stars: ✭ 2,383 (+1361.96%)
Mutual labels:  framework
Avalanche
Avalanche: a End-to-End Library for Continual Learning.
Stars: ✭ 151 (-7.36%)
Mutual labels:  framework
Goat
[DEPRECATED] 🐐 A minimalistic JSON API server in Go
Stars: ✭ 161 (-1.23%)
Mutual labels:  framework
100 Lines Of Code Challenge Js
Write Everything in JavaScript under 100 Lines!!!😈
Stars: ✭ 157 (-3.68%)
Mutual labels:  framework
Objfw
[Official Mirror] A portable framework for the Objective-C language.
Stars: ✭ 161 (-1.23%)
Mutual labels:  framework
Kratos
A modular-designed and easy-to-use microservices framework in Go.
Stars: ✭ 15,844 (+9620.25%)
Mutual labels:  framework
Anpylar
Python client-side web development framework
Stars: ✭ 160 (-1.84%)
Mutual labels:  framework
Mono
Minimalist Framework on top of Express.js
Stars: ✭ 163 (+0%)
Mutual labels:  framework
Fastapi Crudrouter
A dynamic FastAPI router that automatically creates CRUD routes for your models
Stars: ✭ 159 (-2.45%)
Mutual labels:  framework
Busker
An extremely simple web framework.
Stars: ✭ 161 (-1.23%)
Mutual labels:  framework

🐌 snail Carthage compatible Cocoapods codecov.io SwiftPM Compatible

SNAIL

A lightweight observables framework, also available in Kotlin

Installation

Carthage

You can install Carthage with Homebrew using the following command:

brew update
brew install carthage

To integrate Snail into your Xcode project using Carthage, specify it in your Cartfile where "x.x.x" is the current release:

github "UrbanCompass/Snail" "x.x.x"

Swift Package Manager

To install using Swift Package Manager have your Swift package set up, and add Snail as a dependency to your Package.swift.

dependencies: [
    .Package(url: "https://github.com/UrbanCompass/Snail.git", majorVersion: 0)
]

Manually

Add all the files from Snail/Snail to your project

Developing Locally

  1. Run the setup script to install required dependencies ./scripts/setup.sh

Creating Observables

let observable = Observable<thing>()

Disposer

A disposer is in charge of removing all the subscriptions. A disposer is usually located in a centralized place where most of the subscriptions happen (ie: UIViewController in an MVVM architecture). Since most of the subscriptions are to different observables, and those observables are tied to type, all the things that are going to be disposed need to comform to Disposable.

If the Disposer helps get rid of the closures and prevent retention cycles (see weak self section). For the sake of all the examples, let's have a disposer created:

let disposer = Disposer()

Closure Wrapper

The main usage for the Disposer is to get rid of subscription closures that we create on Observables, but the other usage that we found handy, is the ability to dispose of regular closures. As part of the library, we created a small Closure wrapper class that complies with Disposable. This way you can wrap simple closures to be disposed.

let closureCall = Closure {
    print("We ❤️ Snail")
}.add(to: Disposer)

Please note that this would not dispose of the closureCall reference to closure, it would only Dispose the content of the Closure.

Subscribing to Observables

observable.subscribe(
    onNext: { thing in ... }, // do something with thing
    onError: { error in ... }, // do something with error
    onDone: { ... } //do something when it's done
).add(to: disposer)

Closures are optional too...

observable.subscribe(
    onNext: { thing in ... } // do something with thing
).add(to: disposer)
observable.subscribe(
    onError: { error in ... } // do something with error
).add(to: disposer)

Creating Observables Variables

let variable = Variable<whatever>(some initial value)
let optionalString = Variable<String?>(nil)
optionalString.asObservable().subscribe(
    onNext: { string in ... } // do something with value changes
).add(to: disposer)

optionalString.value = "something"
let int = Variable<Int>(12)
int.asObservable().subscribe(
    onNext: { int in ... } // do something with value changes
).add(to: disposer)

int.value = 42

Combining Observable Variables

let isLoaderAnimating = Variable<Bool>(false)
isLoaderAnimating.bind(to: viewModel.isLoading) // forward changes from one Variable to another

viewModel.isLoading = true
print(isLoaderAnimating.value) // true
Observable.merge([userCreated, userUpdated]).subscribe(
  onNext: { user in ... } // do something with the latest value that got updated
}).add(to: disposer)

userCreated.value = User(name: "Russell") // triggers 
userUpdated.value = User(name: "Lee") // triggers 
Observable.combineLatest((isMapLoading, isListLoading)).subscribe(
  onNext: { isMapLoading, isListLoading in ... } // do something when both values are set, every time one gets updated
}).add(to: disposer)

isMapLoading.value = true
isListLoading.value = true // triggers

Miscellaneous Observables

let just = Just(1) // always returns the initial value (1 in this case)

enum TestError: Error {
  case test
}
let failure = Fail(TestError.test) // always fail with error

let n = 5
let replay = Replay(n) // replays the last N events when a new observer subscribes

Operators

Snail provides some basic operators in order to transform and operate on observables.

  • map: This operator allows to map the value of an obsverable into another value. Similar to map on Collection types.

    let observable = Observable<Int>()
    let subject = observable.map { "Number: \($0)" }
    // -> subject emits `String` whenever `observable` emits.
    
  • filter: This operator allows filtering out certain values from the observable chain. Similar to filter on Collection types. You simply return true if the value should be emitted and false to filter it out.

    let observable = Observable<Int>()
    let subject = observable.filter { $0 % 2 == 0 }
    // -> subject will only emit even numbers.
    
  • flatMap: This operator allows mapping values into other observables, for example you may want to create an observable for a network request when a user tap observable emits.

    let fetchTrigger = Observable<Void>()
    let subject = fetchTrigger.flatMap { Variable(100).asObservable() }
    // -> subject is an `Observable<Int>` that is created when `fetchTrigger` emits.
    

Subscribing to Control Events

let control = UIControl()
control.controlEvent(.touchUpInside).subscribe(
  onNext: { ... }  // do something with thing
).add(to: disposer)

let button = UIButton()
button.tap.subscribe(
  onNext: { ... }  // do something with thing
).add(to: disposer)

Queues

You can specify which queue an observables will be notified on by using .subscribe(queue: <desired queue>). If you don't specify, then the observable will be notified on the same queue that the observable published on.

There are 3 scenarios:

  1. You don't specify the queue. Your observer will be notified on the same thread as the observable published on.

  2. You specified main queue AND the observable published on the main queue. Your observer will be notified synchronously on the main queue.

  3. You specified a queue. Your observer will be notified async on the specified queue.

Examples

Subscribing on DispatchQueue.main

observable.subscribe(queue: .main,
    onNext: { thing in ... }
).add(to: disposer)

Weak self is optional

You can use [weak self] if you want, but with the introduction of Disposer, retention cycles are destroyed when calling disposer.disposeAll().

One idea would be to call disposer.disposeAll() when you pop a view controller from the navigation stack.

protocol HasDisposer {
    var disposer: Disposer
}

class NavigationController: UINavigationController {
    public override func popViewController(animated: Bool) -> UIViewController? {
        let viewController = super.popViewController(animated: animated)
        (viewController as? HasDisposer).disposer.disposeAll()
        return viewController
    }
}

In Practice

Subscribing to Notifications

NotificationCenter.default.observeEvent(Notification.Name.UIKeyboardWillShow)
  .subscribe(queue: .main, onNext: { notification in
    self.keyboardWillShow(notification)
  }).add(to: disposer)

Subscribing to Gestures

let panGestureRecognizer = UIPanGestureRecognizer()
panGestureRecognizer.asObservable()
  .subscribe(queue: .main, onNext: { sender in
    // Your code here
  }).add(to: disposer)
view.addGestureRecognizer(panGestureRecognizer)

Subscribing to UIBarButton Taps

navigationItem.leftBarButtonItem?.tap
  .subscribe(onNext: {
    self.dismiss(animated: true, completion: nil)
  }).add(to: disposer)
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].