ReactiveCocoa框架学习(二)

前端之家收集整理的这篇文章主要介绍了ReactiveCocoa框架学习(二)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

本文翻译自GitHub上的开源框架ReactiveCocoa的readme,

英文原文链接https://github.com/ReactiveCocoa/ReactiveCocoa.

ReactiveCocoa (RAC)是一个Objective-C的框架,它的灵感来自函数式响应式编程.

如果你已经很熟悉函数式响应式编程编程或者了解ReactiveCocoa的一些基本前提,check outDocumentation文件夹作为框架的概述,这里面有一些关于它怎么工作的深层次的信息.

感谢Rheinfabrik对ReactiveCocoa 3!_开发慷慨地赞助.

什么是ReactiveCocoa?

ReactiveCocoa文档写得很厉害,并且详细地介绍了RAC是什么以及它是怎么工作的?

如果你多学一点,我们推荐下面这些资源:

  1. Introduction

  2. When to use ReactiveCocoa

  3. Framework Overview

  4. Basic Operators

  5. Header documentation

  6. PrevIoUsly answered StackOverflow questionsandGitHub issues

  7. The rest of theDocumentationfolder

  8. Functional Reactive Programming on iOS(eBook)

如果你有任何其他的问题,请随意提交issue,

file an issue.

介绍

ReactiveCocoa的灵感来自函数式响应式编程.Rather than using mutable variables which are replaced and modified in-place,RAC提供signals(表现为RACSignal)来捕捉当前以及将来的值.

通过对signals进行连接,绑定和响应,不需要连续地观察和更新值,软件就能写了.

举个例子,一个text field能够绑定到最新状态,即使它在变,而不需要用额外的代码去更新text field每一秒的状态.它有点像KVO,但它用blocks代替了重写-observeValueForKeyPath:ofObject:change:context:.

Signals也能够呈现异步的操作,有点像futures andpromises.这极大地简化了异步软件,包括了网络处理的代码.

RAC有一个主要的优点,就是提供了一个单一的,统一的方法去处理异步的行为,包括delegate方法,blocks回调,target-action机制,notifications和KVO.

这里有一个简单的例子:

1
2
3
4
5
6
7
8
//Whenself.usernamechanges,logsthenewnametotheconsole.
//
//RACObserve(self,username)createsanewRACSignalthatsendsthecurrent
//valueofself.username,thenthenewvaluewheneveritchanges.
//-subscribeNext:willexecutetheblockwheneverthesignalsendsavalue.
[RACObserve(self,username)subscribeNext:^(NSString*newName){
NSLog(@ "%@" ,newName);
}];

这不像KVO notifications,signals能够连接在一起并且能够同时进行操作:

8
9
10
11
//Onlylogsnamesthatstartswith"j".
//-filterreturnsanewRACSignalthatonlysendsanewvaluewhenitsblock
//returnsYES.
[[RACObserve(self,username)
filter:^(NSString*newName){
@H_502_184@return [newNamehasPrefix:@ "j" ];
}]
subscribeNext:^(NSString*newName){
}];

Signals也能够用来导出状态.而不是observing properties或者设置其他的 properties去反应新的值,RAC通过signals and operations让表示属性变得有可能:

11
12
13
14
//Createsaone-waybindingsothatself.createEnabledwillbe
//truewheneverself.passwordandself.passwordConfirmation
//areequal.
//
//RAC()isamacrothatmakesthebindinglooknicer.
//
//+combineLatest:reduce:takesanarrayofsignals,executestheblockwiththe
//latestvaluefromeachsignalwheneveranyofthemchanges,andreturnsanew
//RACSignalthatsendsthereturnvalueofthatblockasvalues.
RAC(self,createEnabled)=[RACSignal
combineLatest:@[RACObserve(self,password),RACObserve(self,passwordConfirmation)]
reduce:^(NSString*password,NSString*passwordConfirm){
@([passwordConfirmisEqualToString:password]);
}];

Signals不仅仅能够用在KVO,还可以用在很多的地方.比如说,它们也能够展示button presses:

12
//Logsamessagewheneverthebuttonispressed.
//RACCommandcreatessignalstorepresentUIactions.Eachsignalcan
//representabuttonpress,forexample,andhaveadditionalworkassociated
//withit.
//-rac_commandisanadditiontoNSButton.Thebuttonwillsenditselfonthat
//commandwheneverit'spressed.
self.button.rac_command=[[RACCommandalloc]initWithSignalBlock:^(id_){
"buttonwaspressed!" );
[RACSignalempty];
}];

