Giter Site home page Giter Site logo

reactiveobjc's Introduction

ReactiveObjC

NOTE: This is legacy introduction to the Objective-C ReactiveCocoa, which is now known as ReactiveObjC. For the updated version that uses Swift, please see ReactiveCocoa or ReactiveSwift

ReactiveObjC (formally ReactiveCocoa or RAC) is an Objective-C framework inspired by Functional Reactive Programming. It provides APIs for composing and transforming streams of values.

If you're already familiar with functional reactive programming or know the basic premise of ReactiveObjC, check out the other documentation in this folder for a framework overview and more in-depth information about how it all works in practice.

New to ReactiveObjC?

ReactiveObjC is documented like crazy, and there's a wealth of introductory material available to explain what RAC is and how you can use it.

If you want to learn more, we recommend these resources, roughly in order:

  1. Introduction
  2. When to use ReactiveObjC
  3. Framework Overview
  4. Basic Operators
  5. Header documentation
  6. Previously answered Stack Overflow questions and GitHub issues
  7. The rest of this folder
  8. Functional Reactive Programming on iOS (eBook)

If you have any further questions, please feel free to file an issue.

Introduction

ReactiveObjC is inspired by functional reactive programming. Rather than using mutable variables which are replaced and modified in-place, RAC provides signals (represented by RACSignal) that capture present and future values.

By chaining, combining, and reacting to signals, software can be written declaratively, without the need for code that continually observes and updates values.

For example, a text field can be bound to the latest time, even as it changes, instead of using additional code that watches the clock and updates the text field every second. It works much like KVO, but with blocks instead of overriding -observeValueForKeyPath:ofObject:change:context:.

Signals can also represent asynchronous operations, much like futures and promises. This greatly simplifies asynchronous software, including networking code.

One of the major advantages of RAC is that it provides a single, unified approach to dealing with asynchronous behaviors, including delegate methods, callback blocks, target-action mechanisms, notifications, and KVO.

Here's a simple example:

// When self.username changes, logs the new name to the console.
//
// RACObserve(self, username) creates a new RACSignal that sends the current
// value of self.username, then the new value whenever it changes.
// -subscribeNext: will execute the block whenever the signal sends a value.
[RACObserve(self, username) subscribeNext:^(NSString *newName) {
	NSLog(@"%@", newName);
}];

But unlike KVO notifications, signals can be chained together and operated on:

// Only logs names that starts with "j".
//
// -filter returns a new RACSignal that only sends a new value when its block
// returns YES.
[[RACObserve(self, username)
	filter:^(NSString *newName) {
		return [newName hasPrefix:@"j"];
	}]
	subscribeNext:^(NSString *newName) {
		NSLog(@"%@", newName);
	}];

Signals can also be used to derive state. Instead of observing properties and setting other properties in response to the new values, RAC makes it possible to express properties in terms of signals and operations:

// Creates a one-way binding so that self.createEnabled will be
// true whenever self.password and self.passwordConfirmation
// are equal.
//
// RAC() is a macro that makes the binding look nicer.
//
// +combineLatest:reduce: takes an array of signals, executes the block with the
// latest value from each signal whenever any of them changes, and returns a new
// RACSignal that sends the return value of that block as values.
RAC(self, createEnabled) = [RACSignal
	combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ]
	reduce:^(NSString *password, NSString *passwordConfirm) {
		return @([passwordConfirm isEqualToString:password]);
	}];

Signals can be built on any stream of values over time, not just KVO. For example, they can also represent button presses:

// Logs a message whenever the button is pressed.
//
// RACCommand creates signals to represent UI actions. Each signal can
// represent a button press, for example, and have additional work associated
// with it.
//
// -rac_command is an addition to NSButton. The button will send itself on that
// command whenever it's pressed.
self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
	NSLog(@"button was pressed!");
	return [RACSignal empty];
}];

Or asynchronous network operations:

// Hooks up a "Log in" button to log in over the network.
//
// This block will be run whenever the login command is executed, starting
// the login process.
self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^(id sender) {
	// The hypothetical -logIn method returns a signal that sends a value when
	// the network request finishes.
	return [client logIn];
}];

// -executionSignals returns a signal that includes the signals returned from
// the above block, one for each time the command is executed.
[self.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) {
	// Log a message whenever we log in successfully.
	[loginSignal subscribeCompleted:^{
		NSLog(@"Logged in successfully!");
	}];
}];

// Executes the login command when the button is pressed.
self.loginButton.rac_command = self.loginCommand;

Signals can also represent timers, other UI events, or anything else that changes over time.

Using signals for asynchronous operations makes it possible to build up more complex behavior by chaining and transforming those signals. Work can easily be triggered after a group of operations completes:

// Performs 2 network operations and logs a message to the console when they are
// both completed.
//
// +merge: takes an array of signals and returns a new RACSignal that passes
// through the values of all of the signals and completes when all of the
// signals complete.
//
// -subscribeCompleted: will execute the block when the signal completes.
[[RACSignal
	merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]]
	subscribeCompleted:^{
		NSLog(@"They're both done!");
	}];

Signals can be chained to sequentially execute asynchronous operations, instead of nesting callbacks with blocks. This is similar to how futures and promises are usually used:

