Reactivecocoa, the most popular iOS function responsive Programming library (version 2.5), does not have one! introduction
Project home: Reactivecocoa
Example Download: https://github.com/ios122/ios122
Brief comment: The most popular, most valuable iOS responsive programming library, not one of the perfect partners for the!ios MVVM pattern, more on MVVM and Reactivecocoa, reference to this article: "High-energy" Reactivecocoa and MVVM Primer
Note: Reactivecocoa Latest version 3.0, using Swift Rewrite, minimum support iOS8.0, and the actual status of most companies in the country (generally required minimum compatibility iOS7.0), so choose a lower version of the compatibility 2.5 version for interpretation and interpretation.
System Requirements
Installation
It is recommended to use CocoaPods installation:
platform :ios, ‘7.0‘pod "ReactiveCocoa" # RAC,一个支持响应式编程的库.
Entry
Reactivecocoa is inspired by function-responsive programming. Reactivecocoa is often referred to as Rac.rac, and instead of using variables, the signals (as RACSignal represented) are used to capture the values of current and future data or views.
By linking, combining and responding to the signals, the software can be written in a declarative way, so that it is no longer necessary to monitor and update the values of the data or views frequently.
One of the main features of RAC is to provide a single, unified way to handle various asynchronous operations-including proxy methods, block callbacks, target-action mechanisms, notifications, and KVO.
This is a simple example:
// 当self.username变化时,在控制台打印新的名字.//// RACObserve(self, username) 创建一个新的 RACSignal 信号对象,它将会发送self.username当前的值,和以后 self.username 发生变化时 的所有新值.// -subscribeNext: 无论signal信号对象何时发送消息,此block回调都将会被执行.[RACObserve(self, username) subscribeNext:^(NSString *newName) { NSLog(@"%@", newName);}];
However, unlike KVO, the signals signal object supports chained operations:
// 只打印以"j"开头的名字.//// -filter: 当其bock方法返回YES时,才会返回一个新的RACSignal 信号对象;即如果其block方法返回NO,信号不再继续往下传播.[[RACObserve(self, username) filter:^(NSString *newName) { return [newName hasPrefix:@"j"]; }] subscribeNext:^(NSString *newName) { NSLog(@"%@", newName); }];
Signals signals can also be used to derive attributes (that is, those properties that are determined by the values of other properties, such as the person may have a property of age and an attribute Isyong is young, Isyong is inferred from the value of the ages property, as determined by the value of the age itself). It is no longer necessary to monitor the value of a property, and then to update the values of other properties that are affected by the new value of this property. RAC can support the expression of derived properties in a way that signales signals and operations.
// 创建一个单向绑定, self.password和self.passwordConfirmation 相等// 时,self.createEnabled 会自动变为true.//// RAC() 是一个宏,使绑定看起来更NICE.// // +combineLatest:reduce: 使用一个 signals 信号的数组;// 在任意signal变化时,使用他们的最后一次的值来执行block;// 并返回一个新的 RACSignal信号对象来将block的值用作属性的新值来发送;// 简单说,类似于重写createEnabled 属性的 getter 方法.RAC(self, createEnabled) = [RACSignal combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ] reduce:^(NSString *password, NSString *passwordConfirm) { return @([passwordConfirm isEqualToString:password]); }];// 使用时,是不需要考虑属性是否是派生属性以及以何种方式绑定的:[RACObserve(self, createEnabled) subscribeNext: ^(NSNumber * enbable){ NSLog(@"%@", enbable);}];
Signals signals can be created based on any data stream that changes over time, not just kvo. For example, they can be used to indicate a click event for a button:
// 任意时间点击按钮,都会打印一条消息. //// RACCommand 创建代表UI事件的signals信号.例如,单个信号都可以代表一个按钮被点击,// 然后会有一些额外的操作与处理.//// -rac_command 是NSButton的一个扩展.按钮被点击时,会将会把自身发送给rac_command self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) { NSLog(@"button was pressed!"); return [RACSignal empty];}];
or an asynchronous network request:
// 监听"登陆"按钮,并记录网络请求成功的消息.// 这个block会在来任意开始登陆步骤,执行登陆命令时调用.self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^(id sender) { // 这是一个假想中的 -logIn 方法, 返回一个 signal信号对象,这个对象在网络对象完成时发送 值. // 可以使用 -filter 方法来保证当且仅当网络请求完成时,才返回一个 signal 对象. return [client logIn];}];// -executionSignals 返回一个signal对象,这个signal对象就是每次执行命令时通过上面的block返回的那个signal对象.[self.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) { // 打印信息,不论何时我们登陆成功. [loginSignal subscribeCompleted:^{ NSLog(@"Logged in successfully!"); }];}];// 当按钮被点击时,执行login命令.self.loginButton.rac_command = self.loginCommand;
The signals signal can also indicate a timer, other UI events, or anything else that will change over time.
Using signals signals on asynchronous operations makes it possible to construct more complex behaviors by linking and transforming these signal signals. You can trigger this action after a set of operations is complete:
// 执行两个网络操作,并在它们都完成后在控制台打印信息.//// +merge: 传入一组signal信号,并返回一个新的RACSignal信号对象.这个新返回的RACSignal信号对象,传递所有请求的值,并在所有的请求完成时完成.即:新返回的RACSignal信号,在每个请求完成时,都会发送个消息;在所有消息完成时,除了发送消息外,还会触发"完成"相关的block.//// -subscribeCompleted: signal信号完成时,将会执行block.[[RACSignal merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]] subscribeCompleted:^{ NSLog(@"They‘re both done!"); }];
Signals signals can be linked to perform asynchronous operations in succession without the need for nested block calls. The usage is similar to:
// 用户登录,然后加载缓存信息,然后从服务器获取剩余的消息.在这一切完成后,输入信息到控制台.//// 假想的 -logInUser 方法,在登录完成后,返回一个signal信号对象.//// -flattenMap: 无论任何时候,signal信号发送一个值,它的block都将被执行,然后返回一个新的RACSignal,这个新的RACSignal信号对象会merge合并所有此block返回的signals信号为一个RACSignal信号对象.[[[[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 easier to bind the results of asynchronous operations:
// 创建一个单向的绑定,遮掩self.imagView.image就可以在用户的头像下载完成后自动被设置.//// 假定的 -fetchUserWithUsername: 方法返回一个发送用户对象的signal信号对象.//// -deliverOn: 创建一个新的 signals 信号对象,以在其他队列来处理他们的任务.// 在这个示例中,这个方法被用来将任务移到后台队列,并在稍后下载完成后返回主线程中.//// -map: 每个获取的用户都会传递进到这个block,然后返回新的RACSignal信号对象,这个// signal信号对象发送从这个block返回的值.RAC(self.imageView, image) = [[[[client fetchUserWithUsername:@"joshaber"] deliverOn:[RACScheduler scheduler]] map:^(User *user) { // 下载头像(这在后台执行.) return [UIImage imageWithData: [NSData dataWithContentsOfURL: user.avatarURL]]; }] // 现在赋值在主线程完成. deliverOn:RACScheduler.mainThreadScheduler];
When to use Reactivecocoa?
Reactivecocoa is very abstract, first contact, it is often difficult to understand how to use it to solve specific problems.
Here are some of the more advantageous scenarios for using RAC:
Handles asynchronous or event-driven data sources.
Most of the cocoa programs focus on responding to changes in user events or program state. The code that handles these situations can quickly become complicated, like spaghetti, with many callbacks and state variables to handle order problems.
Some programming patterns, on the surface, are somewhat similar, such as UI callback methods, response to network requests, and KVO notifications; in fact, they have a lot in common. Racsignal signal classes, unifying these different APIs to combine and manipulate them.
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.usernameTextF Ield.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]; }}
The
... can be overridden in such a way as RAC:
-(void) viewdidload {[Super viewdidload]; @weakify (self); RAC (Self.loginbutton, enabled) = [Racsignal combinelatest:@[self.usernameTextField.rac_textSignal, Self.passwordTextField.rac_textSignal, Racobserve (Loginmanager.sharedmanager, Loggingin), RAC Observe (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 p Assword: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];}
Chain-dependent operations.
Dependencies typically occur in network requests, such as when the latter request should be created after the previous request has been completed, 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];}];
The Reactivecocoa can be particularly convenient to handle this logic pattern:
[[[[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."); }];
Independent work in parallel.
The
works in parallel with independent data and then eventually merges them into one result, which is trivial in cocoa and often contains many synchronization codes:
__block Nsarray *databaseobjects;__block Nsarray *filecontents; Nsoperationqueue *backgroundqueue = [[Nsoperationqueue alloc] init]; Nsblockoperation *databaseoperation = [nsblockoperation blockoperationwithblock:^{databaseObjects = [DatabaseClient F Etchobjectsmatchingpredicate:predicate];}]; Nsblockoperation *filesoperation = [nsblockoperation blockoperationwithblock:^{nsmutablearray *filesInProgress = [NSM Utablearray 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 optimized by compounding the use of the signals signal object:
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"); }];
Simplifies the transformation of a collection.
Higher-level sorting functions, such as map (mappings), ( filter filters), ( fold folding)/ reduce (decrease), are severely absent in the foundation; This causes the loop code to be written similar to the following:
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 using a unified declarative Syntax:
RACSequence *results = [[strings.rac_sequence filter:^ BOOL (NSString *str) { return str.length >= 2; }] map:^(NSString *str) { return [str stringByAppendingString:@"foobar"]; }];
Reactivecocoa, the most popular iOS function responsive Programming library (version 2.5), does not have one!