或者异步的网络操作:

14
15
16
17
18
19
//Hooksupa"Login"buttontologinoverthenetwork.
//Thisblockwillberunwheneverthelogincommandisexecuted,starting
//theloginprocess.
self.loginCommand=[[RACCommandalloc]initWithSignalBlock:^(idsender){
//Thehypothetical-logInmethodreturnsasignalthatsendsavaluewhen
//thenetworkrequestfinishes.
[clientlogIn];
}];
//-executionSignalsreturnsasignalthatincludesthesignalsreturnedfrom
//theaboveblock,oneforeachtimethecommandisexecuted.
[self.loginCommand.executionSignalssubscribeNext:^(RACSignal*loginSignal){
//Logamessagewheneverweloginsuccessfully.
[loginSignalsubscribeCompleted:^{
"Loggedinsuccessfully!" );
}];
}];
//Executesthelogincommandwhenthebuttonispressed.
self.loginButton.rac_command=self.loginCommand;

Signals能够展示timers,其他的UI事件,或者其他跟时间改变有关的东西.

对于用signals来进行异步操作,通过连接和改变这些signals能够进行更加复杂的行为.在一组操作完成时,工作能够很简单触发:

13
//Performs2networkoperationsandlogsamessagetotheconsolewhentheyare
//bothcompleted.
//
//+merge:takesanarrayofsignalsandreturnsanewRACSignalthatpasses
//throughthevaluesofallofthesignalsandcompleteswhenallofthe
//signalscomplete.
//
//-subscribeCompleted:willexecutetheblockwhenthesignalcompletes.
[[RACSignal
merge:@[[clientfetchUserRepos],[clientfetchOrgRepos]]]
subscribeCompleted:^{
"They'rebothdone!" );
}];

Signals能够顺序地执行异步操作,而不是嵌套block回调.这个和futures and promises很相似:

19
20
21
22
23
24
25
//Logsintheuser,thenloadsanycachedmessages,thenfetchestheremaining
//messagesfromtheserver.Afterthat'salldone,logsamessagetothe
//console.
//Thehypothetical-logInUsermethodsreturnsasignalthatcompletesafter
//loggingin.
//-flattenMap:willexecuteitsblockwheneverthesignalsendsavalue,and
//returnsanewRACSignalthatmergesallofthesignalsreturnedfromtheblock
//intoasinglesignal.
[[[[client
logInUser]
flattenMap:^(User*user){
//Returnasignalthatloadscachedmessagesfortheuser.
[clientloadCachedMessagesForUser:user];
}]
flattenMap:^(NSArray*messages){
//Returnasignalthatfetchesanyremainingmessages.
[clientfetchMessagesAfterMessage:messages.lastObject];
}]
subscribeNext:^(NSArray*newMessages){
"Newmessages:%@" }completed:^{
"Fetchedallmessages." );
}];

RAC也能够简单地绑定异步操作的结果:

20
//Createsaone-waybindingsothatself.imageView.imagewillbesetastheuser's
//avatarassoonasit'sdownloaded.
//Thehypothetical-fetchUserWithUsername:methodreturnsasignalwhichsends
//theuser.
//-deliverOn:createsnewsignalsthatwilldotheirworkonotherqueues.In
//thisexample,it'susedtomoveworktoabackgroundqueueandthenbacktothemainthread.
//
//-map:callsitsblockwitheachuserthat'sfetchedandreturnsanew
//RACSignalthatsendsvaluesreturnedfromtheblock.
RAC(self.imageView,image)=[[[[client
fetchUserWithUsername:@ "joshaber" ]
deliverOn:[RACSchedulerscheduler]]
map:^(User*user){
//Downloadtheavatar(thisisdoneonabackgroundqueue).
[[NSImagealloc]initWithContentsOfURL:user.avatarURL];
}]
//Nowtheassignmentwillbedoneonthemainthread.
deliverOn:RACScheduler.mainThreadScheduler];

这里仅仅说了RAC能做什么,但很难说清RAC为什么如此强大.虽然通过这个README很难说清RAC,但我尽可能用更少的代码,更少的模版,把更好的代码去表达清楚.

如果想要更多的示例代码,可以check outC-41或者GroceryList,这些都是真正用ReactiveCocoa写的iOS apps.更多的RAC信息可以看一下Documentation文件夹.

什么时候用ReactiveCocoa