// Logs in the user, then loads any cached messages, then fetches the remaining
// messages from the server. After that's all done, logs a message to the
// console.
//
// The hypothetical -logInUser methods returns a signal that completes after
// logging in.
//
// -flattenMap: will execute its block whenever the signal sends a value, and
// returns a new RACSignal that merges all of the signals returned from the block
// into a single signal.
[[[[client
	logInUser]
	flattenMap:^(User *user) {
		// Return a signal that loads cached messages for the user.
		return [client loadCachedMessagesForUser:user];
	}]
	flattenMap:^(NSArray *messages) {
		// Return a signal that fetches any remaining messages.
		return [client fetchMessagesAfterMessage:messages.lastObject];
	}]
	subscribeNext:^(NSArray *newMessages) {
		NSLog(@"New messages: %@", newMessages);
	} completed:^{
		NSLog(@"Fetched all messages.");
	}];

RAC even makes it easy to bind to the result of an asynchronous operation:

// Creates a one-way binding so that self.imageView.image will be set as the user's
// avatar as soon as it's downloaded.
//
// The hypothetical -fetchUserWithUsername: method returns a signal which sends
// the user.
//
// -deliverOn: creates new signals that will do their work on other queues. In
// this example, it's used to move work to a background queue and then back to the main thread.
//
// -map: calls its block with each user that's fetched and returns a new
// RACSignal that sends values returned from the block.
RAC(self.imageView, image) = [[[[client
	fetchUserWithUsername:@"joshaber"]
	deliverOn:[RACScheduler scheduler]]
	map:^(User *user) {
		// Download the avatar (this is done on a background queue).
		return [[NSImage alloc] initWithContentsOfURL:user.avatarURL];
	}]
	// Now the assignment will be done on the main thread.
	deliverOn:RACScheduler.mainThreadScheduler];

That demonstrates some of what RAC can do, but it doesn't demonstrate why RAC is so powerful. It's hard to appreciate RAC from README-sized examples, but it makes it possible to write code with less state, less boilerplate, better code locality, and better expression of intent.

For more sample code, check out C-41 or GroceryList, which are real iOS apps written using ReactiveObjC. Additional information about RAC can be found in this folder.

When to use ReactiveObjC

Upon first glance, ReactiveObjC is very abstract, and it can be difficult to understand how to apply it to concrete problems.

Here are some of the use cases that RAC excels at.

Handling asynchronous or event-driven data sources

Much of Cocoa programming is focused on reacting to user events or changes in application state. Code that deals with such events can quickly become very complex and spaghetti-like, with lots of callbacks and state variables to handle ordering issues.

Patterns that seem superficially different, like UI callbacks, network responses, and KVO notifications, actually have a lot in common. RACSignal unifies all these different APIs so that they can be composed together and manipulated in the same way.

For example, the following code:

static void *ObservationContext = &ObservationContext;

- (void)viewDidLoad {
	[super viewDidLoad];

	[LoginManager.sharedManager addObserver:self forKeyPath:@"loggingIn" options:NSKeyValueObservingOptionInitial context:&ObservationContext];
	[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(loggedOut:) name:UserDidLogOutNotification object:LoginManager.sharedManager];

	[self.usernameTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged];
	[self.passwordTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged];
	[self.logInButton addTarget:self action:@selector(logInPressed:) forControlEvents:UIControlEventTouchUpInside];
}

- (void)dealloc {
	[LoginManager.sharedManager removeObserver:self forKeyPath:@"loggingIn" context:ObservationContext];
	[NSNotificationCenter.defaultCenter removeObserver:self];
}

- (void)updateLogInButton {
	BOOL textFieldsNonEmpty = self.usernameTextField.text.length > 0 && self.passwordTextField.text.length > 0;
	BOOL readyToLogIn = !LoginManager.sharedManager.isLoggingIn && !self.loggedIn;
	self.logInButton.enabled = textFieldsNonEmpty && readyToLogIn;
}

- (IBAction)logInPressed:(UIButton *)sender {
	[[LoginManager sharedManager]
		logInWithUsername:self.usernameTextField.text
		password:self.passwordTextField.text
		success:^{
			self.loggedIn = YES;
		} failure:^(NSError *error) {
			[self presentError:error];
		}];
}

