RACSubject
RACSubject
是RACSignal
的子类,既能订阅信号
也能发送信号
;拥有RACSignal
和RACSubscriber
两者的功能。
- (void)demoSubject{
// 先订阅再发送,才能接收到信号
RACSubject *subject = [RACSubject subject];
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"三文鱼"];
}
// 打印日志
2022-12-11 17:36:27.757701+0800 001---RACCommand[7300:199693] 三文鱼
为什么RACSubject
既能订阅信号也能发送信号呢?看源码...
- 查看
[RACSubject subject];
源码
+ (instancetype)subject {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (self == nil) return nil;
_disposable = [RACCompoundDisposable compoundDisposable];
// 订阅数组 --- 数组中存的是订阅 --- 什么时候存进去的呢?
_subscribers = [[NSMutableArray alloc] initWithCapacity:1];
return self;
}
- 查看
[subject subscribeNext:...]
源码
// 这一步跟 [signal subscribeNext:...] 一样
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
// 不一样的地方 ---
// 保证线程安全的同时 添加订阅者 ---- subscribers ---- signal(subject)
// --- 发送信号 --
// 订阅只需要遍历 取出subscriber即可
NSMutableArray *subscribers = self.subscribers;
@synchronized (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;
}
- 查看
[subject sendNext:@"三文鱼"];
源码
- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
// 回调拿到订阅者,进行发送信号
[subscriber sendNext:value];
}];
}
- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {
NSArray *subscribers;
@synchronized (self.subscribers) {
subscribers = [self.subscribers copy];
}
// 遍历 -- 订阅者
for (id<RACSubscriber> subscriber in subscribers) {
block(subscriber);
}
}
这种应用场景:发送信号和响应信号是同一个对象
,如果UI需要响应,响应的同时又需要一些接收逻辑进行反馈,就有了即攻也为守的 RACSubject
应用。
案例一:RACSubject
使用的另一场景,需要多次订阅;假如发送的信号,是网络请求回来的,而且多次订阅的地方都需要网络请求回来的数据
- (void)subject{
// 1:创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"信号产生了");
[NSThread sleepForTimeInterval:1]; // 带宽,模拟网络请求时间
// 3:信号发送
[subscriber sendNext:@"Hello World"];
// 4:销毁信号
return [RACDisposable disposableWithBlock:^{
NSLog(@"销毁了");
}];
}];
// 1:信号订阅 ---> 信号订阅的产生 ---- signal
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"1:订阅到了%@",x);
}];
// 2:信号订阅 ---> 信号订阅的产生 ---- signal
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"2:订阅到了%@",x);
}];
// 3:信号订阅 ---> 信号订阅的产生 ---- signal
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"3:订阅到了%@",x);
}];
}
// 打印日志
2022-12-11 19:07:59.372717+0800 001---RACCommand[7990:240122] 信号产生了
2022-12-11 19:08:00.373720+0800 001---RACCommand[7990:240122] 1:订阅到了Hello World
2022-12-11 19:08:00.374207+0800 001---RACCommand[7990:240122] 销毁了
2022-12-11 19:08:00.374623+0800 001---RACCommand[7990:240122] 信号产生了
2022-12-11 19:08:01.375733+0800 001---RACCommand[7990:240122] 2:订阅到了Hello World
2022-12-11 19:08:01.375941+0800 001---RACCommand[7990:240122] 销毁了
2022-12-11 19:08:01.376085+0800 001---RACCommand[7990:240122] 信号产生了
2022-12-11 19:08:02.376652+0800 001---RACCommand[7990:240122] 3:订阅到了Hello World
2022-12-11 19:08:02.376944+0800 001---RACCommand[7990:240122] 销毁了
通过日志我们发现信号产生了三次,如果订阅的信息需要网络请求,那么就会进行多次请求
,这样就严重影响了性能;对于这种情况,就可以使用RACSubject
进行解决。
- (void)subject{
// 1:创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"信号产生了");
[NSThread sleepForTimeInterval:1]; // 带宽
// 3:信号发送
[subscriber sendNext:@"Hello World"];
// 4:销毁信号
return [RACDisposable disposableWithBlock:^{
NSLog(@"销毁了");
}];
}];
RACMulticastConnection *connect = [signal publish];
// connect.signal ---> subject add subcriber ---> subject.subcribers
// 1:信号订阅 ---> 信号订阅的产生 ---- signal
// subject.subcribers中会有循环遍历 sendNext --- RACMulticastConnection直接对subject进行封装保证每次订阅都能来
[connect.signal subscribeNext:^(id _Nullable x) {
NSLog(@"1:订阅到了%@",x);
}];
// 2:信号订阅 ---> 信号订阅的产生 ---- signal
[connect.signal subscribeNext:^(id _Nullable x) {
NSLog(@"2:订阅到了%@",x);
}];
// 3:信号订阅 ---> 信号订阅的产生 ---- signal
[connect.signal subscribeNext:^(id _Nullable x) {
NSLog(@"3:订阅到了%@",x);
}];
// 函数 ---> 保证signal的产生只执行一次
[connect connect];
}
// 打印日志
2022-12-11 19:17:36.751917+0800 001---RACCommand[8081:246152] 信号产生了
2022-12-11 19:17:37.753348+0800 001---RACCommand[8081:246152] 1:订阅到了Hello World
2022-12-11 19:17:37.754041+0800 001---RACCommand[8081:246152] 2:订阅到了Hello World
2022-12-11 19:17:37.754729+0800 001---RACCommand[8081:246152] 3:订阅到了Hello World
2022-12-11 19:17:37.795748+0800 001---RACCommand[8081:246152] 销毁了
通过日志我们就可以看出,信号只产生了一次
,而三处地方都订阅到了信号信息
。
下面我们查看源码来分析其底层原理
- 查看
[signal publish];
源码
- (RACMulticastConnection *)publish {
RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
RACMulticastConnection *connection = [self multicast:subject];
return connection;
}
// RACMulticastConnection 继承自 NSObject
@interface RACMulticastConnection<__covariant ValueType> : NSObject
- (RACMulticastConnection *)multicast:(RACSubject *)subject {
[subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
// self 就是 signal (dynamicasinal)
RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
return connection;
}
- 查看
RACMulticastConnection
初始化方法initWithSourceSignal:
- (instancetype)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
NSCParameterAssert(source != nil);
NSCParameterAssert(subject != nil);
self = [super init];
_sourceSignal = source;
_serialDisposable = [[RACSerialDisposable alloc] init];
_signal = subject;
return self;
}
通过initWithSourceSignal
源码,我们发现connect.signal
就是subject
。
- 之前我们在
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber
源码方法中发现subscribers
数组就是在添加订阅者subscriber
,所以三次订阅只是把subscriber
往subscribers
数组中添加。 - 查看
[connect connect];
源码
- (RACDisposable *)connect {
// 函数:推荐把这种对比函数应用于工程中
// 0 VS _hasConnected进行对比 ---> 匹配输出yes,并且把 _hasConnected 赋值为 1
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
// 下面的代码只执行一次
if (shouldConnect) {
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
return self.serialDisposable;
}
// [self.sourceSignal subscribe:_signal]; 调用方法只执行一次
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
subscribe
只执行一次,也就意味着上面多次订阅的信号只会产生一次。
RAC宏定义
#define
表示预编译或预处理的宏,#
号最终会在底层编译的时候去掉
#
表示宏参数,代替参数值为内容的字符常量
A##B
最终会变成AB
的连接字符
...
表示 宏参数,常搭配__VA_ARGS
,输出打印函数里边就有这样的封装
下面我们来学习宏@weakify(self, name);
的封装流程
@property (nonatomic, copy) NSString *name;
- (void)viewDidLoad {
[super viewDidLoad];
// @表示从C语言编译成OC语言
@weakify(self, name);
}
- 查看
RAC
中weakify
的定义
// \表示换行符,__VA_ARGS__表示外部传过来的参数
#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
// 查看 rac_keywordify 的定义
#define rac_keywordify autoreleasepool {}
- 查看
metamacro_foreach_cxt
源码
// MACRO等同于rac_weakify_,SEP等同于空,CONTEXT等同于__weak
// ...等同于__VA_ARGS__
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
// 查看 metamacro_argcount(__VA_ARGS__) 源码
// argcount 表示参数个数
// __VA_ARGS__表示上面传进来的参数 (self, name)
#define metamacro_argcount(...) \
metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
- 查看
metamacro_at
源码
#define metamacro_at(N, ...) \
metamacro_concat(metamacro_at, N)(__VA_ARGS__)
// 查看metamacro_concat
#define metamacro_concat(A, B) \
metamacro_concat_(A, B)
// 查看metamacro_concat_
// 最终传进来的参数会变成 A##B
#define metamacro_concat_(A, B) A ## B
A##B
最终和metamacro_at(N, ...)
中的N
相连变成metamacro_atN(...)
,而N
表示20
,然后把self, name, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
传进去,最终调用的方法是metamacro_at20
,源码如下
// 只能存20个元素
#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)
// self, name 传进来之后变成
metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, self, name) metamacro_head(__VA_ARGS__)
- 查看
metamacro_head
源码
#define metamacro_head(...) \
metamacro_head_(__VA_ARGS__, 0)
// 查看metamacro_head_定义
// 取出第一个参数
#define metamacro_head_(FIRST, ...) FIRST
-
metamacro_at20(self, name, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) metamacro_head(__VA_ARGS__)
方法取前20个参数,输出的__VA_ARGS__
等于2, 1
,而metamacro_head_
输出的是FIRST
即输出2
;最终metamacro_concat(metamacro_at, N)(__VA_ARGS__)
输出的是2
; - 接下来
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
开始连接metamacro_foreach_cxt
与metamacro_argcount(__VA_ARGS__)
,而metamacro_argcount(__VA_ARGS__)
值为2
,就连接成metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, __VA_ARGS__)
- 查看
metamacro_foreach_cxt2
源码
#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \
SEP \
MACRO(1, CONTEXT, _1)
// 查看metamacro_foreach_cxt1
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
而MACRO
就是上面metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
的rac_weakify_
,SEP
为空,CONTEXT
是__weak
,最终MACRO(0, CONTEXT, _0)
演变成rac_weakify_(1, CONTEXT, _1)
- 查看
rac_weakify_
源码
#define rac_weakify_(INDEX, CONTEXT, VAR) \
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
演变成__weak __typeof__(self) self_weak_ = self;
最终rac_weakify_
就是__weak __typeof__(self) self_weak_ = self;
即(@weakify
等价于 __weak __typeof__(self) self_weak_ = (self);
)。
这样的宏定义能达到预编译的效果,最终会还原成C语言
,对这一层的宏定义封装是一整套,比如weakify(...)
、strongify(...)
、RAC
、RACTuple
、RAC(TARGET, ...)
等等,如果不封装这么深,每一次都是写死的(比如rac_weakify_
写死成__weak __typeof__(self) self_weak_ = self;
)就不具备了灵活性;最主要的是封装扩展之后更加动态性。
RACCommand
RACCommand
继承自NSObject
,它能够直接对响应进行监听;可以监听状态,比如当前是否正在执行
、当前是否有相应的error信息
、相应的成功信息
等。
案例一:使用RACCommand
返回一个空信号
@property (weak, nonatomic) IBOutlet UIButton *loginButton;
self.loginButton.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
// 这里还可以对command进行输出
NSLog(@"%@", input);
// 返回一个空信号
return [RACSignal empty];
}];
案例二:监听其他信息
- (void)testCommand{
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"Hello World"];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"销毁");
}];
}];
}];
// 当前command还可以监听其他东西
[command.executionSignals subscribeNext:^(id _Nullable x) {
// 表示有信号来了
NSLog(@"executionSignals == %@", x);
}];
// 有可能发送的信息比较多,订阅最新的信息
[[command.executionSignals switchToLatest]subscribeNext:^(id _Nullable x) {
// 拿到最终响应信号的值
NSLog(@"switchToLatest == %@", x);
}];
// 监听是否正在执行,比如正在请求网络
[command.executing subscribeNext:^(NSNumber * _Nullable x) {
// 正在执行的状态,信号执行完成之后状态又变成0
NSLog(@"executing == %@", x);
}];
// 监听错误信息
[command.errors subscribeError:^(NSError * _Nullable error) {
NSLog(@"%@", error);
}];
[command execute:@"命令"];
}
// 打印日志
2022-12-13 19:53:33.249939+0800 001---RACCommand[29462:990777] executing == 0
2022-12-13 19:53:33.335391+0800 001---RACCommand[29462:990777] executing == 1
2022-12-13 19:53:33.335716+0800 001---RACCommand[29462:990777] executionSignals == <RACDynamicSignal: 0x6000015fd380> name:
2022-12-13 19:53:33.336048+0800 001---RACCommand[29462:990777] switchToLatest == Hello World
2022-12-13 19:53:33.336459+0800 001---RACCommand[29462:990777] 销毁
2022-12-13 19:53:33.336792+0800 001---RACCommand[29462:990777] executing == 0
下面我们来学习下案例二中command
执行的源码
-
RACCommand
类有很多属性是用来判断状态,比如判断是否正在来
、是否正在执行
、是否发生错误
等
@interface RACCommand<__contravariant InputType, __covariant ValueType> : NSObject
@property (nonatomic, strong, readonly) RACSignal<RACSignal<ValueType> *> *executionSignals;
@property (nonatomic, strong, readonly) RACSignal<NSNumber *> *executing;
@property (nonatomic, strong, readonly) RACSignal<NSNumber *> *enabled;
@property (nonatomic, strong, readonly) RACSignal<NSError *> *errors;
/// Whether the command allows multiple executions to proceed concurrently.
///
/// The default value for this property is NO.
@property (atomic, assign) BOOL allowsConcurrentExecution;
- 查看
RACCommand
的初始化方法initWithSignalBlock
- (instancetype)initWithSignalBlock:(RACSignal<id> * (^)(id input))signalBlock {
// initWithEnabled绑定进去
return [self initWithEnabled:nil signalBlock:signalBlock];
}
- 查看
initWithEnabled
源码
- (instancetype)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal<id> * (^)(id input))signalBlock {
NSCParameterAssert(signalBlock != nil);
self = [super init];
// 绑定RACSubject成为内部属性
_addedExecutionSignalsSubject = [RACSubject new];
_allowsConcurrentExecutionSubject = [RACSubject new];
_signalBlock = [signalBlock copy];
// 核心是addedExecutionSignalsSubject,他是属于RACSubject.RACSubject是即可攻也可以守的。
_executionSignals = [[[self.addedExecutionSignalsSubject
map:^(RACSignal *signal) {
return [signal catchTo:[RACSignal empty]];
}]
deliverOn:RACScheduler.mainThreadScheduler]
setNameWithFormat:@"%@ -executionSignals", self];
// `errors` needs to be multicasted so that it picks up all
// `activeExecutionSignals` that are added.
//
// In other words, if someone subscribes to `errors` _after_ an execution
// has started, it should still receive any error from that execution.
RACMulticastConnection *errorsConnection = [[[self.addedExecutionSignalsSubject
flattenMap:^(RACSignal *signal) {
return [[signal
ignoreValues]
catch:^(NSError *error) {
return [RACSignal return:error];
}];
}]
deliverOn:RACScheduler.mainThreadScheduler]
publish];
_errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];
[errorsConnection connect];
RACSignal *immediateExecuting = [[[[self.addedExecutionSignalsSubject
flattenMap:^(RACSignal *signal) {
return [[[signal
catchTo:[RACSignal empty]]
then:^{
return [RACSignal return:@-1];
}]
startWith:@1];
}]
scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) {
return @(running.integerValue + next.integerValue);
}]
map:^(NSNumber *count) {
return @(count.integerValue > 0);
}]
startWith:@NO];
_executing = [[[[[immediateExecuting
deliverOn:RACScheduler.mainThreadScheduler]
// This is useful before the first value arrives on the main thread.
startWith:@NO]
distinctUntilChanged]
replayLast]
setNameWithFormat:@"%@ -executing", self];
RACSignal *moreExecutionsAllowed = [RACSignal
if:[self.allowsConcurrentExecutionSubject startWith:@NO]
then:[RACSignal return:@YES]
else:[immediateExecuting not]];
if (enabledSignal == nil) {
enabledSignal = [RACSignal return:@YES];
} else {
enabledSignal = [enabledSignal startWith:@YES];
}
_immediateEnabled = [[[[RACSignal
combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]
and]
takeUntil:self.rac_willDeallocSignal]
replayLast];
_enabled = [[[[[self.immediateEnabled
take:1]
concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]]
distinctUntilChanged]
replayLast]
setNameWithFormat:@"%@ -enabled", self];
return self;
}
上面[command.executionSignals subscribeNext:...]
的订阅就是通过addedExecutionSignalsSubject
来的;而addedExecutionSignalsSubject
中有了signal
订阅才是有效的。
- 订阅是由
addedExecutionSignalsSubject
来的,那么我们就来查看addedExecutionSignalsSubject
的signal
是如何来的?主要是看execute
举个例子:班长带兵进行排练
。流程就是班长发送指令给士兵,士兵进行排练
,班长->士兵->排练
的过程中,班长不能自己下达这个命令,那么谁下命令给班长呢?是排长。排长的作用就是相当于execute
,班长的作用相当于addedExecutionSignalsSubject
。
// [command execute:@"命令"];
- (RACSignal *)execute:(id)input {
// `immediateEnabled` is guaranteed to send a value upon subscription, so
// -first is acceptable here.
BOOL enabled = [[self.immediateEnabled first] boolValue];
if (!enabled) {
NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{
NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil),
RACUnderlyingCommandErrorKey: self
}];
return [RACSignal error:error];
}
// 当前的block执行,这里的input就传给了 `[[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input)` 中的input
RACSignal *signal = self.signalBlock(input);
NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);
// We subscribe to the signal on the main thread so that it occurs _after_
// -addActiveExecutionSignal: completes below.
//
// This means that `executing` and `enabled` will send updated values before
// the signal actually starts performing work.
// RAC关联者
// 保证信号只能执行一次
RACMulticastConnection *connection = [[signal
subscribeOn:RACScheduler.mainThreadScheduler]
multicast:[RACReplaySubject subject]];
// subscribers
// 遍历 --- subscriber --- sendNext
[self.addedExecutionSignalsSubject sendNext:connection.signal];
// 保证每个信号都能够传递
[connection connect];
return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, RACDescription(input)];
}
我们跟踪signalBlock
回到外部,在外部block
回调中可以根据input
的指令不同,操作不同的事情;比如登录
或者注册
RACCommand *command = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
// 根据input命令不同,去进行登陆或者注册
// 执行2 输入命令
NSLog(@"RACCommand createSignal %@",input);
// 登录 ---> 注册 ---- ?????
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"yy"];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
// 执行6
NSLog(@"销毁了---");
}];
}];
}];
RACCommand流程
RAC总结应用
下面我们使用RACCommand
来开发一个登陆业务?
准备工作:新建工程RACCommand
,Main.storyboard
文件中创建好相应的UI页面,并且新建好LoginViewModel
类,注意Pods中引入相应的库
,工程结构如下图
- 使用
RAC
把UI中的值传入viewModel
中
正向传值
: 就是通过UI 将值 传递给 ViewModel
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *statuslabel;
@property (weak, nonatomic) IBOutlet UIImageView *iconImageView;
@property (weak, nonatomic) IBOutlet UITextField *accountTF;
@property (weak, nonatomic) IBOutlet UITextField *passwordTF;
@property (weak, nonatomic) IBOutlet UIButton *loginButton;
@property (nonatomic, strong) LoginViewModel *loginVM;
@end
- (void)viewDidLoad {
[super viewDidLoad];
//绑定 视图 和 viewModel
[self bindViewModelAndView];
}
- (void)bindViewModelAndView{
// vm <-- UI 传递
// UI中响应的值传入vm中
RAC(self.loginVM,account) = self.accountTF.rac_textSignal;
RAC(self.loginVM,password) = self.passwordTF.rac_textSignal;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"%@",self.loginVM);
}
// 运行工程,账号-密码输入框中分别输入 1234 1234,打印vm查看发现 UI中的值成功绑定到vm中
// 打印日志
2022-12-14 20:44:49.256616+0800 001---RACCommand[37707:1376164] 1234-1234
- 通过
KVO
的方式把viewModel
的值传入UI
层
反向传值
:就是通过viewModel
将值 传递给 UI;如果开发不是使用RAC,传值的情况可能是使用kvo
、代理
、block
- (void)bindViewModelAndView{
@weakify(self);
/** 把UI的值 传递到 VM里面去 */
RAC(self.loginVM,account) = self.accountTF.rac_textSignal;
RAC(self.loginVM,password) = self.passwordTF.rac_textSignal;
// 反向传值 VM ---> UI 传值
// 防止内存循环
[RACObserve(self.loginVM, iconUrl)subscribeNext:^(id _Nullable x) {
@strongify(self);
self.iconImageView.image = [UIImage imageNamed:x];
}];
}
<!-- LoginViewModel.m文件中 init初始化方法 -->
- (instancetype)init{
if (self = [super init]) {
// 通过kvo监听
// skip 1 是调用1次
// 123 --- host ---
// 进行一个map 拼接
// distinctUntilChanged 直到改变的时候 调用 distinctUntilChanged 这个函数
// 回到vc里面进行逻辑处理
RAC(self,iconUrl) = [[[RACObserve(self, account) skip:1] map:^id _Nullable(id _Nullable value)
{
return [NSString stringWithFormat:@"www:%@",value];
}] distinctUntilChanged];
}
return self;
}
账号输入框中输入不同值,头像动态变化,效果图如下
vm传值到ui-
RAC
的直接绑定signal信号属性
,以按钮的状态为例:根据账号密码是否输入
改变登录按钮颜色
的状态
<!-- ViewController.m文件 bindViewModelAndView方法 -->
@weakify(self);
// vm ---> signal
[self.loginVM.loginEnableSignal subscribeNext:^(NSNumber *x) {
@strongify(self);
UIColor *color = (x.intValue == 0) ? [UIColor lightGrayColor] : [UIColor blueColor];
[self.loginButton setBackgroundColor:color];
}];
<!-- LoginViewModel.m文件中 init初始化方法 -->
- (instancetype)init{
if (self = [super init]) {
// 业务逻辑层处理
// 按钮点击能够 ---- account + passwrod --- 函数 组合 + 聚合
self.loginEnableSignal = [RACSignal combineLatest:@[RACObserve(self, account),RACObserve(self, password)] reduce:^id (NSString *account,NSString *password) {
return @(account.length>0 && password.length >0);
}];
}
return self;
}
登录按钮颜色状态
-
RAC
事件响应的监听,以登录按钮的点击发送虚拟的请求为例
<!-- ViewController.m文件 bindViewModelAndView方法 -->
[[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
// 点击登录按钮,要进行网络请求,把网络请求的逻辑迁移至vm,让vm来做
// ----> vm ---> 迁移
// 涉及 ---> 网络请求 ---> 请求状态 -- 响应 ---- 根据不同命令 --- 让vm ---> 请求网络
[self.loginVM.loginCommand execute:@"登录"];
NSLog(@"按钮来了");
}];
<!-- LoginViewModel.m文件 -->
#import "LoginViewModel.h"
@implementation LoginViewModel
- (instancetype)init{
if (self = [super init]) {
// 123 --> host ---
RAC(self,iconUrl) = [[[RACObserve(self, account) skip:1] map:^id _Nullable(id _Nullable value) {
return [NSString stringWithFormat:@"www:%@",value];
}] distinctUntilChanged];
// 按钮能够点击 --- account + password 都有值 --- 函数 组合+聚合
self.loginEnableSignal = [RACSignal combineLatest:@[RACObserve(self, account),RACObserve(self, password)] reduce:^id (NSString *account,NSString *password){
return @(account.length>0&&password.length>0);
}];
self.islogining = NO;
self.statusSubject = [RACSubject subject];
[self setupLoginCommand];
}
return self;
}
- (void)setupLoginCommand{
@weakify(self);
// 初始化command命令
self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
// 网络信号 --- 成功 -- 失败 -- 状态
NSLog(@"input === %@",input);
@strongify(self);
return [self loginRequest];
}];
// 成功
[[self.loginCommand.executionSignals switchToLatest] subscribeNext:^(id _Nullable x) {
NSLog(@"switchToLatest == %@",x);
@strongify(self);
self.islogining = NO;
[self.statusSubject sendNext:@"登录成功"];
}];
// 失败
[self.loginCommand.errors subscribeNext:^(NSError * _Nullable x) {
NSLog(@"errors == %@",x);
self.islogining = NO;
[self.statusSubject sendNext:@"登录失败"];
}];
// 状态
[[self.loginCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
NSLog(@"executing == %@",x);
if ([x boolValue]) {
[self statusLableAnimation];
}
}];
}
#pragma mark - 网络请求
- (RACSignal *)loginRequest{
// 抽取出网络请求部分
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:2];
if ([self.account isEqualToString:@"123"] && [self.password isEqualToString:@"123"]) {
// 可以根据网络请求数据,进行序列化转模型等处理 --- [model class] -- @[model,model]
[subscriber sendNext:@"登录成功"];
[subscriber sendCompleted];
}else{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:100868 userInfo:@{@"LGError":@"fail"}];
[subscriber sendError:error];
}
});
return [RACDisposable disposableWithBlock:^{
NSLog(@"销毁了");
}];
}];
}
- (void)statusLableAnimation{
self.islogining = YES;
__block int num = 0;
RACSignal *timerSignal = [[[RACSignal interval:0.5 onScheduler:[RACScheduler mainThreadScheduler]] map:^id _Nullable(NSDate * _Nullable value) {
NSLog(@"登录时间:%@",value);
NSString *statusStr = @"登录中,请稍后";
num += 1;
int count = num % 3;
switch (count) {
case 0:
statusStr = @"登录中,请稍后.";
break;
case 1:
statusStr = @"登录中,请稍后..";
break;
case 2:
statusStr = @"登录中,请稍后...";
break;
default:
break;
}
return statusStr;
}] takeUntilBlock:^BOOL(id _Nullable x) {
if (num > 10 || !self.islogining) {
return YES;
}
return NO;
}];
[timerSignal subscribeNext:^(id _Nullable x) {
NSLog(@"subscribeNext == %@",x);
[self.statusSubject sendNext:x];
}];
}
- (NSString *)description{
return [NSString stringWithFormat:@"%@-%@",self.account,self.password];
}
@end
完整工程代码如下
-
ViewController
类
<!-- ViewController.h文件 -->
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
<!-- ViewController.m文件 -->
#import "ViewController.h"
#import <ReactiveObjC.h>
#import "LoginViewModel.h"
#import <SVProgressHUD.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *statuslabel;
@property (weak, nonatomic) IBOutlet UIImageView *iconImageView;
@property (weak, nonatomic) IBOutlet UITextField *accountTF;
@property (weak, nonatomic) IBOutlet UITextField *passwordTF;
@property (weak, nonatomic) IBOutlet UIButton *loginButton;
@property (nonatomic, strong) LoginViewModel *loginVM;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//绑定 视图 和 viewModel
[self bindViewModelAndView];
}
- (void)bindViewModelAndView{
// 使用RAC 对viewModel 与 UI进行双向绑定
// VM中主要做业务逻辑,UI层进行展示
@weakify(self);
// vm <-- UI 传递
// UI中响应的值传入vm中
RAC(self.loginVM,account) = self.accountTF.rac_textSignal;
RAC(self.loginVM,password) = self.passwordTF.rac_textSignal;
// 按钮的点击能动性 --- 根据account
RAC(self.loginButton,enabled) = self.loginVM.loginEnableSignal;
// 响应的发送 和 响应的接受 交给 vm -- vm接收到数据需要给到UI
RAC(self.statuslabel,text) = self.loginVM.statusSubject;
// vm ---> UI
// RACObserve --> signal
[RACObserve(self.loginVM, iconUrl) subscribeNext:^(id _Nullable x) {
@strongify(self);
self.iconImageView.image = [UIImage imageNamed:x];
}];
// vm ---> signal
[self.loginVM.loginEnableSignal subscribeNext:^(NSNumber *x) {
@strongify(self);
UIColor *color = (x.intValue == 0) ? [UIColor lightGrayColor] : [UIColor blueColor];
[self.loginButton setBackgroundColor:color];
}];
[[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
// 点击登录按钮,要进行网络请求,把网络请求的逻辑迁移至vm,让vm来做
// ----> vm ---> 迁移
// 涉及 ---> 网络请求 ---> 请求状态 -- 响应 ---- 根据不同命令 --- 让vm ---> 请求网络
[self.loginVM.loginCommand execute:@"登录"];
NSLog(@"按钮来了");
}];
}
#pragma mark - LAZY
- (LoginViewModel *)loginVM{
if (!_loginVM) {
_loginVM = [[LoginViewModel alloc] init];
}
return _loginVM;
}
@end
-
LoginViewModel
类
<!-- LoginViewModel.h文件 -->
#import <Foundation/Foundation.h>
#import <ReactiveObjC.h>
#import <SVProgressHUD.h>
@interface LoginViewModel : NSObject
@property (nonatomic, copy) NSString *iconUrl;
@property (nonatomic, copy) NSString *account;
@property (nonatomic, copy) NSString *password;
@property (nonatomic, strong) RACSignal *loginEnableSignal;
@property (nonatomic, strong) RACCommand *loginCommand;
@property (nonatomic, strong) RACSubject *statusSubject;
@property (nonatomic) BOOL islogining;
@end
<!-- LoginViewModel.m文件 --> 代码同 RAC事件响应的监听 (这里不再啰嗦)
完整登录效果
网友评论