乍看上去,ReactiveCocoa是很抽象的,它可能很难理解如何将它应用到具体的问题.

这里有一些RAC常用的地方.

处理异步或者事件驱动数据源

很多Cocoa编程集中在响应user events或者改变application state.这样写代码很快地会变得很复杂,就像一个意大利面,需要处理大量的回调和状态变量的问题.

这个模式表面上看起来不同,像UI回调,网络响应,和KVO notifications,实际上有很多的共同之处。RACSignal统一了这些API,这样他们能够组装在一起然后用相同的方式操作.

举例看一下下面的代码:

25
26
27
28
29
30
31
32
33
34
35
36
37
38
staticvoid*ObservationContext=&ObservationContext;
-(void)viewDidLoad{
[ super viewDidLoad];
[LoginManager.sharedManageraddObserver:selfforKeyPath:@ "loggingIn" options:NSKeyValueObservingOptionInitialcontext:&ObservationContext];
[NSNotificationCenter.defaultCenteraddObserver:selfselector:@selector(loggedOut:)name:UserDidlogoutNotificationobject:LoginManager.sharedManager];
[self.usernameTextFieldaddTarget:selfaction:@selector(updateLogInButton)forControlEvents:UIControlEventEditingChanged];
[self.passwordTextFieldaddTarget:selfaction:@selector(updateLogInButton)forControlEvents:UIControlEventEditingChanged];
[self.logInButtonaddTarget:selfaction:@selector(logInPressed:)forControlEvents:UIControlEventTouchUpInside];
}
-(void)dealloc{
[LoginManager.sharedManagerremoveObserver:selfforKeyPath:@ context:ObservationContext];
[NSNotificationCenter.defaultCenterremoveObserver:self];
}
-(void)updateLogInButton{
BOOLtextFieldsNonEmpty=self.usernameTextField.text.length>0&&self.passwordTextField.text.length>0;
BOOLreadyToLogIn=!LoginManager.sharedManager.isLoggingIn&&!self.loggedIn;
self.logInButton.enabled=textFieldsNonEmpty&&readyToLogIn;
}
-(IBAction)logInPressed:(UIButton*)sender{
[[LoginManagersharedManager]
logInWithUsername:self.usernameTextField.text
password:self.passwordTextField.text
success:^{
self.loggedIn=YES;
}failure:^(NSError*error){
[selfpresentError:error];
}];
}
-(void)loggedOut:(NSNotification*)notification{
self.loggedIn=NO;
}
-(void)observeValueForKeyPath:(NSString*)keyPathofObject:(id)objectchange:(NSDictionary*)changecontext:(void*)context{
if (context==ObservationContext){
[selfupdateLogInButton];
} else {
observeValueForKeyPath:keyPathofObject:objectchange:changecontext:context];
}
}

… 用RAC表达的话就像下面这样:

29
-(void)viewDidLoad{
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){
@(username.length>0&&password.length>0&&!loggingIn.boolValue&&!loggedIn.boolValue);
}];
[[self.logInButtonrac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(UIButton*sender){
@strongify(self);
RACSignal*loginSignal=[LoginManager.sharedManager
logInWithUsername:self.usernameTextField.text
password:self.passwordTextField.text];
[loginSignalsubscribeError:^(NSError*error){
@strongify(self);
[selfpresentError:error];
}completed:^{
@strongify(self);
self.loggedIn=YES;
}];
}];
rac_addObserverForName:UserDidlogoutNotificationobject:nil]
mapReplace:@NO];
}

连接依赖的操作

依赖经常用在网络请求,当下一个对服务器网络请求需要构建在前一个完成时,可以看一下下面的代码:

13
[clientlogInWithSuccess:^{
[clientloadCachedMessagesWithSuccess:^(NSArray*messages){
[clientfetchMessagesAfterMessage:messages.lastObjectsuccess:^(NSArray*nextMessages){
);
}failure:^(NSError*error){
[selfpresentError:error];
}];
}failure:^(NSError*error){
[selfpresentError:error];
}];
}failure:^(NSError*error){
[selfpresentError:error];
}];

ReactiveCocoa 则让这种模式特别简单:

[[[[clientlogIn]
then:^{
[clientloadCachedMessages];
}]
flattenMap:^(NSArray*messages){
[clientfetchMessagesAfterMessage:messages.lastObject];
}]
subscribeError:^(NSError*error){
}completed:^{
);
}];

并行地独立地工作