- (void)loggedOut:(NSNotification *)notification {
	self.loggedIn = NO;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	if (context == ObservationContext) {
		[self updateLogInButton];
	} else {
		[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
	}
}

… could be expressed in RAC like so:

- (void)viewDidLoad {
	[super viewDidLoad];

	@weakify(self);

	RAC(self.logInButton, enabled) = [RACSignal
		combineLatest:@[
			self.usernameTextField.rac_textSignal,
			self.passwordTextField.rac_textSignal,
			RACObserve(LoginManager.sharedManager, loggingIn),
			RACObserve(self, loggedIn)
		] reduce:^(NSString *username, NSString *password, NSNumber *loggingIn, NSNumber *loggedIn) {
			return @(username.length > 0 && password.length > 0 && !loggingIn.boolValue && !loggedIn.boolValue);
		}];

	[[self.logInButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIButton *sender) {
		@strongify(self);

		RACSignal *loginSignal = [LoginManager.sharedManager
			logInWithUsername:self.usernameTextField.text
			password:self.passwordTextField.text];

			[loginSignal subscribeError:^(NSError *error) {
				@strongify(self);
				[self presentError:error];
			} completed:^{
				@strongify(self);
				self.loggedIn = YES;
			}];
	}];

	RAC(self, loggedIn) = [[NSNotificationCenter.defaultCenter
		rac_addObserverForName:UserDidLogOutNotification object:nil]
		mapReplace:@NO];
}

Chaining dependent operations

Dependencies are most often found in network requests, where a previous request to the server needs to complete before the next one can be constructed, and so on:

[client logInWithSuccess:^{
	[client loadCachedMessagesWithSuccess:^(NSArray *messages) {
		[client fetchMessagesAfterMessage:messages.lastObject success:^(NSArray *nextMessages) {
			NSLog(@"Fetched all messages.");
		} failure:^(NSError *error) {
			[self presentError:error];
		}];
	} failure:^(NSError *error) {
		[self presentError:error];
	}];
} failure:^(NSError *error) {
	[self presentError:error];
}];

ReactiveObjC makes this pattern particularly easy:

[[[[client logIn]
	then:^{
		return [client loadCachedMessages];
	}]
	flattenMap:^(NSArray *messages) {
		return [client fetchMessagesAfterMessage:messages.lastObject];
	}]
	subscribeError:^(NSError *error) {
		[self presentError:error];
	} completed:^{
		NSLog(@"Fetched all messages.");
	}];

Parallelizing independent work

Working with independent data sets in parallel and then combining them into a final result is non-trivial in Cocoa, and often involves a lot of synchronization:

__block NSArray *databaseObjects;
__block NSArray *fileContents;

NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *databaseOperation = [NSBlockOperation blockOperationWithBlock:^{
	databaseObjects = [databaseClient fetchObjectsMatchingPredicate:predicate];
}];

NSBlockOperation *filesOperation = [NSBlockOperation blockOperationWithBlock:^{
	NSMutableArray *filesInProgress = [NSMutableArray array];
	for (NSString *path in files) {
		[filesInProgress addObject:[NSData dataWithContentsOfFile:path]];
	}

	fileContents = [filesInProgress copy];
}];

NSBlockOperation *finishOperation = [NSBlockOperation blockOperationWithBlock:^{
	[self finishProcessingDatabaseObjects:databaseObjects fileContents:fileContents];
	NSLog(@"Done processing");
}];

[finishOperation addDependency:databaseOperation];
[finishOperation addDependency:filesOperation];
[backgroundQueue addOperation:databaseOperation];
[backgroundQueue addOperation:filesOperation];
[backgroundQueue addOperation:finishOperation];

The above code can be cleaned up and optimized by simply composing signals:

RACSignal *databaseSignal = [[databaseClient
	fetchObjectsMatchingPredicate:predicate]
	subscribeOn:[RACScheduler scheduler]];

RACSignal *fileSignal = [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) {
	NSMutableArray *filesInProgress = [NSMutableArray array];
	for (NSString *path in files) {
		[filesInProgress addObject:[NSData dataWithContentsOfFile:path]];
	}

	[subscriber sendNext:[filesInProgress copy]];
	[subscriber sendCompleted];
}];

[[RACSignal
	combineLatest:@[ databaseSignal, fileSignal ]
	reduce:^ id (NSArray *databaseObjects, NSArray *fileContents) {
		[self finishProcessingDatabaseObjects:databaseObjects fileContents:fileContents];
		return nil;
	}]
	subscribeCompleted:^{
		NSLog(@"Done processing");
	}];

Simplifying collection transformations

Higher-order functions like map, filter, fold/reduce are sorely missing from Foundation, leading to loop-focused code like this:

NSMutableArray *results = [NSMutableArray array];
for (NSString *str in strings) {
	if (str.length < 2) {
		continue;
	}

	NSString *newString = [str stringByAppendingString:@"foobar"];
	[results addObject:newString];
}

RACSequence allows any Cocoa collection to be manipulated in a uniform and declarative way:

RACSequence *results = [[strings.rac_sequence
	filter:^ BOOL (NSString *str) {
		return str.length >= 2;
	}]
	map:^(NSString *str) {
		return [str stringByAppendingString:@"foobar"];
	}];

System Requirements

ReactiveObjC supports OS X 10.8+ and iOS 8.0+.

Importing ReactiveObjC

To add RAC to your application:

  1. Add the ReactiveObjC repository as a submodule of your application's repository.
  2. Run git submodule update --init --recursive from within the ReactiveObjC folder.
  3. Drag and drop ReactiveObjC.xcodeproj into your application's Xcode project or workspace.
  4. On the "Build Phases" tab of your application target, add RAC to the "Link Binary With Libraries" phase.
  5. Add ReactiveObjC.framework. RAC must also be added to any "Copy Frameworks" build phase. If you don't already have one, simply add a "Copy Files" build phase and target the "Frameworks" destination.
  6. Add "$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/include" $(inherited) to the "Header Search Paths" build setting (this is only necessary for archive builds, but it has no negative effect otherwise).
  7. For iOS targets, add -ObjC to the "Other Linker Flags" build setting.
  8. If you added RAC to a project (not a workspace), you will also need to add the appropriate RAC target to the "Target Dependencies" of your application.

To see a project already set up with RAC, check out C-41 or GroceryList, which are real iOS apps written using ReactiveObjC.

More Info

ReactiveObjC is inspired by .NET's Reactive Extensions (Rx). Most of the principles of Rx apply to RAC as well. There are some really good Rx resources out there:

RAC and Rx are both frameworks inspired by functional reactive programming. Here are some resources related to FRP:

reactiveobjc's People

Contributors

335g avatar adlai-holler avatar aitskovi avatar alanjrogers avatar almassapargali avatar andersio avatar bigboybad avatar brow avatar erichoracek avatar ikesyo avatar javisoto avatar jlawton avatar jonsterling avatar joshaber avatar joshvera avatar jspahrsummers avatar kastiglione avatar kkazuo avatar lawrencelomax avatar lbrndnr avatar mattjgalloway avatar mdiep avatar nachosoto avatar natestedman avatar neilpa avatar robrix avatar ruiaaperes avatar sharplet avatar thenikso avatar tomj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reactiveobjc's Issues

RAC UITextField big bug

copy: ReactiveCocoa/ReactiveCocoa#3179 (comment)

