ReactiveCocoa 响应式函数编程
简介
ReactiveCocoa(简称为RAC),RAC具有函数响应式编程特性,由Matt Diephouse开源的一个应用于iOS和OS X的新框架。
为什么使用RAC?
因为RAC具有高聚合低耦合的思想所以使用RAC会让代码更简洁,逻辑更清晰。
如何在项目中添加RAC?
纯 swift 项目,继续使用 ReactiveCocoa 。但是 RAC 依赖于 ReactiveSwift ,等于你引入了两个库。
纯 OC 项目,需要使用 ReactiveObjC 。这个库里面包含原来 RAC 2的全部代码。
项目是 swift 和 OC 混编,需要同时引用 ReactiveCocoa 和 ReactiveObjCBridge 。但是 ReactiveObjCBridge 依赖于 ReactiveObjC ,所以你就等于引入了 4 个库。
工作原理
常见类解释
1. Stream - 信号流值 - RACStream类
表示一个基本单元可以为任意值,其值会随着事件的变化而变化,可以在其上进行一些复杂的操作运算(map,filter,skip,take等.)此类不会被经常使用, 多情况下表现为signal和sequences(RACSignal 和RACSequence继承于RACStream类)
[[RACObserve(self, reactiveString)
filter:^BOOL(NSString *value) {
return [value hasPrefix:@"A"];
}]
subscribeNext:^(NSString *value) {
NSLog(@"%@",value);
}];
2. Signals - 信号 - RACSignal类
什么是Signals?
有订阅者监听时信号才会发信息, Signals会向那个订阅者发送0或多个载有数值的”next”事件,后面跟着一个”complete”事件或一个”error”事件。
Signals会发送三种不同信号给Subscriber
- next:是可以为nil的新值, RACStream方法只能在这个值上进行操作运算。
- error:表示在Signals完成之前发生了错误,值不会在RACStream类中存储。
- completed:表示Signals成功的完成,值不会在RACStream类中存储。
__block int aNumber = ;
// Signal that will have the side effect of incrementing `aNumber` block
// variable for each subscription before sending it.
RACSignal *aSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
aNumber++;
[subscriber sendNext:@(aNumber)];
[subscriber sendCompleted];
return nil;
}]; // This will print "subscriber one: 1"
[aSignal subscribeNext:^(id x) {
NSLog(@"subscriber one: %@", x);
}]; // This will print "subscriber two: 2"
[aSignal subscribeNext:^(id x) {
NSLog(@"subscriber two: %@", x);
}];
如果需要对信号进行过滤,转换,分解和合并那些值的话则不同的订阅者可能需要使用信号通过不同方式发送的值。
RACSignal *usernameIsValidSignal = RACObserve(self.viewModel, usernameValid);
RAC(self.Button, alpha) = [usernameIsValidSignal
map:^(NSNumber *valid) {
return valid. boolValue?@:@0.5;
}];
3. Subscriber - 订阅者 - RACSubscriber协议
表示能够接收信号的对象,订阅信号才会激活信号,实现RACSubscriber协议的对象都可以为订阅者。
可以通过- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock 方法创建Subscriber。
RACSignal *repeatSignal = [[RACSignal interval: onScheduler:[RACScheduler mainThreadScheduler]] repeat];
[repeatSignal subscribeNext: ^(NSDate* time){
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"HH:mm:ss"];
NSLog(@"%@",[formatter stringFromDate:time]);
}];
4. Subjects - 手动控制信号 - RACSubject - 热信号
表示可以手动控制信号,
处理流程:创建信号-订阅信号-发送信号
// 1.创建信号
RACSubject *subject = [RACSubject subject];
// 2.订阅信号 First
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用.
NSLog(@"FirstSubscribeNext%@",x);
}];
// 2.订阅信号 Second
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用.
NSLog(@"SecondSubscribeNext%@",x);
}];
// 3.发送信号
[subject sendNext:@""];
[subject sendNext:@""];
使用 RACSubject 替代代理的方法:
// 需求:
// 1.给当前控制器添加一个按钮,modal到另一个控制器界面
// 2.另一个控制器view中有个按钮,点击按钮,通知当前控制器 //步骤一:在第二个控制器.h,添加一个RACSubject代替代理。
@interface TwoViewController : UIViewController @property (nonatomic, strong) RACSubject *delegateSignal; @end //步骤二:监听第二个控制器按钮点击
@implementation TwoViewController
- (IBAction)notice:(id)sender {
// 通知第一个控制器,告诉它,按钮被点了 // 通知代理
// 判断代理信号是否有值
if (self.delegateSignal) {
// 有值,才需要通知
[self.delegateSignal sendNext:nil];
}
} @end //步骤三:在第一个控制器中,监听跳转按钮,给第二个控制器的代理信号赋值,并且监听.
@implementation OneViewController
- (IBAction)btnClick:(id)sender { // 创建第二个控制器
TwoViewController *twoVc = [[TwoViewController alloc] init]; // 设置代理信号
twoVc.delegateSignal = [RACSubject subject]; // 订阅代理信号
[twoVc.delegateSignal subscribeNext:^(id x) { NSLog(@"点击了通知按钮");
}]; // 跳转到第二个控制器
[self presentViewController:twoVc animated:YES completion:nil]; } @end
也是RAC代码与非RAC代码的Bridge 所以非常有用,此类继承于RACSignal类。
5. ReplaySubject - 手动回放控制信号 - RACReplaySubject - 热信号
表示可以手动控制信号,底层实现和RACSubject不一样,它会先把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock然后调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock。
可以有以下两种处理流程:
处理流程 1:创建信号-订阅信号-发送信号(和Subjects一样)
处理流程 2:创建信号-发送信号-订阅信号
// 1.创建信号
RACReplaySubject *replaySubject = [RACReplaySubject subject];
// 2.发送信号
[replaySubject sendNext:@""];
[replaySubject sendNext:@""];
// 3.订阅信号 First
[replaySubject subscribeNext:^(id x) {
NSLog(@"FirstSubscribeNext%@",x);
}];
// 3.订阅信号 Second
[replaySubject subscribeNext:^(id x) {
NSLog(@"SecondSubscribeNext%@",x);
}];
6. Command- 命令信号 - RACCommand
表示订阅响应Action信号,通常由UI来出发,比如一个Button当控件被触发时会被自动禁用掉。
UIButton *reactiveBtn = [[UIButton alloc] init];
[reactiveBtn setTitle:@"点我" forState:UIControlStateNormal];
reactiveBtn.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(UIButton *input) {
NSLog(@"点击了我:%@",input.currentTitle);
//返回一个空的信号量
return [RACSignal empty];
}];
7. Sequences- 集合 - RACSequence
表示一个不可变的序列值且不能包含空值,使用-rac_sequence.signal来获取Signal。
RACSignal *signal = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
// Outputs
[signal subscribeNext:^(NSString *x) {
NSLog(@"%@", x);
}];
8. Disposables- 清理订阅 - RACDisposable
表示用于取消信号的订阅,当一个signal被subscriber后,当执行sendComplete或sendError时subscriber会被移除,或者手动调用[disposable dispose]进行移除操作。
当subscriber被移除后,所有该subscriber相关的工作都会被停止或取消,如http请求,资源也会被释放。
9. Scheduler- 计划 - RACScheduler
表示一个信号队列,是信号执行任务时所在的队列或者信号执行完成后将结果放到队列里执行,它支持取消对列里的执行并总是串行执行。
RAC常用宏
RACObserve(TARGET, KEYPATH)
表现形式:RACObserve(self, stringProperty)
KVO的简化版本 相当于对TARGET中KEYPATH的值设置监听,返回一个RACSignal
RAC(TARGET, ...)
表现形式:RAC(self, stringProperty) = TextField.rac_textSignal
第一个是需要设置属性值的对象,第二个是属性名
RAC宏允许直接把信号的输出应用到对象的属性上
每次信号产生一个next事件,传递过来的值都会应用到该属性上
RACChannelTo(TARGET, ...)
RACChannelTo 用于双向绑定
RACChannelTo(self, stringProperty)=RACChannelTo(self.label, text) ;
应用说明:
//RACObserve 应用
[self.textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
self.textString = x;
}]; RACSignal *textFieldSignal = RACObserve(self, self.textString);
[textFieldSignal subscribeNext:^(id _Nullable x) {
NSLog(@"textField: %@", x);
}]; //RAC 应用
RACSignal *signal = RAC(self, self.textString) = self.textField.rac_textSignal;
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"text: %@", x);
}];
RAC结构图
RAC基础使用
创建一个TextField名为usernameTextField 设置监听TextField
[self.usernameTextField.rac_textSignal
subscribeNext:^(id x){
NSLog(@"%@", x);
}];
filter
:如果想添加一个条件 只输出x的长度大于3的,可以使用filter操作来实现这个目的
//filter应用
[self.usernameTextField.rac_textSignal
filter:^BOOL(NSString* text){
return text.length > ;
}];
map
:把text转换成length进行输出,使用map可以对信号进行转换,一个源信号转换成另外一个新的信号输出[[[self.usernameTextField.rac_textSignal
map:^id(NSString*text){
return @(text.length);
}]
filter:^BOOL(NSNumber*length){
return[length integerValue] > ;
}]
subscribeNext:^(id x){
NSLog(@"%@", x);
}];
信号可聚合也可以分割
聚合: 多个信号可以聚合成一个新的信号,这个可以是任何类型的信号
RACSignal *signal =
[RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal]
reduce:^id(NSNumber*usernameValid, NSNumber *passwordValid){
return @([usernameValid boolValue]&&[passwordValid boolValue]);
}];
分割:一个信号可以有很多subscriber,也就是作为很多后续步骤的源
RACSignal *signal = self.usernameTextField.rac_textSignal;
[signal subscribeNext:^(id x) {
NSLog(@"");
}];
[signal subscribeNext:^(id x) {
NSLog(@"");
}];
}
RAC设置Button的ControlEvents
[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
NSLog(@"button click");
}];
登陆功能举例说明
需要实现登陆功能需要点击登陆button
- (RACSignal *)signInSignal {
return [RACSignal createSignal:^RACDisposable *(id subscriber){
[self.signInService
signInWithUsername:self.usernameTextField.text
password:self.passwordTextField.text
complete:^(BOOL success){
[subscriber sendNext:@(success)];
[subscriber sendCompleted];
}];
return nil;
}];
}
[[[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
doNext:^(id x){
self.signInButton.enabled =NO;
self.signInFailureText.hidden =YES;
}] flattenMap:^id(id x){
return[self signInSignal];
}] subscribeNext:^(NSNumber*signedIn){
self.signInButton.enabled =YES;
BOOL success =[signedIn boolValue];
self.signInFailureText.hidden = success;
if(success){
[self performSegueWithIdentifier:@"signInSuccess" sender:self];
}
}];
flattenMap
:[self signInSignal]返回的也是signal,所以是信号中的信号,使用这个操作把按钮点击事件转换为登录信号,同时还从内部信号发送事件到外部信号。
doNext
:为一个附加操作,在一个next事件发生时执行的逻辑,而该逻辑并不改变事件本身。
RAC高级使用
error 和 completed,节流,线程,延伸,其他
内存管理
ReactiveCocoa设计的一个目标就是支持匿名生成管道这种编程风格。到目前为止,在你所写的所有响应式代码中,这应该是很直观的。
为了支持这种模型,ReactiveCocoa自己持有全局的所有信号。如果一个signal有一个或多个订阅者,那这个signal就是活跃的。如果所有的订阅者都被移除了,那这个信号就能被销毁了。
如何取消订阅一个signal?
在一个completed或者error事件之后,订阅会自动移除。你还可以通过RACDisposable 手动移除订阅。
RACSignal的订阅方法都会返回一个RACDisposable实例,它能让你通过dispose方法手动移除订阅。这个方法并不常用到,但是还是有必要知道可以这样做。
RACSignal *backgroundColorSignal =
[self.searchText.rac_textSignal
map:^id(NSString *text) {
return [self isValidSearchText:text] ?
[UIColor whiteColor] : [UIColor yellowColor];
}]; RACDisposable *subscription =
[backgroundColorSignal
subscribeNext:^(UIColor *color) {
self.searchText.backgroundColor = color;
}]; [subscription dispose];
避免循环引用
在ReactiveCocoa中提供了避免循环引用的方法
@weakify宏让你创建一个弱引用的影子对象(如果你需要多个弱引用,你可以传入多个变量),
@strongify让你创建一个对之前传入@weakify对象的强引用。
@weakify(self)
[[self.searchText.rac_textSignal
map:^id(NSString *text) {
return [self isValidSearchText:text] ?
[UIColor whiteColor] : [UIColor yellowColor];
}]
subscribeNext:^(UIColor *color) {
@strongify(self)
self.searchText.backgroundColor = color;
}];
signal能发送3种不同类型的事件
Next
Completed
Error
当应用获取访问社交媒体账号的权限时,用户会看见一个弹框。这是一个异步操作,因此把这封装进一个signal是很好的选择
-(RACSignal *)requestAccessToTwitterSignal {
// 1 - define an error
NSError *accessError = [NSError errorWithDomain:RWTwitterInstantDomain
code:RWTwitterInstantErrorAccessDenied
userInfo:nil]; // 2 - create the signal
@weakify(self)
return [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 3 - request access to twitter
@strongify(self)
[self.accountStore requestAccessToAccountsWithType:self.twitterAccountType
options:nil
completion:^(BOOL granted, NSError *error) {
// 4 - handle the response
if (!granted) {
[subscriber sendError:accessError];
} else {
[subscriber sendNext:nil];
[subscriber sendCompleted];
}
}];
return nil;
}];
}
then:then方法会等待completed事件的发送,然后再订阅由then block返回的signal。这样就高效地把控制权从一个signal传递给下一个。
[[[[self requestAccessToTwitterSignal]
then:^RACSignal *{
@strongify(self)
return self.searchText.rac_textSignal;
}]
filter:^BOOL(NSString *text) {
@strongify(self)
return [self isValidSearchText:text];
}]
subscribeNext:^(id x) {
NSLog(@"%@", x);
} error:^(NSError *error) {
NSLog(@"An error occurred: %@", error);
}];
实时搜索内容方法
- 创建请求链接方法
-(SLRequest *)requestforTwitterSearchWithText:(NSString *)text {
NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/1.1/search/tweets.json"];
NSDictionary *params = @{@"q" : text};
SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodGET
URL:url
parameters:params];
return request;
}
- 创建请求signal
-(RACSignal *)signalForSearchWithText:(NSString *)text {
// 1 - define the errors
NSError *noAccountsError = [NSError errorWithDomain:RWTwitterInstantDomain
code:RWTwitterInstantErrorNoTwitterAccounts
userInfo:nil];
NSError *invalidResponseError = [NSError errorWithDomain:RWTwitterInstantDomain
code:RWTwitterInstantErrorInvalidResponse
userInfo:nil]; // 2 - create the signal block
@weakify(self)
return [RACSignal createSignal:^RACDisposable *(id subscriber) {
@strongify(self); // 3 - create the request
SLRequest *request = [self requestforTwitterSearchWithText:text]; // 4 - supply a twitter account
NSArray *twitterAccounts = [self.accountStore accountsWithAccountType:self.twitterAccountType];
if (twitterAccounts.count == ) {
[subscriber sendError:noAccountsError];
} else {
[request setAccount:[twitterAccounts lastObject]]; // 5 - perform the request
[request performRequestWithHandler: ^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error) {
if (urlResponse.statusCode == ) { // 6 - on success, parse the response
NSDictionary *timelineData = [NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingAllowFragments
error:nil];
[subscriber sendNext:timelineData];
[subscriber sendCompleted];
} else {
// 7 - send an error on failure
[subscriber sendError:invalidResponseError];
}
}];
}
return nil;
}];
}
- 使用flattenMap来把每个next事件映射到一个新的signal
[[[[[self requestAccessToTwitterSignal]
then:^RACSignal *{
@strongify(self)
return self.searchText.rac_textSignal;
}]
filter:^BOOL(NSString *text) {
@strongify(self)
return [self isValidSearchText:text];
}]
flattenMap:^RACStream *(NSString *text) {
@strongify(self)
return [self signalForSearchWithText:text];
}]
subscribeNext:^(id x) {
NSLog(@"%@", x);
} error:^(NSError *error) {
NSLog(@"An error occurred: %@", error);
}];
线程
在subscribeNext:error:中的数据没有在主线程(Thread 1)中执行,更新UI只能在主线程中执行,所以更新UI需要转到主线程中执行。
要怎么更新UI呢?
通常的做法是使用操作队列但是ReactiveCocoa有更简单的解决办法,在flattenMap:之后添加一个deliverOn:操作就可以转到主线程上了。
注:如果你看一下RACScheduler类,就能发现还有很多选项,比如不同的线程优先级,或者在管道中添加延迟。
[[[[[[self requestAccessToTwitterSignal]
then:^RACSignal *{
@strongify(self)
return self.searchText.rac_textSignal;
}]
filter:^BOOL(NSString *text) {
@strongify(self)
return [self isValidSearchText:text];
}]
flattenMap:^RACStream *(NSString *text) {
@strongify(self)
return [self signalForSearchWithText:text];
}]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(id x) {
NSLog(@"%@", x);
} error:^(NSError *error) {
NSLog(@"An error occurred: %@", error);
}];
异步加载图片
-(RACSignal *)signalForLoadingImage:(NSString *)imageUrl {
RACScheduler *scheduler = [RACScheduler
schedulerWithPriority:RACSchedulerPriorityBackground]; return [[RACSignal createSignal:^RACDisposable *(id subscriber) {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
UIImage *image = [UIImage imageWithData:data];
[subscriber sendNext:image];
[subscriber sendCompleted];
return nil;
}] subscribeOn:scheduler];
}
首先获取一个后台scheduler,来让signal不在主线程执行。然后,创建一个signal来下载图片数据,当有订阅者时创建一个UIImage。最后是subscribeOn:来确保signal在指定的scheduler上执行。
-(UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"TableViewCell"];
[[[[self signalForLoadingImage:tweet.profileImageUrl]
takeUntil:cell.rac_prepareForReuseSignal]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(UIImage *image) {
cell.twitterAvatarView.image = image;
}];
return cell;
}
cell是重用的,可能有脏数据,所以上面的代码首先重置图片。然后创建signal来获取图片数据。你之前也遇到过deliverOn:这一步,它会把next事件发送到主线程,这样subscribeNext:block就能安全执行了。
cell.rac_prepareForReuseSignal:Cell复用时的清理。
takeUntil:当给定的signal完成前一直取值
节流
每次输入一个字,搜索都会马上执行。如果你输入很快(或者只是一直按着删除键),这可能会造成应用在一秒内执行好几次搜索,这很不理想。
更好的解决方法是,当搜索文本在短时间内,比如说500毫秒,不再变化时,再执行搜索。
在filter之后添加一个throttle步骤:
[[[[[[[self requestAccessToTwitterSignal]
then:^RACSignal *{
@strongify(self)
return self.searchText.rac_textSignal;
}]
filter:^BOOL(NSString *text) {
@strongify(self)
return [self isValidSearchText:text];
}]
throttle:0.5]
flattenMap:^RACStream *(NSString *text) {
@strongify(self)
return [self signalForSearchWithText:text];
}]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(NSDictionary *jsonSearchResult) {
NSArray *statuses = jsonSearchResult[@"statuses"];
NSArray *tweets = [statuses linq_select:^id(id tweet) {
return [RWTweet tweetWithStatus:tweet];
}];
[self.resultsViewController displayTweets:tweets];
} error:^(NSError *error) {
NSLog(@"An error occurred: %@", error);
}];
throttle:只有当前一个next事件在指定的时间段内没有被接收到后,throttle操作才会发送next事件。
代替代理
如果想在其他地方监听到tableView的代理信息则需要设置如下方法
[[tableView rac_signalForSelector:@selector(tableView:didSelectRowAtIndexPath:) fromProtocol:@protocol(UITableViewDelegate) ] subscribeNext:^(RACTuple * x) {
NSLog(@"点击了");
}];
rac_signalForSelector: fromProtocol: 要先绑定在设置代理
疑点问题总结:
A、什么是冷信号与热信号
冷热信号的概念源于.NET框架Reactive Extensions(RX)中的Hot Observable和Cold Observable,两者的区别是:
1、
Hot Observable是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;
Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。
2、
Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息;
Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。
举例说明冷信号:
__block int aNumber = 0;
//1. 创建信号,等待信号发送,只有订阅了才会调用block函数内的发送(sendNext)
RACSignal *aSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
//3. 订阅完成后,会执行block进行发送此消息
aNumber++;
[subscriber sendNext:@(aNumber)];
[subscriber sendCompleted];
return nil;
}];
// 2. 添加订阅,等到订阅消息
[aSignal subscribeNext:^(id x) {
NSLog(@"subscriber one: %@", x);
}];
说明:处理顺序:1. 创建信号,等待信号发送 -> 2. 添加订阅,等到订阅消息 -> 3. 订阅完成后,会执行block进行发送此消息
B、RAC中的RACSiganl类中几乎都是冷信号,但是也有特例,RAC中提供了两个热信号的类:RACSubject和RACReplaySubject
RACSubject和RACReplaySubject区别:
RACSubject
(1)这个类会保存所有的订阅者,一旦被订阅,就会保存订阅者,等待有值发出的时候,就会告诉所有的订阅者;
(2)处理流程:创建信号-订阅信号-发送信号
RACReplaySubject
(1)是继承RACSubject的所以和RACSubject一样会保存订阅者,然后比父类不同的是它还会保存所有发送过的值,
供有新订阅者订阅的时候发送它漏过的值
(2)处理流程 a:创建信号-订阅信号-发送信号(和Subjects一样)
处理流程 b:创建信号-发送信号-订阅信号
ReactiveCocoa 响应式函数编程的更多相关文章
- IOS响应式编程框架ReactiveCocoa(RAC)使用示例
ReactiveCocoa是响应式编程(FRP)在iOS中的一个实现框架,它的开源地址为:https://github.com/ReactiveCocoa/ReactiveCocoa# :在网上看了几 ...
- iOS响应式编程:ReactiveCocoa vs RxSwift 选谁好
转载: iOS响应式编程:ReactiveCocoa vs RxSwift 选谁好 内容来自stack overflow的一个回答:ReactiveCocoa vs RxSwift – pros an ...
- IOS响应式编程框架ReactiveCocoa(RAC)使用示例-备
ReactiveCocoa是响应式编程(FRP)在IOS中的一个实现框架,它的开源地址为:https://github.com/ReactiveCocoa/ReactiveCocoa# :在网上看了几 ...
- 使用ReactiveCocoa实现iOS平台响应式编程
使用ReactiveCocoa实现iOS平台响应式编程 ReactiveCocoa和响应式编程 在说ReactiveCocoa之前,先要介绍一下FRP(Functional Reactive Prog ...
- [转]使用ReactiveCocoa实现iOS平台响应式编程
原文:http://www.itiger.me/?p=38 使用ReactiveCocoa实现iOS平台响应式编程 ReactiveCocoa和响应式编程 在说ReactiveCocoa之前,先要介绍 ...
- [iOS] 响应式编程开发-ReactiveCocoa(一)
什么是响应式编程 响应式编程是一种面向数据流和变化传播的编程范式.这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播. 例如,在命令式编程环境中 ...
- 函数响应式编程(FRP)框架--ReactiveCocoa
由于工作原因,有段时间没更新博客了,甚是抱歉,只是,从今天開始我又活跃起来了,哈哈,于是决定每周更新一博.大家互相学习.交流. 今天呢.讨论一下关于ReactiveCocoa,这个採用函数响应式编程( ...
- ReactiveCocoa,最受欢迎的iOS函数响应式编程库(2.5版),没有之一!
简介 项目主页: ReactiveCocoa 实例下载: https://github.com/ios122/ios122 简评: 最受欢迎,最有价值的iOS响应式编程库,没有之一!iOS MVVM模 ...
- 函数响应式编程(FRP)—基础概念篇
原文出处:http://ios.jobbole.com/86815/. 一函数响应式编程 说到函数响应式编程,就不得不提到函数式编程,他们俩有什么关系呢?今天我们就详细的解析一下他们的关系. 现在下面 ...
随机推荐
- java+poi实现word转html显示
直入正题,需求为页面预览word文档,用的是poi3.8,以下代码支持表格.图片,不支持分页,只支持doc,不支持docx: 1.导jar包 2.java文件 /** * */ import java ...
- Microsoft Speech SDK开发包 使用
下载开发包.我们首先从微软的官网上面下载开发包,下载地址如下: http://www.microsoft.com/en-us/download/details.aspx?id=10121我们主要下载三 ...
- bzoj4259
fft 搞一个生成函数 对于每位A(j)=Σi=1->m (a[i]-b[i+j])^2*a[i]*b[i+j] 如果A(j)=0说明这位匹配 如果这位是*那么a[i]=0否则等于字母-'a'+ ...
- Robot FrameWork基础学习(三)
一.关键字(Keyword)根据架构的区分可分为以下三层结构: 底层关键字.公共层关键字.特性关键字. 底层关键字一般与最底层的代码在关系,为上层公共关键字和特性关键字提供接口. 公共层关键字:一般是 ...
- B - Alyona and Mex
Description Someone gave Alyona an array containing n positive integers a1, a2, ..., an. In one oper ...
- [CVE-2014-3704]Drupal 7.31 SQL注入漏洞分析与复现
记录下自己的复现思路 漏洞影响: Drupal 7.31 Drupal是一个开源内容管理平台,为数百万个网站和应用程序提供支持. 0x01漏洞复现 复现环境: 1) Apache2.4 2) Php ...
- CSS3 制作魔方 - 玩转魔方
在上一篇<CSS3 制作魔方 - 形成魔方>中介绍了一个完整魔方的绘制实现,本文将介绍魔方的玩转,支持上下左右每一层独立地旋转.先来一睹玩转的风采. 1.一个问题 由于魔方格的位置与转动的 ...
- Mecanim Control
http://www.ufe3d.com/doku.php/mecanimcontrol Mecanim Control Your ultimate solution for Mecanim base ...
- GPU渲染管线与shader
1 几何阶段(顶点shader处理这部分) 模型坐标空间-世界坐标空间-观察坐标空间-屏幕坐标空间 其中从观察空间 到 屏幕空间需要经过3步(CVV单位立方体,规范立方体) a用透视变换矩阵把顶点从视 ...
- 1.python真的是万恶之源么?(初识python)
python真的是万恶之源么? 计算机基础及puthon了解 1.计算机基础知识 cpu : 相当于人类大脑,运算和处理问题 内存 : 临时存储数据,单点就消失,4G,8G,16G,32G 硬盘 : ...