iOS开发基础111-RAC
<p>ReactiveCocoa(RAC)是一个基于函数响应式编程(FRP)的框架,广泛用于iOS开发中。其核心思想是通过流和信号(signal)来处理多变、复杂的事件。以下是ReactiveCocoa常见的一些用法场景,并深入解析其原理。</p><h3 id="1-响应用户输入">1. 响应用户输入</h3>
<h3 id="场景表单验证">场景:表单验证</h3>
<h4 id="示例代码">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
RACSignal *validUsernameSignal = [self.usernameTextField.rac_textSignal
map:^id _Nullable(NSString * _Nullable username) {
return @(username.length > 3);
}];
RACSignal *validPasswordSignal = [self.passwordTextField.rac_textSignal
map:^id _Nullable(NSString * _Nullable password) {
return @(password.length > 3);
}];
RACSignal *formValidSignal =
reduce:^id (NSNumber *usernameValid, NSNumber *passwordValid){
return @( && );
}];
[formValidSignal subscribeNext:^(NSNumber *formValid) {
self.loginButton.enabled = ;
}];
</code></pre>
<h4 id="原理解析">原理解析:</h4>
<ol>
<li><code>rac_textSignal</code>将UITextField的文字变化转化为信号。</li>
<li><code>map:</code>操作符将输入的字符串转化为有效性的布尔值(长度大于3)。</li>
<li><code>combineLatest:reduce:</code>将多个信号结合成一个新的信号,并通过<code>reduce</code>块生成最终结果。</li>
<li><code>subscribeNext:</code>订阅信号,且在信号值变化时对登录按钮进行启用/禁用。</li>
</ol>
<h3 id="2-数据绑定">2. 数据绑定</h3>
<h3 id="场景双向绑定viewmodel和view">场景:双向绑定ViewModel和View</h3>
<h4 id="示例代码-1">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
RAC(self.viewModel, username) = self.usernameTextField.rac_textSignal;
RAC(self.usernameTextField, text) = RACObserve(self.viewModel, username);
</code></pre>
<h4 id="原理解析-1">原理解析:</h4>
<ol>
<li><code>RAC(target, keyPath)</code>将信号绑定到ViewModel的属性。</li>
<li><code>RACObserve</code>创建一个观察信号,当<code>viewModel.username</code>变化时,更新UITextField的text。</li>
<li>双向绑定确保View和ViewModel的属性同步变化。</li>
</ol>
<h3 id="3-处理异步操作">3. 处理异步操作</h3>
<h3 id="场景网络请求">场景:网络请求</h3>
<h4 id="示例代码-2">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
- (RACSignal *)fetchDataFromURL:(NSURL *)url {
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>_Nonnull subscriber) {
NSURLSessionDataTask *task = [ dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
;
} else {
;
;
}
}];
;
return [RACDisposable disposableWithBlock:^{
;
}];
}];
}
[ subscribeNext:^(NSData *data) {
// Process data
} error:^(NSError *error) {
// Handle error
}];
</code></pre>
<h4 id="原理解析-2">原理解析:</h4>
<ol>
<li><code>createSignal:</code>创建一个信号并手动控制其数据(如下一步、完成、错误)流的发送。</li>
<li>在信号内部,执行异步操作(例如网络请求),并在操作完成时发送事件(数据、完成、错误)。</li>
<li><code>subscribeNext:error:</code>对信号的成功和失败情况进行订阅和处理。</li>
</ol>
<h3 id="4-信号组合和变换">4. 信号组合和变换</h3>
<h3 id="场景信号合并和过滤">场景:信号合并和过滤</h3>
<h4 id="示例代码-3">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
// 合并信号
RACSignal *mergedSignal = ];
[mergedSignal subscribeNext:^(id_Nullable x) {
NSLog(@"Received: %@", x);
}];
// 过滤信号
RACSignal *filteredSignal = [self.inputSignal filter:^BOOL(id_Nullable value) {
return > 3;
}];
[filteredSignal subscribeNext:^(id_Nullable x) {
NSLog(@"Filtered value: %@", x);
}];
</code></pre>
<h4 id="原理解析-3">原理解析:</h4>
<ol>
<li><code>merge:</code>将多个信号合并成一个信号,同时订阅多个信号的所有事件。</li>
<li><code>filter:</code>只允许满足条件的事件通过,其他事件将被过滤掉。</li>
</ol>
<h3 id="5-基于时间的操作">5. 基于时间的操作</h3>
<h3 id="场景延迟定时器节流">场景:延迟、定时器、节流</h3>
<h4 id="示例代码-4">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
// 延迟操作
[ delay:2.0];
// 定时器
RACSignal *timerSignal = ];
[timerSignal subscribeNext:^(NSDate * _Nullable x) {
NSLog(@"Timer tick: %@", x);
}];
// 节流操作
RACSignal *throttledSignal = [;
[throttledSignal subscribeNext:^(NSString * _Nullable x) {
NSLog(@"Throttled value: %@", x);
}];
</code></pre>
<h4 id="原理解析-4">原理解析:</h4>
<ol>
<li><code>delay:</code>将信号的所有事件延迟指定的时间发送。</li>
<li><code>interval:</code>创建一个定时器信号,每隔指定的时间发出一个事件。</li>
<li><code>throttle:</code>在指定的时间内只发送最新的一次事件,防止高频率触发。</li>
</ol>
<h3 id="6-错误处理">6. 错误处理</h3>
<h3 id="场景网络请求错误与重试机制">场景:网络请求错误与重试机制</h3>
<h4 id="示例代码-5">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
- (RACSignal *)fetchDataFromURL:(NSURL *)url {
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>_Nonnull subscriber) {
NSURLSessionDataTask *task = [ dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
;
} else {
;
;
}
}];
;
return [RACDisposable disposableWithBlock:^{
;
}];
}];
}
[[ retry:3] subscribeNext:^(NSData *data) {
// Process data
} error:^(NSError *error) {
// Handle error
}];
</code></pre>
<h4 id="原理解析-5">原理解析:</h4>
<ol>
<li><code>retry:</code>在信号遇到错误时重新订阅信号,最多重试指定的次数。</li>
<li>使用<code>sendError:</code>, <code>sendNext:</code>, <code>sendCompleted:</code>手动控制信号的错误和数据发送。</li>
</ol>
<h3 id="7-依赖信号">7. 依赖信号</h3>
<h3 id="场景顺序执行多个异步任务">场景:顺序执行多个异步任务</h3>
<h4 id="示例代码-6">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
- (RACSignal *)loginSignal:(NSString *)username password:(NSString *)password {
// 登录 signal implementation
}
- (RACSignal *)fetchUserProfileSignal {
// 获取用户Profile的信号
}
[[
flattenMap:^__kindof RACSignal * _Nullable(id_Nullable value) {
return ;
}]
subscribeNext:^(id_Nullable x) {
NSLog(@"User Profile: %@", x);
} error:^(NSError * _Nullable error) {
NSLog(@"Error: %@", error);
}];
</code></pre>
<h4 id="原理解析-6">原理解析:</h4>
<ol>
<li><code>flattenMap:</code>将第一个信号的值映射为另一个信号,并订阅这个新信号。这通常用于依赖关系,即在一个任务完成后启动另一个任务。</li>
<li>在登录成功后,使用<code>flattenMap:</code>触发获取用户Profile的信号。</li>
</ol>
<h3 id="8-控件事件处理">8. 控件事件处理</h3>
<h3 id="场景按钮点击事件">场景:按钮点击事件</h3>
<h4 id="示例代码-7">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *myButton;
@end
@implementation ViewController
- (void)viewDidLoad {
;
[
subscribeNext:^(UIButton *button) {
NSLog(@"Button clicked");
}];
}
@end
</code></pre>
<h4 id="原理解析-7">原理解析:</h4>
<ol>
<li><code>rac_signalForControlEvents:</code>将UI控件的事件(例如按钮的点击)转化为信号。</li>
<li>通过<code>subscribeNext:</code>订阅该信号,以处理点击事件。</li>
</ol>
<h3 id="9-定时操作">9. 定时操作</h3>
<h3 id="场景倒计时功能">场景:倒计时功能</h3>
<h4 id="示例代码-8">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *countdownLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
;
__block int remainingTime = 60;
RACSignal *countdownSignal = []
take:remainingTime];
[countdownSignal subscribeNext:^(NSDate * _Nullable x) {
self.countdownLabel.text = ;
remainingTime--;
} completed:^{
self.countdownLabel.text = @"Time's up!";
}];
}
@end
</code></pre>
<h4 id="原理解析-8">原理解析:</h4>
<ol>
<li><code>interval:onScheduler:</code>创建一个每秒发出事件的信号。</li>
<li><code>take:</code>指定信号发送的事件次数。</li>
<li>通过<code>subscribeNext:</code>更新界面。</li>
</ol>
<h3 id="10-表格视图刷新">10. 表格视图刷新</h3>
<h3 id="场景下拉刷新">场景:下拉刷新</h3>
<h4 id="示例代码-9">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
#import <MJRefresh/MJRefresh.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@end
@implementation ViewController
- (void)viewDidLoad {
;
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
[ subscribeNext:^(NSArray *data) {
// Update table view with new data
;
;
}];
}];
}
- (RACSignal *)fetchData {
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>_Nonnull subscriber) {
// Simulate network request
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSArray *data = @[@"Item 1", @"Item 2", @"Item 3"];
;
;
});
return nil;
}];
}
@end
</code></pre>
<h4 id="原理解析-9">原理解析:</h4>
<ol>
<li>使用第三方库MJRefresh添加下拉刷新控件。</li>
<li>在刷新时,发起数据请求的信号并更新表格视图数据。</li>
<li>在订阅数据之后结束刷新状态。</li>
</ol>
<h3 id="11-链式反应">11. 链式反应</h3>
<h3 id="场景密码和确认密码验证">场景:密码和确认密码验证</h3>
<h4 id="示例代码-10">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
RACSignal *validPasswordSignal = [self.passwordTextField.rac_textSignal
map:^id _Nullable(NSString * _Nullable password) {
return @(password.length > 3);
}];
RACSignal *validConfirmPasswordSignal = [RACSignal
combineLatest:@
reduce:^id (NSString *password, NSString *confirmPassword) {
return @(password.length > 3 && );
}];
[
reduce:^id (NSNumber *passwordValid, NSNumber *confirmPasswordValid){
return @( && );
}]
subscribeNext:^(NSNumber *formValid) {
self.registerButton.enabled = ;
}];
</code></pre>
<h4 id="原理解析-10">原理解析:</h4>
<ol>
<li><code>rac_textSignal</code>处理密码和确认密码输入框的文本变化。</li>
<li><code>combineLatest:reduce:</code>将两个信号结合,并在两个信号联动时验证密码和确认密码。</li>
<li>通过组合后的信号来控制注册按钮是否可用。</li>
</ol>
<h3 id="12-进度条更新">12. 进度条更新</h3>
<h3 id="场景文件下载进度">场景:文件下载进度</h3>
<h4 id="示例代码-11">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
- (RACSignal *)downloadFileWithProgress {
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>_Nonnull subscriber) {
NSURLSession *session = ;
NSURL *url = ;
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url
completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
;
} else {
;
}
}];
;
return [RACDisposable disposableWithBlock:^{
;
}];
}];
}
[ subscribeCompleted:^{
self.progressView.progress = 1.0;
} error:^(NSError * _Nullable error) {
NSLog(@"Download failed: %@", error);
}];
]
subscribeNext:^(NSDate * _Nullable x) {
self.progressView.progress = (arc4random_uniform(100) / 100.0);
}];
</code></pre>
<h4 id="原理解析-11">原理解析:</h4>
<ol>
<li><code>createSignal:</code>进行文件下载,并在下载完成时发送完成事件。</li>
<li>创建一个定时信号来模拟下载进度,并实时更新UI。</li>
</ol>
<h3 id="13-监听值变化">13. 监听值变化</h3>
<h3 id="场景属性绑定">场景:属性绑定</h3>
<h4 id="示例代码-12">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
@interface MyViewModel : NSObject
@property (nonatomic, strong) NSString *name;
@end
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (nonatomic, strong) MyViewModel *viewModel;
@end
@implementation ViewController
- (void)viewDidLoad {
;
self.viewModel = [ init];
RAC(self.nameLabel, text) = RACObserve(self.viewModel, name);
self.viewModel.name = @"Initial name";
}
@end
</code></pre>
<h4 id="原理解析-12">原理解析:</h4>
<ol>
<li><code>RACObserve</code>监听ViewModel属性变化。</li>
<li>使用<code>RAC(target, keyPath)</code>绑定属性值到UILabel的text属性。</li>
</ol>
<h3 id="14-动态计算属性">14. 动态计算属性</h3>
<h3 id="场景计算两数之和">场景:计算两数之和</h3>
<h4 id="示例代码-13">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *firstNumberTextField;
@property (weak, nonatomic) IBOutlet UITextField *secondNumberTextField;
@property (weak, nonatomic) IBOutlet UILabel *sumLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
;
RACSignal *firstNumberSignal = [self.firstNumberTextField.rac_textSignal map:^id _Nullable(NSString * _Nullable text) {
return @();
}];
RACSignal *secondNumberSignal = [self.secondNumberTextField.rac_textSignal map:^id _Nullable(NSString * _Nullable text) {
return @();
}];
RACSignal *sumSignal = reduce:^id (NSNumber *firstNumber, NSNumber *secondNumber) {
return @( + );
}];
RAC(self.sumLabel, text) = [sumSignal map:^id _Nullable(NSNumber * _Nullable sum) {
return ;
}];
}
@end
</code></pre>
<h4 id="原理解析-13">原理解析:</h4>
<ol>
<li><code>map:</code>将文本转化为数字。</li>
<li><code>combineLatest:reduce:</code>计算两数之和。</li>
<li>使用<code>RAC(target, keyPath)</code>绑定结果到UILabel。</li>
</ol>
<h3 id="15-处理通知">15. 处理通知</h3>
<h3 id="场景监听键盘弹出和隐藏">场景:监听键盘弹出和隐藏</h3>
<h4 id="示例代码-14">示例代码:</h4>
<pre><code class="language-objective-c">#import <ReactiveObjC/ReactiveObjC.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint;
@end
@implementation ViewController
- (void)viewDidLoad {
;
RACSignal *keyboardShowSignal = [[ rac_addObserverForName:UIKeyboardWillShowNotification object:nil] takeUntil:self.rac_willDeallocSignal];
RACSignal *keyboardHideSignal = [[ rac_addObserverForName:UIKeyboardWillHideNotification object:nil] takeUntil:self.rac_willDeallocSignal];
RACSignal *keyboardFrameSignal = ]
.map(^id (NSNotification *notification) {
return ;
}];
[keyboardFrameSignal subscribeNext:^(NSValue *keyboardFrame) {
CGRect frame = keyboardFrame.CGRectValue;
self.bottomConstraint.constant = frame.size.height;
[UIView animateWithDuration:0.25 animations:^{
;
}];
}];
}
@end
</code></pre>
<h4 id="原理解析-14">原理解析:</h4>
<ol>
<li><code>rac_addObserverForName:</code>监听键盘显示和隐藏通知。</li>
<li><code>map:</code>将通知转化为键盘的frame值。</li>
<li>通过订阅信号动态调整底部约束。</li>
</ol>
<p>这些场景展示了ReactiveCocoa在iOS开发中的多种应用,通过信号和操作符,能够简化异步操作和事件处理,使代码更加简洁和可维护。</p>
</div>
<div id="MySignature" role="contentinfo">
将来的你会感谢今天如此努力的你!
版权声明:本文为博主原创文章,未经博主允许不得转载。<br><br>
来源:https://www.cnblogs.com/chglog/p/18307052
頁:
[1]