RACSignal *signal1 = self.textField.rac_textSignal;
RACSignal *cSignal1 = [signal1 distinctUntilChanged];
[cSignal1 subscribeNext:^(NSString *number) {

    NSInteger length = number.length;
    NSString *text = weakself.textField.text;
    DEBUGG(@"111Text: %@, number: %@", text, number);
    if (length >= 20)
    {
        NSString *subText = [number substringToIndex:20];
        DEBUGG(@"subText: %@, number: %@", subText, number);
        weakself.textField.text = subText;
    }
}];

first input text: 1234567891234567
second input text: 12345678912345678; show subtext: 1234567891234567
third input text: 12345678912345678; show subtext: 12345678912345678
because the subscribeNext block is not called;

RACMulticastConnection and error handling

Hello,
Using RACMulticastConnection it's quite easy to cache the result of a network request. However I'm having trouble in handling gracefully the following scenario:
If the signal completes with error the first new subscription should trigger again the signal, all this in a thread safe manner.

Thanks.

readme link 404

The link of "Escape from Callback Hell" in readme is 404.

Lightweight generics on Signal purely for bridging and type annotations

Now that Obj-C lightweight generics can be bridged to swift, its seems like it could be quite useful to have lightweight generics on the values sent by RACSignal. This would be extremely helpful to those that are either planning or undergoing a piecemeal migration of a codebase from ReactiveObjC to ReactiveSwift.

From playing around with it a little bit, generic type annotations added to RACSignal now appear to be fully accessible in Swift 3. By using the lightweight generics from RACSignal, the pain of bridging between the two could be reduced significantly. However, it appears that this would cause a breaking change in the way that signals are bridged to Swift, since Swift extensions of Obj-C classes do not have access to the Obj-C lightweight generic annotations. A set of free functions for RACSignal → Signal/SignalProducer conversion should work for this though.

While there was previously a discussion about adding lightweight generics to RACSignal, that approach seemed to break down in two ways:

  • When you attempted to create generic signal chains, the compiler couldn't handle an method changing the generic type of the returned signal from the generic type of the signal that the operator was performed on. I don't think this type of functionality should be included as part of this addition. Instead, I'm suggesting that the generic types should not be included in any of the signal operators so that we can define away this class of issues entirely.
  • Issues with the inheritance hierarchy of RACSignal : RACStream when both have generic types. My suggestion is that we don't make RACStream generic whatsoever as part of this, since in practice RACStream is almost never exposed in interfaces, and tends to be used as an implementation detail only.

As such, the only places where the ValueType generic type would be used in a method would be in RACSignal methods where the return type is not a RACSignal. For example, the in the -[RACSignal subscribe...] family of methods:

@interface RACSignal<__covariant ValueType> (Subscription)

- (RACDisposable *)subscribeNext:(void (^)(ValueType _Nullable x))nextBlock;

@end

This would also encompass adding a second generic type to RACCommand:

@interface RACCommand<__contravariant InputType, __covariant ValueType> : NSObject

- (RACSignal<ValueType> *)execute:(nullable InputType)input;

@end

This change would expressly not be for improving the compiler checks on existing RACSignal chains written in Objective-C. As such, existing Objective-C RACSignal usage would be largely unaffected.

This migration could also involve adding type annotations to most of the UIKit/AppKit/Foundation categories that expose a RACSignal. While this wouldn't be very helpful for existing consumers of these categories since they have mostly been ported to native Swift 3 with REX, it would serve as a better form of documentation than the existing documentation comments.

Let me know what your thoughts are. I can take a shot at implementing this if it seems like something worth doing. Thanks!

Unknown type name RACSubject, RACSignal

Hi,

I am in dire need of help, I am working on a project started by somebody else. I just did a pod update and I have:

Installing ReactiveCocoa (5.0.1)
Installing ReactiveSwift (1.1.0)
Installing Result (3.2.1)

When I build the project i get:

screen shot 2017-03-08 at 13 34 30

Any input is much appreciated.

Adrian

Swift compiler crash

Hi,

While porting the project to Swift 3 I cannot get around some segmentation faults. The scenario is the following. I have an Objective-C protocol:

#import "ViewModelProtocol.h"

@class RACCommand;
@class RACSignal;

@protocol ListViewModelProtocol <ViewModelProtocol>

@property (nonatomic, strong) RACCommand *listItemSelectedCommand;
@property (nonatomic, strong, readonly) RACCommand *reloadDataCommand;
@property (nonatomic, strong) NSArray    *datasource;

@end

Which is implemented in Swift as:

    lazy var reloadDataCommand: RACCommand<AnyObject, AnyObject>! = {
        return RACCommand { (argument) -> RACSignal<AnyObject> in
            return self.reloadDataAction(argument: argument)
        }
    }()

In the end I always get the same error:

1. While emitting SIL for getter for reloadDataCommand at /Users/vmagalhaes/work/ios/nlife/nlife/ViewModels/ParkingViewModel.swift:28:14

Probably this has nothing to do with ReactiveObjc but any help would be appreciated.

There is a reference to something similar but my property is not optional https://bugs.swift.org/browse/SR-1825

Thanks in advance,

[RACStream(Operations) reduceEach:]_block_invoke

1:testLoginViewModel.m

(RACSignal *)isValidUsernameAndPasswordSignal
{
return [RACSignal combineLatest:@[RACObserve(self, username), RACObserve(self, password)] reduce:^(NSString *username, NSString *password) {
return @([self isValidEmail:username] && [self isValidPassword:password]);
}];
}
//验证

