ReactiveCocoa源码解读(一)

本着饮水思源的想法,面对ReactiveCocoa的强大功能,按捺不住心中的好奇心,于是走进其源码之中,一探ReactiveCocoa的魅力所在。虽然,耳闻其强大功能的核心是:信号,但一直不知道这个信号是如何产生、如何传递,又是如何被处理的。曾经以为信号传递是通知,但是真正读了源码后,才发现之前的想法有多不妥,而人家的实现又是多巧妙。

本文主要从ReactiveCocoa的主要类入手,通过剖析其整个应用过程中,信号的生命周期来领略其编程之美。

一、RACSignal

1. 应用

// 1.创建信号
RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    // 注:block在此仅仅是个参数,未被调用,
    //当有订阅者订阅信号时会调用block。

    // 2.发送信号
    [subscriber sendNext:@1];
    // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
    return nil;
}];

// 3.订阅信号,才会激活信号.
[siganl subscribeNext:^(id x) {
    // block调用时刻:每当有信号发出数据,就会调用block.
    NSLog(@"接收到数据:%@",x);
}];

2.源码实现

  • 创建信号

+(RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe;

// RACDynamicSignal.m

+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {
    //创建了一个RACDynamicSignal类的信号
    RACDynamicSignal *signal = [[self alloc] init];
    //将代码块保存到信号里面(但此时仅仅是保存,没有调用),所以信号还是冷信号
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
}
  • 订阅信号
(RACDisposable *)subscribeNext:(void (^ )(id x))nextBlock;
// RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);
    // 内部创建了RACSubscriber(订阅者)类的实例对象o,并且将nextBlock保存到o中,在返回值出执行o,实际也是执行了nextBlock。
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    return [self subscribe:o];
}

// RACSubscriber.m
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
    RACSubscriber *subscriber = [[self alloc] init];
    // 将block保存到subscriber中
    subscriber->_next = [next copy];
    subscriber->_error = [error copy];
    subscriber->_completed = [completed copy];

    return subscriber;
}

// RACDynamicSignal.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
    //判断有无self.didSubscribe,有则执行该self.didSubscribe,意味着将订阅者subscriber发送过去
    if (self.didSubscribe != NULL) {
        RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
            RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            [disposable addDisposable:innerDisposable];
        }];

        [disposable addDisposable:schedulingDisposable];
    }

    return disposable;
}

// RACPassthroughSubscriber.m
- (instancetype)initWithSubscriber:(id)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {
    NSCParameterAssert(subscriber != nil);

    self = [super init];
    if (self == nil) return nil;
    // 保存订阅者,信号,处理操作
    _innerSubscriber = subscriber;
    _signal = signal;
    _disposable = disposable;

    [self.innerSubscriber didSubscribeWithDisposable:self.disposable];
    return self;
}
  • 发送信号
[subscriber sendNext:@1]
// RACPassthroughSubscriber.m
- (void)sendNext:(id)value {
    if (self.disposable.disposed) return;

    if (RACSIGNAL_NEXT_ENABLED()) {
        RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description]));
    }

    [self.innerSubscriber sendNext:value];
}

// RACSubscriber.m
- (void)sendNext:(id)value {
    @synchronized (self) {
        void (^nextBlock)(id) = [self.next copy];
        if (nextBlock == nil) return;
        // 名为next的block是返回值为void,参数为id类型的value,在sendNext:内部,将next复制给nextBlock,执行该方法后,subscribeNext:的block参数才会被调用。
        nextBlock(value);
    }
}

3.流程图

RACSignal

4.总结

先创建信号,然后订阅信号,最后执行didSubscribe内部的方法,顺序是不能变的

RACSignal底层实现

* 1.创建信号,首先把didSubscribe保存到信号中,还不会触发。
* 2.当信号被订阅,也就是调用signal的subscribeNext:nextBlock
    2.1 subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中。
    2.2 subscribeNext内部会调用siganl的didSubscribe
* 3.siganl的didSubscribe中调用[subscriber sendNext:@1];
    3.1 sendNext底层其实就是执行subscriber的nextBlock

二、RACSubject

1. 应用

// 创建信号
RACSubject *subject = [RACSubject subject];

// 订阅
[subject subscribeNext:^(id x) {
    NSLog(@"第一个订阅者:%@", x);
}];

// 发送信号
[subject sendNext:@"1"];

2.源码实现

  • 创建信号
// RACSubject.m
+ (instancetype)subject {
    return [[self alloc] init];
}

- (id)init {
    self = [super init];
    if (self == nil) return nil;

    _disposable = [RACCompoundDisposable compoundDisposable];
    _subscribers = [[NSMutableArray alloc] initWithCapacity:1];

    return self;
}
  • 订阅信号

