网站首页 > 技术教程 正文
《ReactiveCocoa 概述》
《RACSignal》
《RACDisposable》
《RACSubject、RACReplaySubject(内附冷信号和热信号的区别)》
《集合RACTuple、RACSequence》
《RAC 中的通知、代理、KVO, 基本事件、方法的监听》
《rac_liftSelector》
《RACMulticastConnection》
《RACCommand》
《RAC - 核心方法bind》
《RAC - 定时器》
《RACScheduler》
《RAC - 点击获取验证码 demo》
《RAC - 映射(Map & flattenMap)》
《RAC信号操作解释合集》
《RAC - 信号的生命周期》
1. 简介
ReactiveCocoa (简称为RAC)是由Github开源的一个应用于iOS和OS开发的新框架, 是基于响应式编程思想的Objective-C的实践, Cocoa则是苹果整套框架的简称.
2. 编程思想
结合了以下两种编程风格:
函数式编程(Functional Programming):
把操作尽量写成一系列嵌套的函数或者方法调用.每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果), 即每一步都需要有结果.
响应式编程(Reactive Programming):
不需要考虑调用顺序,只需要知道考虑结果, 即一个改变就会使结果改变.典型例子(AutoLayout): aView 上添加的view, 当aView 约束发生变化时, 的view 也会随之改变.
所以, ReactiveCocoa被描述为函数响应式编程框架.
3. 如何导入ReactiveCocoa框架
通常都会使用CocoaPods导入,
PS: iOS-Cocoapods 的正确安装姿势
注意: (大小写字母一点不要写错)
ReactiveObjC -- 对应的是RAC的OC版本
ReactiveCocoa--对应的是RAC的swift版本
3.1 纯OC项目
pod "ReactiveObjC"
3.2 OC和Swift的混合项目
pod "ReactiveObjC"
pod "ReactiveCocoa"
pod "ReactiveObjCBridge"
3.3 纯Swift项目
pod "ReactiveCocoa"
4. ReactiveCocoa常见类
注: 该图片来源于网络.
.End
--------------
RACSignal: 信号类, 本身不具备发送信号的能力, 当被订阅后, 用于传递改变的数据
- sendNext(id):可理解为传递正确数据,告诉订阅者进行下一步处理
- sendError:传递的数据错误,告诉订阅者错误处理
- sendCompleted:告诉订阅者已完成
- (void)signalTest {
/* 1. 创建信号 signal
- 通过 createSignal: 方法创建,其参数为一个返回值位RACDisposable 类型的block (didSubcribe)
*/
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
// 2. 通过block 传入的订阅者subscriber 来发送信息
[subscriber sendNext:@10];
// sendError、sendCompleted 二者只能发送其一, 就代表结束了.
[subscriber sendError:[NSError errorWithDomain:@"错误" code:1001 userInfo:nil]];
[subscriber sendCompleted];
// 这里需要返回一个RACDisposable 类型的对象, 用于提前结束订阅等操作, 一般无特殊需求, 返回nil 即可.
return nil;
}];
/* 3. 订阅者 (subscriber)
- subscribeNext + error + completed 组合起来就是订阅者
- 一旦订阅者订阅了信号消息, 就会执行上面的didSubcribe 的block.
*/
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
} error:^(NSError * _Nullable error) {
NSLog(@"ERROR=%@", error);
} completed:^{
NSLog(@"完成");
}];
}
RAC 中的通知、代理、KVO, 基本事件、方法的监听
rac_addObserverForName(通知)
OC正常写法(需要自己单独实现noti: 方法去处理通知事件)
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti:) name:@"noti" object:nil];
OC 中使用RAC 写法
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"noti" object:nil] subscribeNext:^(NSNotification * _Nullable x) {
// 处理通知事件
}];
↓rac_addObserverForName: 原理分析↓
- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object {
@unsafeify(object);
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
@strongify(object);
// 内部封装的 KVO
id observer = [self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) {
// 发送监听信息
[subscriber sendNext:note];
}];
return [RACDisposable disposableWithBlock:^{
// 在取消订阅后, 移除观察者
[self removeObserver:observer];
}];
}] setNameWithFormat:@"-rac_addObserverForName: %@ object: <%@: %p>", notificationName, [object class], object];
}
- 代理
RACSubject 通常用来代替代理
rac_signalForSelector 也可以用作代理, 但是无法传值!
这里只说RACSubject , rac_signalForSelector 在下文中有详细介绍.
- 首先创建一个RedView的类继承于UIView
RedView.h
#import <UIKit/UIKit.h>
#import <ReactiveObjC/ReactiveObjC.h>
NS_ASSUME_NONNULL_BEGIN
@interface RedView : UIView
// rac 实现代理
@property (nonatomic, strong)RACSubject *subject;
@end
NS_ASSUME_NONNULL_END
RedView.m
#import "RedView.h"
@implementation RedView
- (RACSubject *)subject{
if(!_subject){
_subject = [RACSubject subject];
}
return _subject;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.subject sendNext:@"红色视图"];
}
@end
控制器中代码:
#import "ViewController.h"
#import <ReactiveObjC/ReactiveObjC.h>
#import "RedView.h"
@interface ViewController ()
@property (nonatomic, strong)RedView *red;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self delegateTest];
}
- (void)delegateTest {
self.red = [[RedView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
self.red.backgroundColor = [UIColor redColor];
[self.view addSubview:self.red];
[self.red.subject subscribeNext:^(id _Nullable x) {
NSLog(@"代理: %@", x);
}];
}
- KVO
需要引入 #import <NSObject+RACKVOWrapper.h>头文件
方式一:
- (void)RAC_KVOTest1 {
self.kvoView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
self.kvoView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.kvoView];
[self.kvoView rac_observeKeyPath:@"frame" options:NSKeyValueObservingOptionNew observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
NSLog(@"监听到最新frame - %@", value);
}];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.kvoView.frame = CGRectMake(50, 50, 50, 50);
}
输出结果: 监听到最新frame - NSRect: {{50, 50}, {50, 50}}
方式二:
- (void)RAC_KVOTest2 {
self.kvoView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
self.kvoView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.kvoView];
[[self.kvoView rac_valuesForKeyPath:@"frame" observer:self] subscribeNext:^(id _Nullable x) {
// 刚开始运行, 就会先打印一遍原始值 -> 监听到最新frame - NSRect: {{100, 100}, {100, 100}}
NSLog(@"监听到最新frame - %@", x);
// 然后点击打印 -> 监听到最新frame - NSRect: {{50, 50}, {50, 50}}
}];
}
方式三:
- (void)RAC_KVOTest3 {
self.kvoView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
self.kvoView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.kvoView];
// 使用RAC 封装的宏定义, 打印结果和方式2 一样
[RACObserve(self.kvoView, frame) subscribeNext:^(id _Nullable x) {
NSLog(@"监听到最新frame - %@", x);
}];
}
注意: 方式2、3中有注释提到, 在监听过程中会出现先打印一次原始数据值
- rac_signalForSelector(监听方法)
- (void)rac_signalForSelectorTest {
[[self rac_signalForSelector:@selector(touchesBegan:withEvent:)] subscribeNext:^(RACTuple * _Nullable x) {
NSLog(@"监听屏幕点击方法: %@", x);
}];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
}
也可以用作代理的使用, 比如检测上面RedView 的点击事件
[[self.red rac_signalForSelector:@selector(touchesBegan:withEvent:)] subscribeNext:^(RACTuple * _Nullable x) {
NSLog(@"控制器知道RedView被点击了");
}];
- rac_signalForControlEvents(监听事件)
- (void)rac_signalForControlEventsTest {
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(200, 200, 100, 50)];
button.backgroundColor = [UIColor blueColor];
[self.view addSubview:button];
[[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"按钮被点击了");
}];
}
- rac_textSignal(监听textfield输入)
- (void)rac_textSignalTest {
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(200, 200, 100, 50)];
textField.layer.borderColor = [UIColor redColor].CGColor;
textField.layer.borderWidth = 1;
textField.layer.cornerRadius = 5;
textField.clipsToBounds = YES;
[self.view addSubview:textField];
[textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
// x:是最新文本数据
NSLog(@"%@", x);
}];
}
- RAC 的宏
RAC(<#TARGET, ...#>)
RAC(对象,对象的属性) = (一个信号);
比如:RAC(button, enable) = (RACSignal) 按钮的enable 等于一个信号
.End
RACSubject、RACReplaySubject(内附冷信号和热信号的区别)
- RACSubject
RACSignal 是冷信号, 不能够自己发送信号, 需要订阅者订阅, 特点是确定未来, 也就是知道什么时候结束/终止, 无视订阅者, 不管谁订阅, 都是从头开始执行一段老代码
帮助理解: 冷信号 相当于 剧本, 当订阅者订阅时, 就相当于开始拍戏, 不管谁订阅, 都是从头开始拍, 拍完了也就结束了.
RACSubject 继承自RACSignal, 是热信号, 也就是说既可以充当信号,也可以发送信号, 并不确定什么时候终止, 关心订阅者, 先来先得、后来少得
帮助理解: 热信号 相当于 拍好的戏, 当订阅者订阅时, 就开始演戏, 接下来再有订阅者订阅, 演到哪里就继续演, 不会重新从头开始, 即先来的看得多、后来看的就少.
代码分析:
// 1.创建信号
RACSubject *subject = [RACSubject subject];
// 2.订阅信号
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"x = %@", x);
}];
// 3.发送数据
[subject sendNext:@10];
↓[RACSubject subject] 内部实现↓
+ (instancetype)subject {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (self == nil) return nil;
// 创建一个 复合的_disposable 对象, 用于取消订阅
_disposable = [RACCompoundDisposable compoundDisposable];
// 创建一个 _subscribers 订阅者集合, 用来保存订阅者
_subscribers = [[NSMutableArray alloc] initWithCapacity:1];
return self;
}
↓ 订阅信号 subscribeNext: 内部实现↓
其实内部也RACSignal 信号订阅大致相同, 唯一不同的是subscribe: 方法的实现:
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
NSMutableArray *subscribers = self.subscribers;
@synchronized (subscribers) {
// 注意: 每次订阅, 都将订阅者添加至 信号内部的_subscribers 数组中
[subscribers addObject:subscriber];
}
[disposable addDisposable:[RACDisposable disposableWithBlock:^{
@synchronized (subscribers) {
// Since newer subscribers are generally shorter-lived, search
// starting from the end of the list.
NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
return obj == subscriber;
}];
if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
}
}]];
return disposable;
}
↓ 发布消息sendNext: 内部实现↓
- (void)sendNext:(id)value {
// 遍历_subscribers 集合, 向每一个订阅者发送sendNext: 消息, 即所有订阅者依次发送消息
[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
[subscriber sendNext:value];
}];
}
总结
创建的subject的是内部会创建一个数组_subscribers用来保存所有的订阅者订阅信息的时候会创建订阅者,并且保存到数组中遍历subject中_subscribers中的订阅者,依次发送信息
注意
RACSubject 可以被订阅多次,并且只能是先订阅后发布, 因为:先发送, 再有订阅者, 订阅者收不到订阅之前的消息, 所以称之为先来先得, 晚来少得
- RACReplaySubject
针对RACSubject 的注意点, 偏偏要先发送消息, 再去订阅信号, 该怎么办呢???
这里就可以使用RACReplaySubject , 它继承自RACSubject, 目的就是来解决先发送信号后订阅的问题.
代码分析: 和RACSubject 的使用一毛一样
// 先发送信号
[replaySubject sendNext:@10];
// 后订阅信号
[replaySubject subscribeNext:^(id _Nullable x) {
// 可以正常打印
NSLog(@"x = %@", x);
}];
↓具体实现原理↓
RACReplaySubject 对象创建的时候, 会在父类的基础之上多做一步,创建一个数组用来保存发送的数据(当_subscribers 中所有订阅者都成功发送了数据, 那么就会删除当前要发送的数据, 避免出现一个数据重复发送的问题)发送数据: 当有订阅者订阅时,发送数据; 没有订阅者订阅时发送失败, 就不发送,等待新的订阅者.订阅信号, 先遍历一次保存数据的数组, 如果有就执行第二步
.End
作者:下班不写程序
来源:简书
猜你喜欢
- 2024-10-24 四季?转,爱情常在:守护平淡中的不平凡
- 2024-10-24 COCOA水屋前面一点点就是海沟浮潜很不错,偶遇鲨鱼
- 2024-10-24 欣赏夏天的草原有多迷人,享受着惬意时光!
- 2024-10-24 “黄昏醉影:陪你走过每一个告别时刻”
- 2024-10-24 丁丁历险记 摄影师 木各格Cocoa(丁丁历险记演员)
- 2024-10-24 死契(花魁)(死契1提示)
- 2024-10-24 源码推荐(03.08):ReactiveCocoa登录交互效果的实现,可编辑可拖动排序
- 2024-10-24 Cocoa+豪华巧克力包装设计(巧克力包装设计说明模板)
- 2024-10-24 经典白蛇回归!Air Force 1 “Cocoa Snake” 即将复刻登场
- 2024-10-24 BobbiBrown新品唇膏cocoa! !日杂榛果奶茶~
你 发表评论:
欢迎- 最近发表
-
- Win11学院:如何在Windows 11上使用WSL安装Ubuntu
- linux移植(Linux移植freemodbus)
- 独家解读:Win10预览版9879为何无法识别硬盘
- 基于Linux系统的本地Yum源搭建与配置(ISO方式、RPM方式)
- Docker镜像瘦身(docker 减小镜像大小)
- 在linux上安装ollama(linux安装locale)
- 渗透测试系统Kali推出Docker镜像(kali linux渗透测试技术详解pdf)
- Linux环境中部署Harbor私有镜像仓库
- linux之间传文件命令之Rsync傻瓜式教程
- 解决ollama在linux中安装或升级时,通过国内镜像缩短安装时长
- 标签列表
-
- 下划线是什么 (87)
- 精美网站 (58)
- qq登录界面 (90)
- nginx 命令 (82)
- nginx .http (73)
- nginx lua (70)
- nginx 重定向 (68)
- Nginx超时 (65)
- nginx 监控 (57)
- odbc (59)
- rar密码破解工具 (62)
- annotation (71)
- 红黑树 (57)
- 智力题 (62)
- php空间申请 (61)
- 按键精灵 注册码 (69)
- 软件测试报告 (59)
- ntcreatefile (64)
- 闪动文字 (56)
- guid (66)
- abap (63)
- mpeg 2 (65)
- column (63)
- dreamweaver教程 (57)
- excel行列转换 (56)
本文暂时没有评论,来添加一个吧(●'◡'●)