(BOOL)isValidEmail:(NSString )data
{
NSString emailPattern =
@"(?:[a-z0-9!#$%&'+/=?^{|}~-]+(?:\.[a-z0-9!#$%\&'*+/=?\^{|}"
@"~-]+)|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-"
@"x7f]|\[\x01-\x09\x0b\x0c\x0e-\x7f])")@(?:(?:[a-z0-9](?:[a-"
@"z0-9-][a-z0-9])?.)+a-z0-9?|[(?:(?:25[0-5"
@"]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-"
@"9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21"
@"-\x5a\x53-\x7f]|\[\x01-\x09\x0b\x0c\x0e-\x7f])+)])";
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:emailPattern options:NSRegularExpressionCaseInsensitive error:&error];
NSTextCheckingResult *match = [regex firstMatchInString:data options:0 range:NSMakeRange(0, [data length])];
return match != nil;
}

(BOOL)isValidPassword:(NSString *)password
{
return password.length >= 6;
}

2:I do unit testing on the above file,But it has been reporting the problem above;[RACStream(Operations) reduceEach:]_block_invoke

#import <Kiwi/Kiwi.h>

#import <ReactiveCocoa/ReactiveCocoa.h>
#import "testLoginViewModel.h"

SPEC_BEGIN(testLoginViewModelSpec)

describe(@"testLoginViewModel", ^{
__block testLoginViewModel* viewModel = nil;

beforeEach(^{
viewModel = [testLoginViewModel new];
});

afterEach(^{
viewModel = nil;
});

context(@"when username is wujunyang and password is freedom", ^{
__block BOOL result = NO;

it(@"should return signal that value is YES", ^{
    viewModel.username = @"[email protected]";
    viewModel.password = @"123456";
    
    [[viewModel isValidUsernameAndPasswordSignal] subscribeNext:^(id x) {
        result = [x boolValue];
    }];
    
     [[theValue(result) should] beYes];
});

});
});

SPEC_END

Where is the problem?

RACCommand's executionSignals's subscribeNext block or error's block execute more times?

i use RACCommand to send a network request in a sendController, you can click this controller right barbuttomitem, then will execute a command; if success, this sendController will be dismissed;
but when fail, you know i add the log in the errors's subscribeNext block; i click barbuttomitem first time, the log will be show one time; but when i click at the second time, the log will be show two times; and it will always added one times if i continue click;
so, i want to know is there some solution to settle this problem? thanks!

A "navigationItem.rightBarButtonItem" Exception"

I define an enable RACSignal ,which is bind to RAC(RAC(self.navigationItem.rightBarButtonItem,enabled), at same time , its also be a enableSignal of "self.navigationItem.rightBarButtonItem.rac_command".
Then the excetpion is thrown:

OG: error: session_id=3EFB33D463E8F6453C31794E3A35EECA, context=Signal name: is already bound to key path "enabled" on object , adding signal name: is undefined behavior
(null)
((
0 CoreFoundation 0x0000000182126dc8 + 148
1 libobjc.A.dylib 0x000000018178bf80 objc_exception_throw + 56
2 CoreFoundation 0x0000000182126c80 + 0
3 Foundation 0x0000000182aac1c0 + 88
4 ReactiveCocoa 0x00000001006d2684 -[RACSignal(Operations) setKeyPath:onObject:nilValue:] + 2216
5 ReactiveCocoa 0x00000001006d1da4 -[RACSignal(Operations) setKeyPath:onObject:] + 116
6 ReactiveCocoa 0x00000001006fa4d8 -[UIBarButtonItem(RACCommandSupport) setRac_command:] + 308
7 Neighbours 0x000000010003f834 -[FillMobileVC bindRac2ThisVC] + 800
8 Neighbours 0x000000010003f3b8 -[FillMobileVC viewDidLoad] + 368

The Code throw Exception is listed below, if "RAC(self.navigationItem.rightBarButtonItem,enabled) = enablSginal" is removed , it works fine.

RACSignal* enablSginal = [self.mobileTextField.rac_textSignal map:^id(id value) {
NSString* mobile = (NSString*)value;
BOOL nextEnabled = [MobileFormatUtil isValideMobile:mobile];
return @(nextEnabled);
}];

RAC(self.navigationItem.rightBarButtonItem,enabled) = enablSginal; //The bug Line//

__weak FillMobileVC* weakSelf = (FillMobileVC*)self;
self.navigationItem.rightBarButtonItem.rac_command = [[RACCommand alloc]initWithEnabled:enablSginal signalBlock:^RACSignal *(id input) {
return [weakSelf requestVeiryCodeSignal:weakSelf.mobileTextField.text];
}];

Tagged release and a prebuilt binary for Carthage?

Not sure about the versioning schema you'd like to use (e.g. is this ReactiveObjC 1.0, or 2.6), but it would be nice to be able to include this repo via Carthage alongside a prebuilt framework.

Thanks for separating this out!

RAC key-value validation feature request

As a programmer,
I want to have ability to use KVV (key-value validation) when do KVC via RAC
to reduce duplication
and avoid model properties key-path hard-coding each time.


Do something like this

    RACSignal *usernameSignal = [RACObserve(self, username) map:^RACTuple *_Nonnull(id _Nullable value) {
        NSError *error;
        return
        RACTuplePack(
        @([self.credentials validateValue:&value
                               forKeyPath:@"username"
                                    error:&error]),
                     error);
    }];

    self.usernameValidation = [usernameSignal map:^NSError *_Nullable(RACTuple *_Nonnull tuple) {
        return tuple.second;
    }];
    RAC(self.credentials, username) = [[RACObserve(self, username) zipWith:usernameSignal]
                                       filter:^BOOL(RACTuple *_Nonnull tuple) {
                                           return tuple.second;
                                       }];

In way like

    [RAC(self.credentials, username)
     validateTo:self.usernameValidation] = RACObserve(self, username);

Where

  • self - view modeller
  • self.credentials - model
  • self.username - data to validate
  • self.credentials.username - validating property
  • self.usernameValidation - signal with results of validation.

你好 我使用命令报错了

Failed to check out repository into /Users/huabinhu/Library/Caches/org.carthage.CarthageKit/dependencies/Quick: No object named "swift-3.0" exists

RACObserve Expected ';' after expression

So I am trying to create a new email signal, but getting error: "Expected ';' after expression".

- (instancetype)init {
    self = [super init];
    if (self) {
        RACSignal *emailSignal = RACObserve(self, email);
    }
    return self;
}

Generic type resolving error (forward class declaration with lightweight generic RACSignal type)

ReactiveObjC (2.1.0)

Have ConfigurationViewModel.h

#import "RootViewModel.h"


@class RACSignal<ObjectType>;
@class Configuration;


@interface ConfigurationViewModel : RootViewModel

- (RACSignal<Configuration *> *_Nonnull)updateConfiguration;

@end

And with SomeViewModel.m

#import "SomeViewModel.h"
#import "RootViewModel_Internal.h"
#import <ReactiveObjC/ReactiveObjC.h>
#import "ConfigurationViewModel.h"


@implementation SomeViewModel

- (void)setupConfiguration
{
    self.configurationViewModel = [self.flow configurationViewModel:self];
    [[self.configurationViewModel updateConfiguration] reply];
_______________________________________________________^
}

@end

Got error:

ARC Semantic issue
/**/SomeViewModel.m:107:56: No visible @interface for 'RACSignal<Configuration *>' declares the selector 'reply'

Purpose of this project

Hi,

I've been using ReactiveCocoa locked to the last Objective C version

Only today I became aware of this project.

Is this meant to be a "replacement" for ReactiveCocoa version 2.5?

Can I safely assume that I can replace ReactiveCocoa in my podfile to ReactiveObjC?

Will this project have future updates?

Could not extend RACSignal with generic in Swift 3

Most of the code in my app is ObjC, and my Unit Test code is pure Swift. After switch from ReactiveCocoa repo to ReactiveObjC, the extension for RACSignal no longer compile. The code is something like this

extension RACSignal {
    
    fileprivate func foo() -> RACSignal
        //...
    }
}

and I get error msg below:

Extension of a generic Objective-C class cannot access the class's generic parameters at runtime

The issue has been discussed this proposal

I wonder is there any way to work around this other than explicitly checkout commit before this ?
Thx in advance😝

-[RACMulticastConnection connect] not working as expected.

When calling connect it connects and subscribes correctly to the underlying signal.
Then, disposing the returned RACDisposable unsubscribe correctly from the underlying signal.
Then, reconnecting no longer subscribe to the underlying signal when it should to.

Here is a code you can paste in a sample project to verify.

- (void)testRx
{
    NSLog(@"Current Time: %@", NSDate.date);
    RACSignal *source = [RACSignal interval:1 onScheduler:[RACScheduler scheduler]]; //creates a sequence
    
    RACMulticastConnection *hot = [source publish]; // convert the sequence into a hot sequence
    
    RACDisposable *subscription1 = [hot.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"1: Next(%@)", x);
    } error:^(NSError * _Nullable error) {
        NSLog(@"1: Error(%@)", error);
    } completed:^{
        NSLog(@"1: Completed");
    }];
    
    NSLog(@"Current Time after 1st subscription: %@", NSDate.date);
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        __block RACDisposable *connectionDisposable = [hot connect]; // hot is connected to source and starts pushing value to subscribers
        NSLog(@"Current Time after Connect: %@", NSDate.date);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"Current Time just before 2nd subscription: %@", NSDate.date);
            
            RACDisposable *subscription2 = [hot.signal subscribeNext:^(id  _Nullable x) {
                NSLog(@"2: Next(%@)", x);
            } error:^(NSError * _Nullable error) {
                NSLog(@"2: Error(%@)", error);
            } completed:^{
                NSLog(@"2: Completed");
            }];
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"Disconnecting");
                [connectionDisposable dispose];
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    NSLog(@"Reconnecting");
                    connectionDisposable = [hot connect]; // this should resubscribe and broadcast values !
                    
                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                        NSLog(@"Ending");
                        [subscription1 dispose];
                        [subscription2 dispose];
                        [connectionDisposable dispose];
                    });
                });
            });
        });
    });
}