RACSubject订阅信号的实质就是将内部创建的订阅者保存在订阅者数组self.subscribers中,仅此而已。订阅者对象有一个名为nextBlock的block参数

// RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);

    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    return [self subscribe:o];
}

// RACSubscriber.m
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
    RACSubscriber *subscriber = [[self alloc] init];

    subscriber->_next = [next copy];
    subscriber->_error = [error copy];
    subscriber->_completed = [completed copy];

    return subscriber;
}

// RACSubject.m
- (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 addObject:subscriber];
    }

    return [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];
        }
    }];
}
  • 发送信号
底层实现:
1. 先遍历订阅者数组中的订阅者;
2. 后执行订阅者中的nextBlock;
3. 最后让订阅者发送信号
// RACSubject.m
- (void)sendNext:(id)value {
    [self enumerateSubscribersUsingBlock:^(id subscriber) {
        [subscriber sendNext:value];
    }];
}

- (void)enumerateSubscribersUsingBlock:(void (^)(id subscriber))block {
    NSArray *subscribers;
    @synchronized (self.subscribers) {
        subscribers = [self.subscribers copy];
    }

    for (id subscriber in subscribers) {
        block(subscriber);
    }
}

3.流程图

RACSubject

4.总结

RACSubscriber:表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。

RACDisposable:用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。

RACSubject的底层与RACSignal不一样:

  • 调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了
  • 调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock
  • 由于本质是将订阅者保存到数组中,所以可以有多个订阅者订阅信息。

必须先订阅,后发送信息。订阅信号就是创建订阅者的过程,如果不先订阅,数组中就没有订阅者对象,那就通过订阅者发送消息

三、RACReplaySubject

1.应用

RACReplaySubject即可以先订阅后发送信号,也可以反过来

RACReplaySubject *subject = [RACReplaySubject subject];

// 第一次订阅
[subject subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

// 发送信号
[subject sendNext:@"1"];

// 第一次订阅
[subject subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

2.源码实现

  • 创建信号
// RACSubject.m
+ (instancetype)subject {
    return [[self alloc] init];
}

// RACReplaySubject.m
- (instancetype)init {
    return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity];
}

- (instancetype)initWithCapacity:(NSUInteger)capacity {
    self = [super init];
    if (self == nil) return nil;

    _capacity = capacity;
    // 会用这个数组保存值value
    _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]);

    return self;
}
  • 订阅信号
// RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);

    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    return [self subscribe:o];
}

// RACReplaySubject.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
        @synchronized (self) {
            for (id value in self.valuesReceived) {
                if (compoundDisposable.disposed) return;

                [subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
            }

            if (compoundDisposable.disposed) return;

            if (self.hasCompleted) {
                [subscriber sendCompleted];
            } else if (self.hasError) {
                [subscriber sendError:self.error];
            } else {
                RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
                [compoundDisposable addDisposable:subscriptionDisposable];
            }
        }
    }];

    [compoundDisposable addDisposable:schedulingDisposable];

    return compoundDisposable;
}
  • 发送信号
// RACReplaySubject.m
- (void)sendNext:(id)value {
    @synchronized (self) {
        //重点:发送信号的时候,会先将值value保存到数组中
        [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
        //调用父类发送(先遍历订阅者,然后发送值value)
        [super sendNext:value];

        if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
            [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
        }
    }
}

3.原理图

RACReplaySubject

4.总结

RACReplaySubjectRACSubject的子类
由于每次发送信号时,会先保存nextBlock,然后调用父类的sendNext方法,遍历订阅者,执行信号;而每次订阅信号时,会从valuesReceived中取值,然后调用sendNext方法,遍历订阅者,执行信号。所以,订阅和发送没有先后顺序。


未完待续 ……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: 在PyTorch源码中,有几个关键的类和函数需要解读。首先是torch.utils.data.Sampler类,它负责提供一种遍历数据集所有元素索引的方式。它的len()方法在计算数据集长度时是必要的。\[1\]接下来是torch.utils.data.DataLoader类,它是PyTorch数据加载的核心。它可以加载数据集,并支持多种参数设置,如batch size、shuffle、num_workers等。\[2\]此外,还有一个vocab类,它用于统计解释变量中涉及到的单词的出现频率,并为每个单词分配一个整数作为该单词的整数表示。@classmethod是一个装饰器,用于定义类方法。\[3\]在源码中还有一些其他的类和函数,但这些是其中的一部分。 #### 引用[.reference_title] - *1* *2* [PyTorch 源码解读之 torch.utils.data:解析数据处理全流程(非常好,一篇足够)](https://blog.csdn.net/Highlight_Jin/article/details/126206958)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Transformer实现以及Pytorch源码解读(一)-数据输入篇](https://blog.csdn.net/weixin_41806489/article/details/128380667)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值