美文网首页
RAC底层原理分析下

RAC底层原理分析下

作者: 浅墨入画 | 来源:发表于2022-12-13 22:37 被阅读0次

RACSubject

RACSubjectRACSignal的子类,既能订阅信号也能发送信号;拥有RACSignalRACSubscriber两者的功能。

- (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,所以三次订阅只是把subscribersubscribers数组中添加。
  • 查看[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只执行一次,也就意味着上面多次订阅的信号只会产生一次。

RACSubject流程

RAC宏定义

#define表示预编译或预处理的宏,#号最终会在底层编译的时候去掉
#表示宏参数,代替参数值为内容的字符常量
A##B最终会变成AB的连接字符
...表示 宏参数,常搭配__VA_ARGS,输出打印函数里边就有这样的封装

下面我们来学习宏@weakify(self, name);的封装流程

@property (nonatomic, copy) NSString *name;

- (void)viewDidLoad {
    [super viewDidLoad];
    // @表示从C语言编译成OC语言
    @weakify(self, name);
}
  • 查看RACweakify的定义
// \表示换行符,__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
  1. 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;
  2. 接下来metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)开始连接metamacro_foreach_cxtmetamacro_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(...)RACRACTupleRAC(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来的,那么我们就来查看addedExecutionSignalsSubjectsignal是如何来的?主要是看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来开发一个登陆业务?

准备工作:新建工程RACCommandMain.storyboard文件中创建好相应的UI页面,并且新建好LoginViewModel类,注意Pods中引入相应的库,工程结构如下图

RACCommand工程
  • 使用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事件响应的监听  (这里不再啰嗦)
完整登录效果

相关文章

网友评论

      本文标题:RAC底层原理分析下

      本文链接:https://www.haomeiwen.com/subject/yqttqdtx.html