You can test the equivalent in C# using the code sample from here.

I will work on a PR to fix this.

RACCommand - dispose executing signal

I have a little confused about dispose mechanism of RACCommand. I want to dispose subscription of executing signal manually, but disposableBlock invokes only after sendCompleted event. It's a problem of my real task, hope someone can help me.

- (void)test {
    RACCommand *rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        return [self signal];
    }];

    RACDisposable *rac_dispose = [[rac_command execute:nil] subscribeNext:^(id x) {}];
    [rac_dispose dispose];
}

- (RACSignal *)signal {
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [subscriber sendNext:nil];
            [subscriber sendCompleted];
        });
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"dispose block");
        }];
    }];
}

Sort call sequence for subscribers.

Hi guys, i'm have a question about RAC2.5
I have 3 subscribers on my signal, i'm don't know when they will be subscribed, but know one of it signals must called before other all. How can i do that on RAC2.5?

Now solve this feature with adding delay:.1 for another subscribers.

RACSubject flattenMap to long running signal break subscribing.

   RACSubject *subject = [RACSubject subject];

   RACSignal *resultSignal = [[[subject filter:^BOOL(NSNumber *item) {

        return [item isKindOfClass:[NSNumber class]];
    }] flattenMap:^RACSignal *(NSNumber *item) {

        return [RACSignal createSignal:
                ^RACDisposable *(id<RACSubscriber> subscriber) {
                    __block TheTask *veryLongRunningTask =
                    [self doVeryLongRunningTaskForItem:item
                                        withCompletion:
                     ^(id result, NSError *error) {

                         if (!error) {
                             [subscriber sendNext:result];
                             [subscriber sendCompleted];
                         } else {
                             [subscriber sendError:error];
                         }
                     }];

                    return [RACDisposable disposableWithBlock:^{
                        [veryLongRunningTask cancel];
                    }];
        }];
   [subject sendNext:@1];
   [subject sendNext:@2];

I need to receive both results for @1 and @2 values in resultSignal, but sending second time next signal to subject very soon after first one, until veryLongRunningTask not completed yet, break expected behaviour: resultSignal do not return neither values, and will ignore all next values after that.

Is it bug, or what exactly did I do wrong, and how should so?


version:

 $ grep ReactiveObjC Podfile.lock
    - ReactiveObjC (~> 2.1)
  - ReactiveObjC (2.1.0)
    - ReactiveObjC (~> 2.1)
  - ReactiveObjC (~> 2.1)
  ReactiveObjC: 2edae120982d8e6d5407503a10edcfdd87eb8639

RACCommand's executing property abnormal

The RACCommand's RACSignal *immediateExecuting logic has been refactored in recent RAC version which makes me a little bit curiously.

The signalBlock of a command may become complexly sometimes..
One scenario that will cause executing signal stuck on @yES if the command received a disposed signal.

The test case I pasted here can re-produce this bug:

qck_it(@"should update executing signal status with a disposed source signal", ^{
	__block NSUInteger executingStage = 0;
	RACCommand<RACSignal *> *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) {
		return signal;
	}];
	
	// disposed source signal
	RACSubject *subject = [RACSubject.subject takeUntilBlock:^BOOL(id x) {
		return YES;
	}];
	
	// trigger executing to @YES
	[command execute:subject];
	
	// Stage 0: not started
	// Stage 1: start executing <= we are stucking at here
	// Stage 2: stop executing  <= the stage we were expected
	[command.executing subscribeNext:^(NSNumber* executingValue) {
		if (executingValue.boolValue) {
			if (0 == executingStage) {
				executingStage = 1;
			}
		} else {
			if (1 == executingStage) {
				executingStage = 2;
			}
		}
	}];
	expect(@(executingStage)).toEventually(equal(@2));
});

Ummm... I cannot figure it out the reason with debugging (for awhile).

Codes used in RACSignal *immediateExecuting:

- (instancetype)startWith:(id)value {
	return [[[self.class return:value]
		concat:self]
		setNameWithFormat:@"[%@] -startWith: %@", self.name, RACDescription(value)];
}

It looks like a signal concat a disposed source signal will lead no any further subscribtion.

Am I misleading anything on it?

First in interval

Hi, we're looking for a way to get the first value sent in an interval, all other values sent in that interval should be ignored, and then this should repeat.

So for example (with an interval of 1 second):

4-(wait 0.4 second)-5-(wait 0.4 second)-6-(wait 0.4 second)-7-(wait 0.4 second)-|
would become:
4-7-|

This differs from throttle or buffer, because the first value should be sent right away rather than waiting for the end of the interval.

I've found a Stackoverflow answer that addresses a similar problem, although it uses a Window operator. It seems that ReactiveObjc used to have such an operator but it was deprecated here.

With the currently existing operators, is there a nice way to accomplish what we're trying to do? We've managed to do it by copying and slightly modifying the implementation of throttle, but that seems like a very messy way to do this.

Any help would be greatly appreciate, thank you!

Bootstrap?

Should a bootstrap script exist in the scripts folder based on step two of the setup?

First release?

Since ReactiveObjC is just what was releasable-ReactiveCocoa, minus a bunch of stuff, it's unsurprisingly in a working/releasable state. Might it be appropriate to.. do that? :)

RACSubject explicit generic

RACSubject currently not a generic type, despite that it inherited from RACSignal. Explicit generic type declaration required.

RACObserve: Expected ';' after expression

I use RACObserve macro, I'm also getting the error: "Expected ';' after expression" in Xcode7.3. And ReactiveObjC v2.1.0 installed using cocoapod.But in Xcode8 is not.
image

feature request: RACTuple with generics

Is it possible to implement statically typed RACTuple with lightweight generics, e.g. RACTuple2<NSString, NSNumber>, RACTuple3<NSString, NSValue, NSNumber>?

Trouble with throttle operator

Hi, is anyone getting some trouble with throttle when creating a signal with +createSignal method:

RACSignal* signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"%@", [NSThread currentThread]);
        
        [subscriber sendNext:@"1"];
        
        [subscriber sendNext:@"2"];
        
        [NSThread sleepForTimeInterval:4];
        
        [subscriber sendNext:@"3"];
        
        [subscriber sendNext:@"4"];
        
        [subscriber sendCompleted];
        return nil;
    }];
    
    [[[signal subscribeOn:[RACScheduler scheduler]] throttle:3] subscribeNext:^(id data) {
        NSLog(@"onNext: %@", data);
    } completed:^{
        NSLog(@"Completed");
    }];