与独立的数据集并行,然后将它们合并成一个最终的结果在Cocoa中是相当不简单的,并且还经常涉及大量的同步:

22
__blockNSArray*databaSEObjects;
__blockNSArray*fileContents;
NSOperationQueue*backgroundQueue=[[NSOperationQueuealloc]init];
NSBlockOperation*databaSEOperation=[NSBlockOperationblockOperationWithBlock:^{
databaSEObjects=[databaseClientfetchObjectsMatchingPredicate:predicate];
}];
NSBlockOperation*filesOperation=[NSBlockOperationblockOperationWithBlock:^{
NSMutableArray*filesInProgress=[NSMutableArrayarray];
for (NSString*path in files){
[filesInProgressaddObject:[NSDatadataWithContentsOfFile:path]];
}
fileContents=[filesInProgresscopy];
}];
NSBlockOperation*finishOperation=[NSBlockOperationblockOperationWithBlock:^{
[selffinishProcessingDatabaSEObjects:databaSEObjectsfileContents:fileContents];
"Doneprocessing" );
}];
[finishOperationaddDependency:databaSEOperation];
[finishOperationaddDependency:filesOperation];
[backgroundQueueaddOperation:databaSEOperation];
[backgroundQueueaddOperation:filesOperation];
[backgroundQueueaddOperation:finishOperation];

上面的代码能够简单地用合成signals来清理和优化:

20
RACSignal*databaseSignal=[[databaseClient
fetchObjectsMatchingPredicate:predicate]
subscribeOn:[RACSchedulerscheduler]];
RACSignal*fileSignal=[RACSignalstartEagerlyWithScheduler:[RACSchedulerscheduler]block:^(idsubscriber){
NSMutableArray*filesInProgress=[NSMutableArrayarray];
files){
[filesInProgressaddObject:[NSDatadataWithContentsOfFile:path]];
}
[subscribersendNext:[filesInProgresscopy]];
[subscribersendCompleted];
}];
[[RACSignal
combineLatest:@[databaseSignal,fileSignal]
reduce:^id(NSArray*databaSEObjects,NSArray*fileContents){
nil;
}]
subscribeCompleted:^{
);
}];

简化集合转换

像map,filter,fold/reduce 这些高级功能在Foundation中是极度缺少的m导致了一些像下面这样循环集中的代码:

NSMutableArray*results=[NSMutableArrayarray];
(NSString*str strings){
(str.length<2){
continue ;
}
NSString*newString=[strstringByAppendingString:@ "foobar" ];
[resultsaddObject:newString];
}

RACSequence能够允许Cocoa集合用统一的方式操作:

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

系统要求

ReactiveCocoa 要求 OS X 10.8+ 以及 iOS 8.0+.

引入 ReactiveCocoa

增加 RAC 到你的应用中:

1. 增加 ReactiveCocoa 仓库 作为你应用仓库的一个子模块.

2. 从ReactiveCocoa文件夹中运行 script/bootstrap .

3. 拖拽 ReactiveCocoa.xcodeproj 到你应用的 Xcode project 或者 workspace中.

4. 在你应用target的"Build Phases"的选项卡,增加 RAC到 "Link Binary With Libraries"

On iOS,增加 libReactiveCocoa-iOS.a.

On OS X,增加 ReactiveCocoa.framework.

RAC 必须选择"Copy Frameworks" . 假如你没有的话,需要选择"Copy Files"和"Frameworks" .

5. 增加 "$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/include"

$(inherited)到 "Header Search Paths" (这需要archive builds,但也没什么影响).

6. For iOS targets,增加 -ObjC 到 "Other Linker Flags" .

7. 假如你增加 RAC到一个project (不是一个workspace),你需要适当的添加RAC target到你应用的"Target Dependencies".

假如你喜欢用CocoaPods,这里有一些慷慨地第三方贡献ReactiveCocoa podspecs.

想看一个用了RAC的工程,check out 独立开发

假如你的工作用RAC是隔离的而不是将其集成到另一个项目,你会想打开ReactiveCocoa.xcworkspace 而不是.xcodeproj.

更多信息

ReactiveCocoa灵感来自.NET的ReactiveExtensions(Rx).Rx的一些原则也能够很好的用在RAC.这里有些好的Rx资源:

RAC和Rx灵感都是来自函数式响应式编程.这里有些关于FRP(functional reactive programming)相关的资源:

原文链接:https://www.f2er.com/react/306463.html

猜你在找的React相关文章