(大喇叭:)本篇比较长请耐心阅读,主要解释一下RACSignal的部分内容,详细介绍了RACSequence和RACTuple的原理,介绍了Push-Driven和Pull-Driven。
简介
ReactiveCocoa是由git开源的应用于iOS和OS开发的重型FRP (Functional Reactive Programming 是一种响应变化的编程范式) 框架。内部使用了大量的block。FRP的核心就是信号。
RACStream
This class represents a monad, upon which many stream-based operations can be built.
RACStream
是一种流。本身并没有特别大的用处,主要在两个子类RACSignal和RACSequence上得到体现。
由于RACStream
是一个根类,所以暂时先不做介绍,它的一些方法主要会用在RACSignal中。先把周边介绍一下在回过头来看,就不会那么害怕了。
RACSignal
RACSignal
是ReactiveCocoa中的重要概念,是一种信号流,继承于RACStream
。信号就是数据流,可以用来传递和绑定。
这个RACSignal
通常是冷信号(关于冷信号与热信号之后章节中再详细说明)。
这里引用一下这里的解释,感觉很形象。
使用ReactiveCocoa实现iOS平台响应式编程
把信号想象成水龙头,只不过里面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能抱枕玻璃球是依次排列,不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开放默认是关的,除非有了接收方(subscriber),才会重新打开。这样只要有新的玻璃球进来,就会自动传送给接收方。
创建一个信号 +createSignal:
通常情况下,我们使用[RACSignal createSignal:]
来创建信号。
信号的发送过程查看之前的文章:ReactiveCocoa信号发送详解
RACSignal
会发送三种信号状态,sendNext:
, sendError:
, sendCompleted
。
-
sendNext
:可以发送多个next,subscriber会依次接受发送的数据。 -
sendError
:发送错误,为NSError
对象。 -
sendCompleted
:发送成功,整个流程完成,不会再发送新的next。
在一个Racsignal的生命周期中,可以发送多个next数据,但是只能发送一个Error或者Completed。
+error:
返回一个信号,订阅后,立即发送错误
+never
返回一个永远不会完成的信号。
+startEagerlyWithScheduler:block:
+startLazilyWithScheduler:block:
这个方法跟下面的eagerSequence
有点类似。在没有订阅该信号的情况下,会执行一次block中的内容。这个方法通常会用在异步处理网络请求上。详细的原理,在理解RACMulticastConnection
之后就会比较清楚了。
RACSignal *signal1 = [[RACSignal startEagerlyWithScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground] block:^(id subscriber) {
NSLog(@"signal 1、、、、、、");
[subscriber sendNext:@"1"];
[subscriber sendCompleted];
}] deliverOn:[RACScheduler mainThreadScheduler]];
RACSignal *signal2 = [[RACSignal startLazilyWithScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground] block:^(id subscriber) {
NSLog(@"signal 2、、、、、、、");
[subscriber sendNext:@"2"];
[subscriber sendCompleted];
}] deliverOn:[RACScheduler mainThreadScheduler]];
//运行代码在没有订阅信号的时候会有输出,是因为在[RACMulticastConnection connect]中会有subscribe发生。
//ReactiveCocoaTest[8783:14103027] signal 1、、、、、、
+return:
直接返回一个信号,并发送一个value。
+empty
返回一个nil
-concat:
把一个信号拼接到另一个信号后面,当多个信号发送时,有序的接受信号。如果第一个信号没有发送完成,则后续的信号不会执行。
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@"signalA"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@"signalB"];
return nil;
}];
// 将两个信号拼接
RACSignal *signal = [signalA concat:signalB];
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 此时会输出signalA、signalB。如果调换位置[signalB concat:signalA],则只会输出signalB
原理:
- 创建了一个新的信号。
- 这个新的信号内部中,订阅了当前的信号singalA,依次发送数据sendNext:
- 当发送完成,执行completed,此时signalB会被订阅者订阅
- (RACSignal *)concat:(RACSignal *)signal {
return [[RACSignal createSignal:^(id subscriber) {
RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init];
RACDisposable *sourceDisposable = [self subscribeNext:^(id x) {
[subscriber sendNext:x];
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
RACDisposable *concattedDisposable = [signal subscribe:subscriber];
serialDisposable.disposable = concattedDisposable;
}];
serialDisposable.disposable = sourceDisposable;
return serialDisposable;
}] setNameWithFormat:@"[%@] -concat: %@", self.name, signal];
}
-zipWith:
压缩信号,将两个信号压缩之后,把数据合并成一个RACTuple发送出去。
原理:
- (RACSignal *)zipWith:(RACSignal *)signal {
NSCParameterAssert(signal != nil);
// 创建一个新信号
return [[RACSignal createSignal:^(id subscriber) {
__block BOOL selfCompleted = NO;
NSMutableArray *selfValues = [NSMutableArray array];
__block BOOL otherCompleted = NO;
NSMutableArray *otherValues = [NSMutableArray array];
// 发送成功
void (^sendCompletedIfNecessary)(void) = ^{
@synchronized (selfValues) {
BOOL selfEmpty = (selfCompleted && selfValues.count == 0);
BOOL otherEmpty = (otherCompleted && otherValues.count == 0);
// 两个信号都发送成功则新信号发送成功
if (selfEmpty || otherEmpty) [subscriber sendCompleted];
}
};
// sendNext
void (^sendNext)(void) = ^{
@synchronized (selfValues) {
if (selfValues.count == 0) return;
if (otherValues.count == 0) return;
RACTuple *tuple = [RACTuple tupleWithObjects:selfValues[0], otherValues[0], nil];
[selfValues removeObjectAtIndex:0];
[otherValues removeObjectAtIndex:0];
// 当压缩成一个RACTuple时,会将第一次发送的数据从数组中删除
// 发送数据
[subscriber sendNext:tuple];
sendCompletedIfNecessary();
}
};
// 订阅singalA被订阅
RACDisposable *selfDisposable = [self subscribeNext:^(id x) {
@synchronized (selfValues) {
[selfValues addObject:x ?: RACTupleNil.tupleNil];
sendNext();
}
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
@synchronized (selfValues) {
selfCompleted = YES;
sendCompletedIfNecessary();
}
}];
// singalB订阅
RACDisposable *otherDisposable = [signal subscribeNext:^(id x) {
@synchronized (selfValues) {
[otherValues addObject:x ?: RACTupleNil.tupleNil];
sendNext();
}
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
@synchronized (selfValues) {
otherCompleted = YES;
sendCompletedIfNecessary();
}
}];
return [RACDisposable disposableWithBlock:^{
[selfDisposable dispose];
[otherDisposable dispose];
}];
}] setNameWithFormat:@"[%@] -zipWith: %@", self.name, signal];
}
- 创建一个新信号,singalA和singalB两个信号都会在内部被订阅。
- 当singalA发送信号,会判断singalB有没有发送信号,同理,sigbalB发送信号时会检测singalA有没有发送信号,当两个都发送了信号之后会打包成一个RACTuple。
- sendCompleted也是同理。
订阅 -subscribe
创建了signal之后,本身是不会做任何处理,必须订阅这个信号,才能为我所用。
通常情况下使用[signal subscribeNext:]
直接订阅信号,这个方法等返回值是一个RACDisposable
类型的值。也就是我们可以在外部对这个信号进行操作,比如取消订阅[disposable dispose]
。
// creat a signal
RACSignal *signal = [[[RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@"a"];
[subscriber sendCompleted];
// 取消订阅要调用这里block块
return [RACDisposable disposableWithBlock:^{
NSLog(@"disposed");
}];
}] doNext:^(id x) {
NSLog(@"do next");
}] doCompleted:^{
NSLog(@"do Completed");
}];
// subscribe do subscription
[signal subscribeNext:^(id x) {
// subscription
NSLog(@"subscriber receive value: %@", x);
} completed:^{
NSLog(@"completed end");
}];
doNext:、doCompleted、doError:
此处只用doNext:
来做说明,其它两个与此类似。
doNext:
是在执行sendNext:
之前执行的操作,就是订阅成功之后,将要发送订阅数据时会先执行此方法。
// RACSignal+Operations.m 源码
- (RACSignal *)doNext:(void (^)(id x))block {
NSCParameterAssert(block != NULL);
return [[RACSignal createSignal:^(id subscriber) {
return [self subscribeNext:^(id x) {
// 先会掉block
block(x);
// 执行订阅发送
[subscriber sendNext:x];
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
[subscriber sendCompleted];
}];
}] setNameWithFormat:@"[%@] -doNext:", self.name];
}
注意:doNext:
方法返回的事一个新的signal,这是一个新创建的信号,并且在[subscriber sendNext:x]
之前,首先回调了block(x)
。所以,如果将doNext:
单独拿出来,在之后的信号订阅过程中,是不会执行doNext:
方法。因为订阅的是最原始的信号。
看一下上面代码的打印结果,如下:
RACDemo[44640:4871628] do next
RACDemo[44640:4871628] subscriber receive value a
RACDemo[44640:4871628] do Completed
RACDemo[44640:4871628] completed end
RACSiganle先说这么多,其他的会在下一篇文章中详细解释。
RACSequence
单纯的翻译来看就是序列。那这个是用来干嘛的呢?
Represents an immutable sequence of values. Unless otherwise specified, the sequences' values are evaluated lazily on demand. Like Cocoa collections, sequences cannot contain nil.
官方是这么解释的,通俗的翻译一下就是:
RACSequence
代表的是一组不可变的序列值。除非另有说明,这个序列值在使用的时候通过懒加载的方式。跟cocoa框架中的集合一样,不能包含空值。
想到这里,就会想到Foundation中的NSArray
,NSDictionary
。不错,这里可以将NSArray
,NSDictionary
转换为RACSequence
。并且通过RACSequence
转为RACSignal
。
NSArray转换 --> RACArraySequence
NSArray *array = @[@"a", @"b", @"c"];
// 可以调用array.rac_sequence直接做转换
RACSequence *arraySeq = array.rac_sequence;
RACSignal *arraySignal = arraySeq.signal;
[arraySignal subscribeNext:^(id x) {
NSLog(@"subscriber array receive value: %@",x);
}];
源码
// NSArray+RACSequenceAdditions.m
- (RACSequence *)rac_sequence {
return [RACArraySequence sequenceWithArray:self offset:0];
}
// RACArraySequence.m
+ (instancetype)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset {
NSCParameterAssert(offset <= array.count);
if (offset == array.count) return self.empty;
RACArraySequence *seq = [[self alloc] init];
seq->_backingArray = [array copy];
seq->_offset = offset;
return seq;
}
上述源码中提到了RACArraySequence
,调用了一个类方法直接返回RACArraySequence
。
这个RACArraySequence
中有两个属性分别存储数组和偏移。这个offset
值得一提,代表的是这个Sequence
是从array
的第几个开始的。接下来就会看到。往下看
// 获取头
- (id)head {
return self.backingArray[self.offset];
}
// 获取尾部
- (RACSequence *)tail {
RACSequence *sequence = [self.class sequenceWithArray:self.backingArray offset:self.offset + 1];
sequence.name = self.name;
return sequence;
}
// 获取数组
- (NSArray *)array {
return [self.backingArray subarrayWithRange:NSMakeRange(self.offset, self.backingArray.count - self.offset)];
}
这个head返回的值是数组中第offset
个数据。返回的array是从第offset个开始的。
而tail
这个方法返回的还是一个序列,而且这个序列是从第offset+1个数据开始的,有可能回返回empty。
以上的三个方法会在RACSequence
中详细介绍。
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len;
这个方法是NSFastEnumeration
协议中的方法,不做显式调用。在这里就不去追究了。
NSDictionary转换
NSDictionary *dict = @{@"key1" : @"value1",
@"key2" : @"value2"};
RACSequence *dictSeq = dict.rac_sequence;
RACSignal *dictSignal = dictSeq.signal;
// 字典中的数据流比较特殊、是一个`RACTuple`类型,下面会讲到。
[dictSignal subscribeNext:^(RACTuple *tupe) {
NSLog(@"key = %@, value = %@", tupe.first, tupe.second);
}];
// 所有的key的序列
RACSequence *keysSeq = dict.rac_keySequence;
// 所有的value的序列
RACSequence *valuesSeq = dict.rac_valueSequence;
- (RACSequence *)rac_sequence {
NSDictionary *immutableDict = [self copy];
// TODO: First class support for dictionary sequences.
return [immutableDict.allKeys.rac_sequence map:^(id key) {
id value = immutableDict[key];
return RACTuplePack(key, value);
}];
}
源码中可以看到是通过获取dictionary所有key来重新组成一个新的序列。主要的就是一个map函数,不要着急,马上就会介绍。
RACSequence的方法
这个东西,真的是很牛x的,感慨一下。源文件就不贴了,自己看吧,一个一个介绍。
head、tail、array
// 返回第一个序列中的第一个数据
@property (nonatomic, strong, readonly) id head;
// 返回除了第一个数据之外的其他数据组成的序列
@property (nonatomic, strong, readonly) RACSequence *tail;
// 返回一个数组
@property (nonatomic, copy, readonly) NSArray *array;
这几个属性就不说了,上面已经介绍过了。
objectEnumerator
// RACSequence.h
@property (nonatomic, copy, readonly) NSEnumerator *objectEnumerator;
// RACSequence.m
- (NSEnumerator *)objectEnumerator {
RACSequenceEnumerator *enumerator = [[RACSequenceEnumerator alloc] init];
enumerator.sequence = self;
return enumerator;
}
这个objectEnumerator
其实是一个RACSequenceEnumerator,是一个遍历,来自NSEnumerator
。
-eagerSequence、-lazySequence
/// Converts a sequence into an eager sequence.
///
/// An eager sequence fully evaluates all of its values immediately. Sequences
/// derived from an eager sequence will also be eager.
///
/// Returns a new eager sequence, or the receiver if the sequence is already
/// eager.
@property (nonatomic, copy, readonly) RACSequence *eagerSequence;
/// Converts a sequence into a lazy sequence.
///
/// A lazy sequence evaluates its values on demand, as they are accessed.
/// Sequences derived from a lazy sequence will also be lazy.
///
/// Returns a new lazy sequence, or the receiver if the sequence is already lazy.
@property (nonatomic, copy, readonly) RACSequence *lazySequence;
这里需要详细解释一下。
在一开始提到RACSequence
的时候,我们就已经说过:除非另有说明,这个序列值在使用的时候通过懒加载的方式。而eagerSequence
则是lazy的对立面。
// 通过RACEagerSequence返回
- (RACSequence *)eagerSequence {
return [RACEagerSequence sequenceWithArray:self.array offset:0];
}
// 返回自己
- (RACSequence *)lazySequence {
return self;
}
我们先不看其原理是怎么样的,我们先来看个例子。原理在下一章会讲。
NSArray *array1 = @[@"a", @"b", @"c"];
NSArray *array2 = @[@1, @2, @3];
// 这里又用到了map函数(映射),马上就会讲到
RACSequence *seq1 = [array1.rac_sequence map:^id(id value) {
NSLog(@"lazy value = %@", value);
return value;
}];
RACSequence *seq2 = [array2.rac_sequence.eagerSequence map:^id(id value) {
NSLog(@"eager value = %@", value);
return value;
}];
NSArray *newArray1 = seq1.array;
NSArray *newArray2 = seq2.array;
// 打印结果
RACDemo[26685:1927997] eager value = 1
RACDemo[26685:1927997] eager value = 2
RACDemo[26685:1927997] eager value = 3
RACDemo[26685:1927997] lazy value = a
RACDemo[26685:1927997] lazy value = b
RACDemo[26685:1927997] lazy value = c
从打印结果来看,先打印的eager,然后才是lazy。在这里即使将后两行代码注释掉,也会执行eager里的NSLog。先剧透一下原理,其实就是bind
,大家可以先了解一下。
-signal、-signalWithScheduler:
- (RACSignal *)signal;
- (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler;
将RACSequence
转为RACSignal
。
RACScheduler
在合适的时候会说一下这个类,内部使用GCD实现的。
-foldLeftWithStart:reduce:、-foldRightWithStart:reduce
- (id)foldLeftWithStart:(id)start reduce:(id (^)(id accumulator, id value))reduce;
- (id)foldRightWithStart:(id)start reduce:(id (^)(id first, RACSequence *rest))reduce;
这两个方法都是遍历、给一个开始的值,一个是从左开始遍历,一个是从又开始遍历。简化方法就是:
[1,2,3] startValue
left: reduce(reduce(reduce(startValue,1),2),3)
right reduce(reduce(reduce(1,startValue),2),3)
举个吧,看的更真切。
NSArray *array1 = @[@"a", @"b", @"c"];
NSArray *array2 = @[@1, @2, @3];
id a = [array1.rac_sequence foldLeftWithStart:@"d" reduce:^id(id accumulator, id value) {
NSLog(@"accumulator = %@, value = %@", accumulator, value);
return [NSString stringWithFormat:@"%@ + %@",accumulator, value];
}];
NSLog(@"a = %@", a);
id b = [array2.rac_sequence foldRightWithStart:@(4) reduce:^id(id first, RACSequence *rest) {
NSLog(@"first = %@, rest.head = %@",first, rest.head);
return @([first integerValue] + [rest.head integerValue]);
}];
NSLog(@"b = %@", b);
// 打印结果:
RACDemo[27480:1956629] accumulator = d, value = a
RACDemo[27480:1956629] accumulator = d + a, value = b
RACDemo[27480:1956629] accumulator = d + a + b, value = c
RACDemo[27480:1956629] a = d + a + b + c
RACDemo[27480:1956629] first = 3, rest.head = 4
RACDemo[27480:1956629] first = 2, rest.head = 7
RACDemo[27480:1956629] first = 1, rest.head = 9
RACDemo[27480:1956629] b = 10
-any、-all
- (BOOL)any:(BOOL (^)(id value))block;
- (BOOL)all:(BOOL (^)(id value))block;
这两个都是测试型函数,比较简单。
- any是序列中的任一一个元素满足某个条件为true。
- all则是序列中的所有元素都满足某一条件返回true。内部调用
foldLeftWithStart:
一个一个比较。这里感觉不太好,如果有一个不满足就应该直接返回BOOL值,剩下的元素就不应该再比较了。浪费资源啊。
-objectPassingTest:
这个函数是在any:
函数中调用的。内部调用了flatmap:
。满足条件的记录下来,不满足条件的过滤掉,如果有多个数据满足条件,返回第一个。
- (id)objectPassingTest:(BOOL (^)(id value))block;
+sequenceWithHeadBlock:tailBlock:
+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock;
这个就更好理解了,这是一个类方法,通过我们之前了解到的head
和tail
大概也能知道,就是自己定义head和tail组合成一个新的RACSequence
。
RACTupleNil
在RACTuple.h
中有一个RACTupleNil
类,这个类就是为了表示一个null的元素。也就是说RACTuple
的元素中有null则用RACTupleNil
表示。
在RACTuple
中,用RACTupleNil
来表示元组中没有内容。
RACTupleNil
是一个单例,用RACTupleNil.tupleNil来表示。
不知道设计这个类的目的是什么?
RACTuple
这是一个遵循NSCoding, NSCopying, NSFastEnumeration
协议的继承自NSObject
的有序的序列。
属性
-
count
tuple中的元素个数 -
frist
tuple中第一个元素,second
/third
/fourth
/fifth
-
last
最后一个元素
这几个属性是只读,获取当前tuple中的第几个元素。当index>count时,返回nil。
在RACTuple.m
中,我们看看RACTuple
的具体实现
@interface RACTuple ()
@property (nonatomic, strong) NSArray *backingArray;
@end
这里有一个数组,用来表示元组中的元素。是一个不可变的数组
#pragma mark NSCopying
- (instancetype)copyWithZone:(NSZone *)zone {
// we're immutable, bitches!
return self;
}
上面的源代码中有一句作者的注释,说这是一个不可变的,也就是说元组是不可变的。
+tupleWithObjectsFromArray:
将数组转换成tuple,在实现中调用的是下面的方法。
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array {
return [self tupleWithObjectsFromArray:array convertNullsToNils:NO];
}
+tupleWithObjectsFromArray:convertNullsToNils:
将数组转换为tuple,如果convert
是YES
则将NSNUll
转换为RACTupleNil
RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[@(1), @(2), [NSNull null]]];
NSLog(@"%@", tuple);
RACTuple *tuple1 = [RACTuple tupleWithObjectsFromArray:@[@(1), @(2), [NSNull null]] convertNullsToNils:YES];
NSLog(@"%@", tuple1);
在这里我们发现这两个输出显示的元组中的元素是一样的。
ReactiveCocoaTest[40628:6604925] (
1,
2,
""
)
ReactiveCocoaTest[40628:6604925] (
1,
2,
""
)
那这两个到底有什么区别?
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert {
RACTuple *tuple = [[self alloc] init];
if (convert) {
NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count];
for (id object in array) {
[newArray addObject:(object == NSNull.null ? RACTupleNil.tupleNil : object)];
}
tuple.backingArray = newArray;
} else {
tuple.backingArray = [array copy];
}
return tuple;
}
从上面的源码中可以看到,当convert == YES
时,会将NSNull.null
转为RACTupleNil.tupleNil
。存储的数据发生了变化。在控制台输出时,会调用[class description]
方法。
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.allObjects];
//这里打印的是self.allObjects
}
allObjects
是一个数组,在其内部又会将RACTupleNil.tupleNil
转换为NSNull.null
,所以控制台的输出结果是一样的。
- (NSArray *)allObjects {
NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:self.backingArray.count];
for (id object in self.backingArray) {
[newArray addObject:(object == RACTupleNil.tupleNil ? NSNull.null : object)];
}
return newArray;
}
-tupleWithObjects:
类似于数组的初始化方法,将数据转为tuple
-objectAtIndex:
获取当前index
的元素,如果index > count
则返回nil。
-allObjects
获取tuple中的所有元素,组成一个数组。
-tupleByAddingObject:
类似于可变数组,将一个元素拼接在tuple的元素后面并返回一个新的tuple。
RACTupleUnpackingTrampoline
在来说说这个类,看到的时候是一脸懵逼,这个是干么的。连个注释都没有...
两个方法,一个是单例,就不说了,另一个看代码
- (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables {
NSCParameterAssert(variables != nil);
[variables enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger index, BOOL *stop) {
__strong id *ptr = (__strong id *)value.pointerValue;
*ptr = tuple[index];
}];
}
这个方法传进来两个值,一个是tuple,一个是array(注意:数组中的元素是NSValue)
遍历数组,取数组的每一个元素的指针,然后将tuple对应的值赋值给这个指针对应的值。
NSString *str1 = @"1";
NSString *str2 = @"2";
NSString *str3 = @"3";
NSArray *array = @[[NSValue valueWithPointer:&str1],
[NSValue valueWithPointer:&str2],
[NSValue valueWithPointer:&str3]];
NSLog(@"str1 = %@, str2 = %@, str3 = %@", str1, str2, str3);
RACTupleUnpackingTrampoline *tupleTramp = RACTupleUnpackingTrampoline.trampoline;
RACTuple *tuple = RACTuplePack(@"a", @"b", @"c");
[tupleTramp setObject:tuple forKeyedSubscript:array];
NSLog(@"str1 = %@, str2 = %@, str3 = %@", str1, str2, str3);
看看输出的结果:
ReactiveCocoaTest[78079:10162531] str1 = 3, str2 = 4
ReactiveCocoaTest[78079:10162531] st1 = 3, st2 = 4
其实这样的方法是没有什么实际用处的,也不会有人这么去写。但是定义了这个类,就肯定有它的实际意义。接下来我们看看RACTuple
的两个宏定义。
RACTuplePack、RACTupleUnpack
- RACTuplePack
将数据包装成一个RACTuple类型。至少有一个数据。
/// Creates a new tuple with the given values. At least one value must be given.
/// Values can be nil.
#define RACTuplePack(...) \
RACTuplePack_(__VA_ARGS__)
#define RACTuplePack_(...) \
([RACTuple tupleWithObjectsFromArray:@[ metamacro_foreach(RACTuplePack_object_or_ractuplenil,, __VA_ARGS__) ]])
#define RACTuplePack_object_or_ractuplenil(INDEX, ARG) \
(ARG) ?: RACTupleNil.tupleNil,
首先这个宏定义中使用了[RACTuple tupleWithObjectsFromArray:]
这个函数来创建一个新的tuple。
RACTuple *tuple = RACTuplePack(@1, @2);
- RACTupleUnpack
/// RACTupleUnpack(NSString *string, NSNumber *num) = [RACTuple tupleWithObjects:@"foo", @5, nil];
/// NSLog(@"string: %@", string);
/// NSLog(@"num: %@", num);
///
/// /* The above is equivalent to: */
/// RACTuple *t = [RACTuple tupleWithObjects:@"foo", @5, nil];
/// NSString *string = t[0];
/// NSNumber *num = t[1];
/// NSLog(@"string: %@", string);
/// NSLog(@"num: %@", num);
#define RACTupleUnpack(...) \
RACTupleUnpack_(__VA_ARGS__)
在源文件中已经给出了例子了,就不在举例子了。我们仔细看看这个宏是怎么搞的
#define RACTupleUnpack_(...) \
metamacro_foreach(RACTupleUnpack_decl,, __VA_ARGS__) \
\
int RACTupleUnpack_state = 0; \
\
RACTupleUnpack_after: \
; \
metamacro_foreach(RACTupleUnpack_assign,, __VA_ARGS__) \
if (RACTupleUnpack_state != 0) RACTupleUnpack_state = 2; \
\
while (RACTupleUnpack_state != 2) \
if (RACTupleUnpack_state == 1) { \
goto RACTupleUnpack_after; \
} else \
for (; RACTupleUnpack_state != 1; RACTupleUnpack_state = 1) \
[RACTupleUnpackingTrampoline trampoline][ @[ metamacro_foreach(RACTupleUnpack_value,, __VA_ARGS__) ] ]
一大堆的代码,如果一级一级的去看这个宏中的内容,会发现很复杂,但是我们看到了最后一行代码,上面提到的类在这里是有用处的。
我们写个例子,对这个宏进行编译一下
RACTupleUnpack(NSString *st1, NSString *st2) = [RACTuple tupleWithObjects:@"3", @"4", nil];
这个代码的形式跟Swift的元组很像。
let (intA, intB) = (1, 2)
接下来,我们对这段代码进行编译:
__attribute__((objc_ownership(strong))) id RACTupleUnpack79_var0;
__attribute__((objc_ownership(strong))) id RACTupleUnpack79_var1;
int RACTupleUnpack_state79 = 0; RACTupleUnpack_after79: ;
__attribute__((objc_ownership(strong))) NSString *st1 = RACTupleUnpack79_var0;
__attribute__((objc_ownership(strong))) NSString *st2 = RACTupleUnpack79_var1;
if (RACTupleUnpack_state79 != 0)
RACTupleUnpack_state79 = 2;
while (RACTupleUnpack_state79 != 2)
if (RACTupleUnpack_state79 == 1) {
goto RACTupleUnpack_after79;
}
else
for (; RACTupleUnpack_state79 != 1; RACTupleUnpack_state79 = 1)
[RACTupleUnpackingTrampoline trampoline][ @[ [NSValue valueWithPointer:&RACTupleUnpack79_var0], [NSValue valueWithPointer:&RACTupleUnpack79_var1], ] ] = [RACTuple tupleWithObjects:@"3", @"4", ((void *)0)];
看到这一坨代码,到最后是不是感觉瞬间懂了。
通过[NSValue valueWithPointer:]
获取指针放在数组中。然后通过赋值的方式将数据分别传给两个指针。
总结:聊一聊Push-Driven、Pull-Driven
RACSignal和RACSequence都是继承自RACStream。信号是Push-Driven的信号流,序列是Pull-Driven的序列流。
Push-driven means that values for the signal are not defined at the moment of signal creation and may become available at a later time (for example, as a result from network request, or any user input).
Push-driven : 在创建信号的时候,信号不会被立即赋值,之后才会被赋值(举个栗子:网络请求回来的结果或者是任意的用户输入的结果)。
Pull-driven means that values in the sequence are defined at the moment of signal creation and we can query values from the stream one-by-one.
Pull-driven : 在创建信号的同时序列中的值就会被确定下来,我们可以从流中一个个地查询值。
RACSequence <=> RACSignal
RACSequence也可以转换为RACSignal,转换时需要把sequence里面的值放到一个RACScgeduler中,按照顺序把值全部push到新创建的RACSignal中。
RACSignal也可以转换为RACSequence,转换时需要收集所有发送到这个signal的值,直到complete完成。会用到[signal toArray]
这个方法,这个方法会阻塞当前线程。
RACSequence:是Pull-Driven。除了特别声明(eager)都是懒加载。在创建时,序列中的值就已经确定。当被订阅时会直接返还给订阅者。当转换为signal后,会将数据按照顺序全部push到信号。
RACSignal:是Push-Driven。signal是有订阅者的存在才有意义(一般的信号都是冷信号,被创建之后,只有订阅才会被激活)。signal发送的不仅仅是数据,还包括状态(error,complete)。
下一节来重点说说bind:
等。
参考链接:
iOS的函数响应型编程
ReactiveCocoa模式--Signal
Reactive Cocoa Tutorial [3] = RACSignal的巧克力工厂
谢谢大家,有写的不对的地方,欢迎指正,共同进步。