Expected: 2
Actual Result: 4

Am i missing something ?

flattenMap new return type

I am in the process of migrating from ReactiveCocoa 2.5 to ReactiveObjC 2.1.2

I've noticed that flattenMap's return type changed from RACStream to __kindOf RACSignal

Shouldn't have changed to __kindOf RACStream as RACSignal continues to be a subclass of RACStream?

Generics in `map:`

@erichoracek, @mdiep Can ReactiveObjC use generics for map:-like operators as well, in way described here (use additional mapper wrapper for strict type checking)? Disadvantage of that approach -- it can't be done implicitly.

'metamacros.h' file not found

I have ReactObjC installed via Cocoapods, when I try to build my project I get:
RACTuple.h:10:9: 'metamacros.h' file not found

Any idea on how I can fix this issue?

UIImagePickerController+RAC and AppStore submission

Due to new iTunesConnect rules, new uploaded builds would be rejected for privacy-sensitive data access, if these are not specified in app's info.plist.

ReactiveObjC users would be rejected because of missing NSPhotoLibraryUsageDescription.
This happens due to UIImagePickerController+RACSignalSupport.h, which uses UIImagePickerController.
This happens even if this category isn't used in project, only existence of files is enough.

It's important because developers should not be forced to include privacy descriptions which they don't use.

