{"id":13396608,"url":"https://github.com/ReactKit/ReactKit","last_synced_at":"2025-03-13T23:31:38.521Z","repository":{"id":21281874,"uuid":"24597835","full_name":"ReactKit/ReactKit","owner":"ReactKit","description":"Swift Reactive Programming.","archived":false,"fork":false,"pushed_at":"2015-09-23T05:36:44.000Z","size":1131,"stargazers_count":1193,"open_issues_count":11,"forks_count":40,"subscribers_count":47,"default_branch":"swift/2.0","last_synced_at":"2024-10-30T05:51:11.551Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ReactKit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-09-29T14:09:19.000Z","updated_at":"2024-09-10T15:01:42.000Z","dependencies_parsed_at":"2022-09-02T18:40:10.183Z","dependency_job_id":null,"html_url":"https://github.com/ReactKit/ReactKit","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ReactKit%2FReactKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ReactKit%2FReactKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ReactKit%2FReactKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ReactKit%2FReactKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ReactKit","download_url":"https://codeload.github.com/ReactKit/ReactKit/tar.gz/refs/heads/swift/2.0","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243500132,"owners_count":20300750,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T18:00:57.989Z","updated_at":"2025-03-13T23:31:38.479Z","avatar_url":"https://github.com/ReactKit.png","language":"Swift","funding_links":[],"categories":["Extensions","Libs","Swift","Utilities and Extensions","Frameworks"],"sub_categories":["Events"],"readme":"\u003cimg src=\"https://avatars3.githubusercontent.com/u/8986128\" width=\"36\" height=\"36\"\u003e ReactKit [![Circle CI](https://circleci.com/gh/ReactKit/ReactKit/tree/swift%2F2.0.svg?style=svg)](https://circleci.com/gh/ReactKit/ReactKit/tree/swift%2F2.0)\n========\n\nSwift Reactive Programming.\n\n\n## How to install\n\nSee [Wiki page](https://github.com/ReactKit/ReactKit/wiki/How-to-install).\n\n\n## Example\n\nFor UI Demo, please see [ReactKit/ReactKitCatalog](https://github.com/ReactKit/ReactKitCatalog).\n\n### Key-Value Observing\n\n```swift\n// create stream via KVO\nself.obj1Stream = KVO.stream(obj1, \"value\")\n\n// bind stream via KVC (`\u003c~` as binding operator)\n(obj2, \"value\") \u003c~ self.obj1Stream\n\nXCTAssertEqual(obj1.value, \"initial\")\nXCTAssertEqual(obj2.value, \"initial\")\n\nobj1.value = \"REACT\"\n\nXCTAssertEqual(obj1.value, \"REACT\")\nXCTAssertEqual(obj2.value, \"REACT\")\n```\n\nTo remove stream-bindings, just release `stream` itself (or call `stream.cancel()`).\n\n```swift\nself.obj1Stream = nil   // release stream \u0026 its bindings\n\nobj1.value = \"Done\"\n\nXCTAssertEqual(obj1.value, \"Done\")\nXCTAssertEqual(obj2.value, \"REACT\")\n```\n\nIf you want to observe changes in `Swift.Array` or `NSMutableArray`,\nuse `DynamicArray` feature in [Pull Request #23](https://github.com/ReactKit/ReactKit/pull/23).\n\n### NSNotification\n\n```swift\nself.stream = Notification.stream(\"MyNotification\", obj1)\n    |\u003e map { notification -\u003e NSString? in\n        return \"hello\" // convert NSNotification? to NSString?\n    }\n\n(obj2, \"value\") \u003c~ self.stream\n```\n\nNormally, `NSNotification` itself is useless value for binding with other objects, so use [Stream Operations](#stream-operations) e.g. `map(f: T -\u003e U)` to convert it.\n\nTo understand more about `|\u003e` pipelining operator, see [Stream Pipelining](#stream-pipelining).\n\n### Target-Action\n\n```swift\n// UIButton\nself.buttonStream = self.button.buttonStream(\"OK\")\n\n// UITextField\nself.textFieldStream = self.textField.textChangedStream()\n\n^{ println($0) } \u003c~ self.buttonStream     // prints \"OK\" on tap\n\n// NOTE: ^{ ... } = closure-first operator, same as `stream ~\u003e { ... }`\n^{ println($0) } \u003c~ self.textFieldStream  // prints textField.text on change\n```\n\n### Complex example\n\nThe example below is taken from\n\n- [iOS - ReactiveCocoaをかじってみた - Qiita](http://qiita.com/paming/items/9ac189ab0fe5b25fe722) (well-illustrated)\n\nwhere it describes 4 `UITextField`s which enables/disables `UIButton` at certain condition (demo available in [ReactKit/ReactKitCatalog](https://github.com/ReactKit/ReactKitCatalog)):\n\n```swift\nlet usernameTextStream = self.usernameTextField.textChangedStream()\nlet emailTextStream = self.emailTextField.textChangedStream()\nlet passwordTextStream = self.passwordTextField.textChangedStream()\nlet password2TextStream = self.password2TextField.textChangedStream()\n\nlet allTextStreams = [usernameTextStream, emailTextStream, passwordTextStream, password2TextStream]\n\nlet combinedTextStream = allTextStreams |\u003e merge2All\n\n// create button-enabling stream via any textField change\nself.buttonEnablingStream = combinedTextStream\n    |\u003e map { (values, changedValue) -\u003e NSNumber? in\n\n        let username: NSString? = values[0] ?? nil\n        let email: NSString? = values[1] ?? nil\n        let password: NSString? = values[2] ?? nil\n        let password2: NSString? = values[3] ?? nil\n\n        // validation\n        let buttonEnabled = username?.length \u003e 0 \u0026\u0026 email?.length \u003e 0 \u0026\u0026 password?.length \u003e= MIN_PASSWORD_LENGTH \u0026\u0026 password == password2\n\n        // NOTE: use NSNumber because KVO does not understand Bool\n        return NSNumber(bool: buttonEnabled)\n    }\n\n// REACT: enable/disable okButton\n(self.okButton, \"enabled\") \u003c~ self.buttonEnablingStream!\n```\n\nFor more examples, please see XCTestCases.\n\n\n## How it works\n\nReactKit is based on powerful [SwiftTask](https://github.com/ReactKit/SwiftTask) (JavaScript Promise-like) library, allowing to start \u0026 deliver multiple events (KVO, NSNotification, Target-Action, etc) continuously over time using its **resume \u0026 progress** feature (`react()` or `\u003c~` operator in ReactKit).\n\nUnlike [Reactive Extensions (Rx)](https://github.com/Reactive-Extensions) libraries which has a basic concept of \"hot\" and \"cold\" [observables](http://reactivex.io/documentation/observable.html), ReactKit gracefully integrated them into one **hot + paused (lazy) stream** `Stream\u003cT\u003e` class. Lazy streams will be auto-resumed via `react()` \u0026 `\u003c~` operator.\n\nHere are some differences in architecture:\n\n| | Reactive Extensions (Rx) | ReactKit |\n|:---:|:---:|:---:|\n| Basic Classes | Hot Observable (broadcasting)\u003cbr\u003eCold Observable (laziness) | `Stream\u003cT\u003e` |\n| Generating | Cold Observable (cloneability) | `Void -\u003e Stream\u003cT\u003e`\u003cbr\u003e(= `Stream\u003cT\u003e.Producer`) |\n| Subscribing | `observable.subscribe(onNext, onError, onComplete)` | `stream.react {...}.then {...}`\u003cbr\u003e(method-chainable) |\n| Pausing | `pausableObservable.pause()` | `stream.pause()` |\n| Disposing | `disposable.dispose()` | `stream.cancel()` |\n\n### Stream Pipelining\n\nStreams can be composed by using `|\u003e` **stream-pipelining operator** and [Stream Operations](#stream-operations).\n\nFor example, a very common [incremental search technique](http://en.wikipedia.org/wiki/Incremental_search) using `searchTextStream` will look like this:\n\n```swift\nlet searchResultsStream: Stream\u003c[Result]\u003e = searchTextStream\n    |\u003e debounce(0.3)\n    |\u003e distinctUntilChanged\n    |\u003e map { text -\u003e Stream\u003c[Result]\u003e in\n        return API.getSearchResultsStream(text)\n    }\n    |\u003e switchLatestInner\n```\n\nThere are some scenarios (e.g. `repeat()`) when you want to use a cloneable `Stream\u003cT\u003e.Producer` (`Void -\u003e Stream\u003cT\u003e`) rather than plain `Stream\u003cT\u003e`. In this case, you can use `|\u003e\u003e` **streamProducer-pipelining operator** instead.\n\n```swift\n// first, wrap stream with closure\nlet timerProducer: Void -\u003e Stream\u003cInt\u003e = {\n    return createTimerStream(interval: 1)\n        |\u003e map { ... }\n        |\u003e filter { ... }\n}\n\n// then, use `|\u003e\u003e`  (streamProducer-pipelining operator)\nlet repeatTimerProducer = timerProducer |\u003e\u003e repeat(3)\n```\n\nBut in the above case, wrapping with closure will always become cumbersome, so you can also use `|\u003e\u003e` operator for `Stream` \u0026 [Stream Operations](#stream-operations) as well (thanks to `@autoclosure`).\n\n```swift\nlet repeatTimerProducer = createTimerStream(interval: 1)\n    |\u003e\u003e map { ... }\n    |\u003e\u003e filter { ... }\n    |\u003e\u003e repeat(3)\n```\n\n\n## Functions\n\n### Stream Operations\n\n- For Single Stream\n  - Transforming\n    - `asStream(ValueType)`\n    - `map(f: T -\u003e U)`\n    - `flatMap(f: T -\u003e Stream\u003cU\u003e)`\n    - `map2(f: (old: T?, new: T) -\u003e U)`\n    - `mapAccumulate(initialValue, accumulator)` (alias: `scan`)\n    - `buffer(count)`\n    - `bufferBy(stream)`\n    - `groupBy(classifier: T -\u003e Key)`\n  - Filtering\n    - `filter(f: T -\u003e Bool)`\n    - `filter2(f: (old: T?, new: T) -\u003e Bool)`\n    - `take(count)`\n    - `takeUntil(stream)`\n    - `skip(count)`\n    - `skipUntil(stream)`\n    - `sample(stream)`\n    - `distinct()`\n    - `distinctUntilChanged()`\n  - Combining\n    - `merge(stream)`\n    - `concat(stream)`\n    - `startWith(initialValue)`\n    - `combineLatest(stream)`\n    - `zip(stream)`\n    - `recover(stream)`\n  - Timing\n    - `delay(timeInterval)`\n    - `interval(timeInterval)`\n    - `throttle(timeInterval)`\n    - `debounce(timeInterval)`\n  - Collecting\n    - `reduce(initialValue, accumulator)`\n  - Other Utilities\n    - `peek(f: T -\u003e Void)` (for injecting side effects e.g. debug-logging)\n    - `customize(...)`\n\n- For Array Streams\n  - `mergeAll(streams)`\n  - `merge2All(streams)` (generalized method for `mergeAll` \u0026 `combineLatestAll`)\n  - `combineLatestAll(streams)`\n  - `zipAll(streams)`\n\n- For Nested Stream (`Stream\u003cStream\u003cT\u003e\u003e`)\n  - `mergeInner(nestedStream)`\n  - `concatInner(nestedStream)`\n  - `switchLatestInner(nestedStream)`\n\n- For Stream Producer (`Void -\u003e Stream\u003cT\u003e`)\n  - `prestart(bufferCapacity)` (alias: `replay`)\n  - `times(count)`\n  - `retry(count)`\n\n### Helpers\n\n- Creating\n  - `Stream.once(value)` (alias: `just`)\n  - `Stream.never()`\n  - `Stream.fulfilled()` (alias: `empty`)\n  - `Stream.rejected()` (alias: `error`)\n  - `Stream.sequence(values)` (a.k.a Rx.fromArray)\n  - `Stream.infiniteSequence(initialValue, iterator)` (a.k.a Rx.iterate)\n\n- Other Utilities\n  - `ownedBy(owner: NSObject)` (easy strong referencing to keep streams alive)\n\n\n## Dependencies\n\n- [SwiftTask](https://github.com/ReactKit/SwiftTask)\n\n\n## References\n\n- [Introducing ReactKit // Speaker Deck](https://speakerdeck.com/inamiy/introducing-reactkit) (ver 0.3.0)\n\n\n## Licence\n\n[MIT](https://github.com/ReactKit/ReactKit/blob/master/LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FReactKit%2FReactKit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FReactKit%2FReactKit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FReactKit%2FReactKit/lists"}