Simple removing
#import <ReactiveObjC/UIImagePickerController+RACSignalSupport.h>
from ReactiveObjC.h file doesn't fixes the issue.

Other frameworks examples:
https://forums.developer.apple.com/thread/62229
https://groups.google.com/forum/#!topic/google-admob-ads-sdk/UmeVUDrcDaw

Improve performance of `setNameWithFormat:`

Close this issue at will. I felt it was cleaner to continue the discussion in a new thread instead of discussing in a closed thread from 2014 ReactiveCocoa/ReactiveCocoa#1083 on the swift repo.

I think I found a way to improve the performance of setNameWithFormat using blocks. Memory and performance could potentially be improved when setNameWithFormat is active (typically debug builds). I haven't had any problems with this my self, but reading that issue thread ReactiveCocoa/ReactiveCocoa#1083 made me think it's a problem for some people.

Well, instead of creating the strings directly we can defer (and cache) this operation:
https://gist.github.com/hfossli/3682cd8769bd4d167f6e05ee4b34af8c

I wrote this test. And my prototype passes that test fine. This should mean better performance and lower memory usage.

@interface Foo : NSObject
@property (nonatomic, assign) int descriptionRequestedCount;
@end

@implementation Foo

- (NSString *)description
{
    self.descriptionRequestedCount++;
    return @"<Foo 0xFOOBAR>";
}

@end

@interface RACSignalTest : XCTestCase
@end
@implementation RACSignalTest

- (void)testWithSignal
{
    Foo *foo = [Foo new];
    RACSignal *signal = [[RACSignal new] someOperatorWithObject:foo];
    XCTAssertTrue(foo.descriptionRequestedCount == 0, @"");
    XCTAssertEqualObjects([signal name], @"transformed using `someOperatorWithObject` with object <Foo 0xFOOBAR>");
    XCTAssertTrue(foo.descriptionRequestedCount == 1, @"");
    XCTAssertEqualObjects([signal name], @"transformed using `someOperatorWithObject` with object <Foo 0xFOOBAR>");
    XCTAssertTrue(foo.descriptionRequestedCount == 1, @"");
}

@end

Generic type constraint not working with map operator

I've admittedly not looked deeply into lightweight generics in Objective-C, but I'm seeing an issue when changing type in a signal chain using the map operator.

image

ReactiveObjC expects the output type of the original signal rather than the mapped one, and due to the presence of the generic refuses to allow type coercion. The following results in a build error:

image

My thought is this may be related to #34 as instancetype is picking up on the generic Signal, and that generic isn't updated with map.

Make it possible to pass RACSignalAsynchronousWaitTimeout

Hi,

RACSignalAsynchronousWaitTimeout is used in - (id)asynchronousFirstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error but it is not possible to change that value. If test is expected to take longer then defined value, it will fail.

Would it be possible to create alternative method that accepts this value as well?

Ambiguous use of distinctUntilChanged

Hi,

The following code is causing an ambiguous error:

let observableProducts: RACSignal<AnyObject> = RACObserve(licensingService as! NSObject, "products")
let productChanges = observableProducts.distinctUntilChanged()
Ambiguous use of 'distinctUntilChanged()'
- Found this candidate (ReactiveObjC.RACSignal<ValueType>)
- Found this candidate (ReactiveObjC.RACStream)

The utility class I'm using to bridge RACObserve is:

import Foundation

struct RAC  {
    var target : NSObject!
    var keyPath : String!
    var nilValue : AnyObject!
    
    init(_ target: NSObject!, _ keyPath: String, nilValue: AnyObject? = nil) {
        self.target = target
        self.keyPath = keyPath
        self.nilValue = nilValue
    }
    
    func assignSignal(signal : RACSignal<AnyObject>) {
        signal.setKeyPath(self.keyPath, on: self.target, nilValue: self.nilValue)
    }
}

extension NSObject {
    func RACObserve(_ target: NSObject!,_ keyPath: String) -> RACSignal<AnyObject>! {
        return target.rac_values(forKeyPath: keyPath, observer: self)
    }
}


infix operator <~
func <~ (rac: RAC, signal: RACSignal<AnyObject>!) {
    rac.assignSignal(signal: signal)
}

infix operator ~>
func ~> (signal: RACSignal<AnyObject>!, rac: RAC) {
    rac.assignSignal(signal: signal)
}

Is there any way to workaround this ? I cannot use the pure Swift version for now.

Thanks in advance,

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.