[iOS] RAC入门小教程

这个topic是很久以来多想写的,但是一直没有被迫写,所以这次尝试写一下~

RAC 指的就是 RactiveCocoa ,是 Github 的一个开源框架,能够通过信号提供大量方便的事件处理方案,让我们更简单粗暴地去处理事件,现在分为 ReactiveObjC(OC) 和 ReactiveSwift(swift)。

所以使用的话就只要:

pod 'ReactiveObjC'
RAC组成

RAC可以处理事件的监听,接管了苹果所有的事件机制(addTarget,代理,通知,KVO),是一套重量级响应式函数式编程开源框架,它可以帮助我们简单的处理事件。它具有高聚合低耦合的特性。但需要注意Block中的循环引用问题

这里引入了两个问题:什么是响应式 & 什么是函数式?


什么是函数式?

函数式的一个典型是swift那种,那么它的定义是什么呢?

Functional programming is a programming paradigm
1.treats computation as the evaluation of mathematical functions
2.avoids changing-state and mutable data
by wikipedia

从它的定义也可以知道函数式的主要特点:

  • 没有使用外部变量,只是使用了传入的参数,类似数学中的函数,真正的是函数的输入映射到输出,只要固定输入,那么每次执行函数的结果都是一样的。
    (所以它不怕多线程执行)
diff between OOP and functional programming

那么追求纯函数的理由是什么呢:

  • 可缓存性
    var squareNumber = memoize(function(x){ return x*x; });
    squareNumber(4);//=> 16
    squareNumber(4); // 从缓存中读取输入值为 4 的结果
  • 可移植性/自文档化
    纯函数是完全自给自足的,它需要的所有东西都能轻易获得。自给自足:不使用外部数据或者函数,所有函数内部所需要的外部数据都以参数的形式传入。
  • 可测试性:
    相同输入得到相同的输出
  • 合理性(引用透明性):
    使用一种叫做“等式推导”(equational reasoning)的技术来分析代码。所谓“等式推导”就是“一对一”替换,有点像在不考虑程序性执行的怪异行为(quirks of programmatic evaluation)的情况下,手动执行相关代码。
  • 并行代码:
    可以并行运行任意纯函数。因为纯函数根本不需要访问共享的内存,而且根据其定义,纯函数也不会因副作用而进入竞争态

函数是一等公民(first-class citizens)。层级越高,权力越大。很多时候高层的人有权利做的事,下面的人就不能做。

程序世界里,有且仅有这么几种权力: 创建,赋值(赋值给变量),传递(当作参数传递)

不同的编程语言,函数的权利会不一样。

  • 在java语言中,创建/赋值/传递,这些权利object 都具备,function 都不具备。对象可以通过参数传递到另一个对象里,从而两个对象可以互相通信。函数却不行,两个函数想要通信,必须以对象为介质。
  • 在js中,函数可以创建/赋值/传递,所以在js中函数是一等公民。函数是 javascript 的主要工作单元。

Programming Paradigm 编程范式是什么?

编程范式(Programming Paradigm)是某种编程语言典型的编程风格或者说是编程方式。一种范式可以在不同的语言中实现,一种语言也可以同时支持多种范式。例如 JavaScript 就是一种多范式的语言。

(以下非原创借鉴写的很好的人哒)

  • 几种常用的编程范式:
  1. 过程化(命令式)编程:过程化编程,也被称为命令式编程,应该是最原始的、也是我们最熟悉的一种传统的编程方式。从本质上讲,它是“冯.诺伊曼机“运行机制的抽象,它的编程思维方式源于计算机指令的顺序排列。我们常常写的也就是这种命令式编程。

  2. 事件驱动编程:基于事件驱动的程序设计在图形用户界面(GUI)出现很久前就已经被应用于程序设计中,可是只有当图形用户界面广泛流行时,它才逐渐形演变为一种广泛使用的程序设计模式。

  3. 面向对象编程:过程化范式要求程序员用按部就班的算法看待每个问题。很显然,并不是每个问题都适合这种过程化的思维方式。这也就导致了其它程序设计范式出现,包括我们现在介绍的面向对象的程序设计范式。面向对象的程序设计包括了三个基本概念:封装性、继承性、多态性。面向对象的程序语言通过类、方法、对象和消息传递,来支持面向对象的程序设计范式。

  4. 函数式编程:比起过程化编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。

举个例子:

// 命令式
function mysteryFn (nums) {
  let squares = []
  let sum = 0                           // 1. 创建中间变量

  for (let i = 0; i < nums.length; i++) {
    squares.push(nums[i] * nums[i])     // 2. 循环计算平方
  }

  for (let i = 0; i < squares.length; i++) {
    sum += squares[i]                   // 3. 循环累加
  }

  return sum
}

// 以上代码都是 how 而不是 what...
// 函数式
const mysteryFn = (nums) => nums
  .map(x => x * x)                      // a. 平方
  .reduce((acc, cur) => acc + cur, 0)   // b. 累加

响应式

什么是响应式?这个问题其实之前有篇里面我也讲过,就是如果正常我们写一行代码a = b + c,此时如果b和c都是1,那么这个时候a得到的就是2,然后如果在这行之后b变为了2,你需要重新执行a = b + c才能让a更新为3,但是响应式就是当b变为2的时候c自动就可以变成3。


RAC使用

强推:https://www.jianshu.com/p/35a28cf0a22f

终于到了真正的使用啦,每个类都大概看一下怎么用吧~ RAC有现成的KVO以及UI之类的方法,可以直接用例如:

[[button1 rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
  NSLog(@"button1 clicked");
}];
1. RACSignal

高阶用法欢迎参考:https://www.jianshu.com/p/7620edadcf88

- (void)testSignal {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        NSLog(@"block调用时刻:每当有订阅者订阅信号,就会调用block。");
        
        // 2.发送信号
//        [subscriber sendNext:@1];
        
        // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
        [subscriber sendError:nil];
        
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"信号被销毁");
        }];
    }];
    
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"signal touched 1");
    }];
    
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"signal touched 2");
    }];
}

上面酱紫如果只是设置了订阅block,外加signal在函数结束的时候没有引用了就会自动销毁,于是输出为:

2020-08-21 23:31:04.311701+0800 Example1[98032:2252660] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-21 23:31:04.311879+0800 Example1[98032:2252660] 信号被销毁
2020-08-21 23:31:04.312061+0800 Example1[98032:2252660] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-21 23:31:04.312176+0800 Example1[98032:2252660] 信号被销毁

这里的信号被销毁每次subscribe的时候都会调用,其实也就是signal可能对应多个订阅者,默认每个订阅者加入的时候给signal一个机会让它在block里面给订阅者发消息,如果它发了,订阅者的next block就会执行,但无论发了没有,默认都是给signal处理机会以后就会执行RACDisposable dispose解除已经有过接手信号机会的订阅者。

现在打开[subscriber sendNext:@1];的注释,那么由于调用了sendNext,订阅者的订阅block就会被调用,所以signal会被touch~

2020-08-21 23:34:32.705363+0800 Example1[98144:2256354] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-21 23:34:32.705608+0800 Example1[98144:2256354] signal touched 1
2020-08-21 23:34:32.705818+0800 Example1[98144:2256354] 信号被销毁
2020-08-21 23:34:32.706005+0800 Example1[98144:2256354] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-21 23:34:32.706165+0800 Example1[98144:2256354] signal touched 2
2020-08-21 23:34:32.706355+0800 Example1[98144:2256354] 信号被销毁

原理可以参考上面的系列文章里面的第一个,主要是酱紫的:


signal原理

每个subscriber都遵循RACSubscriber协议,其实有4个方法:

- (void)sendNext:(nullable id)value;
- (void)sendError:(nullable NSError *)error;
- (void)sendCompleted;
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;

error和complete其实对应着我们在订阅的时候传入的block,如果我们send error了,那么error的block就会执行,complete也是:

RACDisposable *dispose = [signal subscribeNext:^(id  _Nullable x) {
    NSLog(@"signal touched 1");
} error:^(NSError * _Nullable error) {
    NSLog(@"signal error");
} completed:^{
    NSLog(@"signal completed");
}];

2. RACDisposable

这个就是听起来就知道是做什么系列了,其实是订阅者销毁的时候回触发的block,RACDisposable就是对block的一层包装:

- (instancetype)initWithBlock:(void (^)(void))block {
    NSCParameterAssert(block != nil);

    self = [super init];

    _disposeBlock = (void *)CFBridgingRetain([block copy]); 
    OSMemoryBarrier();

    return self;
}

订阅者取消订阅有两种可能性都会触发:

  1. 订阅者destroy没有强引用了
  2. 手动调用了[disposable dispose]

第一种其实就是为什么我们的signal每次被订阅都会销毁一次,我们如果用一个数组持有一下订阅者,那么就不会触发销毁啦:

- (void)testSignal {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        NSLog(@"block调用时刻:每当有订阅者订阅信号,就会调用block。");
        
        // 2.发送信号
        [subscriber sendNext:@1];
        
        [self->subscribers addObject:subscriber];
        
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"信号被销毁");
        }];
    }];
    
    RACDisposable *dispose = [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"signal touched 1");
    } error:^(NSError * _Nullable error) {
        NSLog(@"signal error");
    } completed:^{
        NSLog(@"signal completed");
    }];
//    [dispose dispose];
    
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"signal touched 2");
    }];
}

输出:
2020-08-22 12:46:15.780694+0800 Example1[3545:41194] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-22 12:46:15.780933+0800 Example1[3545:41194] signal touched 1
2020-08-22 12:46:15.781115+0800 Example1[3545:41194] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-22 12:46:15.781244+0800 Example1[3545:41194] signal touched 2

这个时候就发现dispose没有再调用了~ 那么如果我想让他不通过释放订阅者解除订阅要怎么做呢?打开上面注释的[dispose dispose];即可手动释放订阅者,输出将变为:

2020-08-22 12:48:55.519454+0800 Example1[3626:43490] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-22 12:48:55.519770+0800 Example1[3626:43490] signal touched 1
2020-08-22 12:48:55.519971+0800 Example1[3626:43490] 信号被销毁
2020-08-22 12:48:55.520135+0800 Example1[3626:43490] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-22 12:48:55.520283+0800 Example1[3626:43490] signal touched 2

注意哦,如果我们sendNext以后调用了sendComplete,那么其实sendComplete里面会给你调用dispose方法解除订阅哒。


3. RACSubject

这个RACSubject其实是继承自RACSignal的,它更像我们以为的信号,就是当 send next 触发的时候,所有订阅者的next block都会被触发:

- (void)testSubject {
    RACSubject *subject = [RACSubject subject];
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"next1 :%@", x);
    }];
    
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"next2 :%@", x);
    }];
    
    NSLog(@"testSubject");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [subject sendNext:@"x"];
    });
}

输出:
2020-08-22 12:52:43.338328+0800 Example1[3713:45879] testSubject
2020-08-22 12:52:46.338972+0800 Example1[3713:45879] next1 :x
2020-08-22 12:52:46.339484+0800 Example1[3713:45879] next2 :x

原理其实就是它循环了一下所有的订阅者:

- (void)sendNext:(id)value {
    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendNext:value];
    }];
}

那么如果我想dispose一下肿么破呢?那就要看subscriber协议里面最后一个方法了:

- (void)testSubject {
    RACSubject *subject = [RACSubject subject];
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"next1 :%@", x);
    }];
    
    [subject didSubscribeWithDisposable:[RACCompoundDisposable disposableWithBlock:^{
        NSLog(@"disposable block1");
    }]];

    [subject didSubscribeWithDisposable:[RACCompoundDisposable disposableWithBlock:^{
        NSLog(@"disposable block2");
    }]];
    
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"next2 :%@", x);
    }];
    
    NSLog(@"testSubject");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [subject sendNext:@"x"];
    });
}

输出:
2020-08-22 12:53:56.902050+0800 Example1[4259:50201] testSubject
2020-08-22 12:53:59.902840+0800 Example1[4259:50201] next1 :x
2020-08-22 12:53:59.903351+0800 Example1[4259:50201] next2 :x
2020-08-22 12:53:59.903802+0800 Example1[4259:50201] disposable block1
2020-08-22 12:53:59.904165+0800 Example1[4259:50201] disposable block2

也就是在signal销毁的时候会触发我们加入的disposable们,并且是按照加入的顺序触发哒。


4. RACReplaySubject

这个其实和subject很类似,区别就是这个是可以先发信号再订阅,也就是新的订阅者订阅的时候,可以拿到之前发过的所有历史信息:

- (void)testReplaySubject {
    RACReplaySubject *subject = [RACReplaySubject subject];
    [subject sendNext:@"hhh1"];
    [subject sendNext:@"hhh2"];
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"value received: %@", x);
    }];
    NSLog(@"finished");
}

输出:
2020-08-22 12:57:29.315784+0800 Example1[4367:53504] value received: hhh1
2020-08-22 12:57:29.316010+0800 Example1[4367:53504] value received: hhh2
2020-08-22 12:57:29.316146+0800 Example1[4367:53504] finished

这个原理其实就是在sendNext的时候把数据存到了一个数组里面,然后订阅者订阅的时候会先把这个数组遍历一次:

- (void)sendNext:(id)value {
    @synchronized (self) {
        [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
        
        if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
            [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
        }
        
        [super sendNext:value];
    }
}

- (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)];
            }

            ……
        }
    }];

    [compoundDisposable addDisposable:schedulingDisposable];

    return compoundDisposable;
}

5. RACScheduler

Scheduler其实是对GCD的一个封装,主要是做调度的:

- (void)testScheduler {
    NSLog(@"started");
    [[RACScheduler scheduler] schedule:^{
        NSLog(@"当前线程:%@",[NSThread currentThread]);
    }];
    NSLog(@"finished");
}

输出:
2020-08-22 13:02:05.910901+0800 Example1[4478:57139] started
2020-08-22 13:02:05.911190+0800 Example1[4478:57139] finished
2020-08-22 13:02:05.911413+0800 Example1[4478:57289] 当前线程:<NSThread: 0x600001200d80>{number = 3, name = (null)}

RACScheduler是一个父类,他有很多子类:


RACScheduler的子类

我们用到的直接其实是:

+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name {
    return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)];
}

@interface RACTargetQueueScheduler : RACQueueScheduler

看RACQueueScheduler的名字也可以知道这个scheduler其实是一个依赖于queue执行的:

- (RACDisposable *)schedule:(void (^)(void))block {
    NSCParameterAssert(block != NULL);

    RACDisposable *disposable = [[RACDisposable alloc] init];

    dispatch_async(self.queue, ^{
        if (disposable.disposed) return;
        [self performAsCurrentScheduler:block];
    });

    return disposable;
}

也就是说,它的执行顺序依赖于我们传入的是并行还是串行queue。

我们通过[RACScheduler scheduler]创建的其实传入的是global queue,也就是一个并行的队列,下面来尝试一下:

- (void)testScheduler {
    NSLog(@"started");
    [[RACScheduler scheduler] schedule:^{
        NSLog(@"当前线程1:%@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"当前线程1:%@",[NSThread currentThread]);
    }];
    
    [[RACScheduler scheduler] schedule:^{
        NSLog(@"当前线程2:%@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"当前线程2:%@",[NSThread currentThread]);
    }];
    
    NSLog(@"finished");
}

输出:
2020-08-22 13:09:57.205527+0800 Example1[4663:63879] started
2020-08-22 13:09:57.205864+0800 Example1[4663:63879] finished
2020-08-22 13:09:57.206289+0800 Example1[4663:63989] 当前线程1:<NSThread: 0x6000035a4640>{number = 4, name = (null)}
2020-08-22 13:09:57.206331+0800 Example1[4663:63988] 当前线程2:<NSThread: 0x6000035f07c0>{number = 5, name = (null)}
2020-08-22 13:10:00.207777+0800 Example1[4663:63989] 当前线程1:<NSThread: 0x6000035a4640>{number = 4, name = (null)}
2020-08-22 13:10:00.207825+0800 Example1[4663:63988] 当前线程2:<NSThread: 0x6000035f07c0>{number = 5, name = (null)}

RACScheduler还有其他的获取方式:

  • immediateScheduler,立即执行的线程,其实就是在当前线程执行的
  • mainThreadScheduler,获取主线程调度器
  • currentScheduler,就是获取当前线程调度器,但不会立即执行
- (void)testScheduler {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"当前线程:%@",[NSThread currentThread]);
        
        NSLog(@"started");
        [[RACScheduler immediateScheduler] schedule:^{
            NSLog(@"当前线程2:%@",[NSThread currentThread]);
            sleep(3);
            NSLog(@"当前线程2:%@",[NSThread currentThread]);
        }];
        
        NSLog(@"finished");
    });
}

输出:
2020-08-22 21:26:46.562790+0800 Example1[7651:88680] started
2020-08-22 21:26:46.563918+0800 Example1[7651:88742] 当前线程:<NSThread: 0x6000038905c0>{number = 7, name = (null)}
2020-08-22 21:26:46.564143+0800 Example1[7651:88742] started
2020-08-22 21:26:46.564534+0800 Example1[7651:88742] 当前线程2:<NSThread: 0x6000038905c0>{number = 7, name = (null)}
2020-08-22 21:26:49.569298+0800 Example1[7651:88742] 当前线程2:<NSThread: 0x6000038905c0>{number = 7, name = (null)}
2020-08-22 21:26:49.569719+0800 Example1[7651:88742] finished

如果换成mainThreadScheduler输出就会是酱紫,不再是同步调用啦:

2020-08-22 21:29:35.556247+0800 Example1[7754:91485] 当前线程:<NSThread: 0x600000b249c0>{number = 4, name = (null)}
2020-08-22 21:29:35.556581+0800 Example1[7754:91485] started
2020-08-22 21:29:35.556826+0800 Example1[7754:91485] finished
2020-08-22 21:29:35.579004+0800 Example1[7754:91405] 当前线程2:<NSThread: 0x600000b61080>{number = 1, name = main}
2020-08-22 21:29:38.579679+0800 Example1[7754:91405] 当前线程2:<NSThread: 0x600000b61080>{number = 1, name = main}

如果换成currentScheduler是酱紫的,按理说也是异步的,但实际输出是酱紫的:

2020-08-22 21:31:23.592051+0800 Example1[7841:93828] 当前线程:<NSThread: 0x60000340dfc0>{number = 5, name = (null)}
2020-08-22 21:31:23.592302+0800 Example1[7841:93828] started
2020-08-22 21:31:23.592497+0800 Example1[7841:93828] finished

为啥没执行schedule的内容呢?

+ (RACScheduler *)currentScheduler {
    RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey];
    if (scheduler != nil) return scheduler;
    if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;

    return nil;
}

如果当前线程之前没有创建scheduler并且又不是主线程,那么就会return一个nil,自然就不会执行啦。

  • scheduler还可以通过优先级来创建,例如酱紫:
- (void)testScheduler {
    [[RACScheduler schedulerWithPriority:RACSchedulerPriorityLow] schedule:^{
        NSLog(@"scheduler:%@",[RACScheduler currentScheduler]);
        NSLog(@"当前线程:%@",[NSThread currentThread]);
    }];
}

输出:
2020-08-22 21:35:24.544379+0800 Example1[8014:98226] scheduler:<RACTargetQueueScheduler: 0x600001476a20> org.reactivecocoa.ReactiveObjC.RACScheduler.backgroundScheduler
2020-08-22 21:35:24.544993+0800 Example1[8014:98226] 当前线程:<NSThread: 0x600000167900>{number = 5, name = (null)}

RAC优先级其实是对应GCD的优先级的:

typedef enum : long {
    RACSchedulerPriorityHigh = DISPATCH_QUEUE_PRIORITY_HIGH,
    RACSchedulerPriorityDefault = DISPATCH_QUEUE_PRIORITY_DEFAULT,
    RACSchedulerPriorityLow = DISPATCH_QUEUE_PRIORITY_LOW,
    RACSchedulerPriorityBackground = DISPATCH_QUEUE_PRIORITY_BACKGROUND,
} RACSchedulerPriority;

不同优先级其实就是对应了gcd不同的queue,注意这里默认是global的queue哈:

+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name {
    return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)];
}

Finally,为什么RAC要封装GCD呢?这个其实我感觉是为了更好用和好看,dispatch很多缩进,但是RAC如果可以直接delivery就会好看很多~ 希望有大佬来教学一下这个问题~


6. RACMulticastConnection

如果我们通过signal做网络请求可以酱紫:

- (void)testConn {
    RACSignal * signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"发送网络请求");
        sleep(3);
        [subscriber sendNext:@"得到网络请求数据"];
        
        return nil;
    }];
    
    [signal subscribeNext:^(id x) {
        NSLog(@"1 - %@",x);
    }];
    
    [signal subscribeNext:^(id x) {
        NSLog(@"2 - %@",x);
    }];
    
    [signal subscribeNext:^(id x) {
        NSLog(@"3 - %@",x);
    }];
}

输出:
2020-08-22 21:46:45.095715+0800 Example1[8973:108249] 发送网络请求
2020-08-22 21:46:48.097432+0800 Example1[8973:108249] 1 - 得到网络请求数据
2020-08-22 21:46:48.098200+0800 Example1[8973:108249] 发送网络请求
2020-08-22 21:46:51.099029+0800 Example1[8973:108249] 2 - 得到网络请求数据
2020-08-22 21:46:51.099650+0800 Example1[8973:108249] 发送网络请求
2020-08-22 21:46:54.101352+0800 Example1[8973:108249] 3 - 得到网络请求数据

就可能会重复触发,RACMulticastConnection就是用于避免这个事儿的:

- (void)testConn {
    RACSignal * signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"发送网络请求");
        sleep(3);
        
        [subscriber sendNext:@"得到网络请求数据"];
        
        return nil;
    }];
    
    RACMulticastConnection *connect = [signal publish];
    
    [connect.signal subscribeNext:^(id x) {
        NSLog(@"1 - %@",x);
    }];
    
    [connect.signal subscribeNext:^(id x) {
        NSLog(@"2 - %@",x);
    }];
    
    [connect.signal subscribeNext:^(id x) {
        NSLog(@"3 - %@",x);
    }];
    
    [connect connect];
}

输出:

2020-08-22 21:57:56.231520+0800 Example1[9697:117688] 发送网络请求
2020-08-22 21:57:59.232046+0800 Example1[9697:117688] 1 - 得到网络请求数据
2020-08-22 21:57:59.232525+0800 Example1[9697:117688] 2 - 得到网络请求数据
2020-08-22 21:57:59.232854+0800 Example1[9697:117688] 3 - 得到网络请求数据

它的大概原理是酱紫的,感兴趣的可以看之前的参考:


conn原理

其实就是conn的signal其实是一个subject,而非之前的RACSignal,所以可以订阅好几个。注意哦,signal订阅的时候是顺序执行的,需要先执行创建时候每次订阅触发的block,之后才能走下一个订阅;而subject比较正常,是可以先订阅的,然后一起等待sendNext的触发


7. RACCommand

这个东西其实我也一直很迷茫是干啥用的~ 参考了:https://www.jianshu.com/p/dc472c644e7b

一般情况下,RACCommand主要用来封装一些请求、事件等,举个例子,我们的tableView在下拉滚动时若想刷新数据需要向接口提供页码或者最后一个数据的ID,我们可以把请求封装进RACCommand里,想要获取数据的时候只要将页码或者ID传入RACCommand里就可以了,同时监控RACCommand何时完成,若完成后将数据加入到tableview的数组就可以了,这是一个平常用的比较多的场景。使用是主要有三个注意点:

  1. RACCommand必须返回信号,信号可以为空
  2. RACCommand必须强引用
  3. RACCommand发送完数据必须发送完成信号

例如酱紫:

-(void)loadInfo{

      //input就是控制器中,viewmodel执行command时excute传入的参数
      RACCommand * command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {

          //command必须有信号返回值,如果没有的话可以为[RACSignal empty]
          return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber)
                  {
                      NSMutableDictionary * params = [NSMutableDictionary dictionary];

                      params[@"build"] = @"3360";
                      params[@"channel"] = @"appstore";
                      params[@"plat"] = @"2";

                      [FYRequestTool GET:@"http://app.bilibili.com/x/banner" parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

                          [subscriber sendNext:responseObject];

                          //发送完信号必须发送完成信号,否则无法执行
                          [subscriber sendCompleted];

                      } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

                          [subscriber sendError:error];

                      }];

                  return [RACDisposable disposableWithBlock:^{

                          [FYRequestTool cancel];

                          NSLog(@"这里面可以写取消请求,完成信号后请求会取消");

                          }];
                  }];
          }];

      //必须强引用这个command,否则无法执行
      self.command = command;
  }

signal如果我们在订阅的block里面去做网络请求,是无法带入一些参数给它的,但是command可以通过execute的参数做区分,然后你可以返回一个signal,在signal内部通过不同input做不同的事情,最后sendNext一下触发订阅者的block告知订阅者并返回拿到的数据:

- (void)testCommand {
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        NSLog(@"create command 1 with input:%@", input);
        sleep(3);
        NSLog(@"create command 2");
        
        return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            // 这里可以拿input去做不同的事情
            NSLog(@"signal subscribed 1 with input:%@", input);
            sleep(3);
            [subscriber sendNext:@"a"];
            NSLog(@"signal subscribed 2");
            
            return [RACDisposable disposableWithBlock:^{
                NSLog(@"RACDisposable disposable block");
            }];
        }];
    }];
    
    NSLog(@"testCommand");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        NSString *input = @"执行";
        
        [[command.executionSignals switchToLatest] subscribeNext:^(id  _Nullable x) {
            NSLog(@"switchToLatest-->%@",x);
        }];
        
        [command execute:input];
    });
}

输出:
2020-08-22 22:25:33.442545+0800 Example1[10798:139387] testCommand
2020-08-22 22:25:36.443640+0800 Example1[10798:139387] create command 1 with input:执行
2020-08-22 22:25:39.445241+0800 Example1[10798:139387] create command 2
2020-08-22 22:25:39.447601+0800 Example1[10798:139387] executing-->0
2020-08-22 22:25:39.448927+0800 Example1[10798:139387] executing-->1
2020-08-22 22:25:39.449554+0800 Example1[10798:139387] signal subscribed 1 with input:执行
2020-08-22 22:25:42.450867+0800 Example1[10798:139387] switchToLatest-->a
2020-08-22 22:25:42.451391+0800 Example1[10798:139387] signal subscribed 2
2020-08-22 22:25:42.451810+0800 Example1[10798:139387] RACDisposable disposable block

8. bind

bind其实是RACStream的方法,但是很多类都复写了它,例如signal的:

- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block {
    NSCParameterAssert(block != NULL);

    /*
     * -bind: should:
     * 
     * 1. Subscribe to the original signal of values.
     * 2. Any time the original signal sends a value, transform it using the binding block.
     * 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received.
     * 4. If the binding block asks the bind to terminate, complete the _original_ signal.
     * 5. When _all_ signals complete, send completed to the subscriber.
     * 
     * If any signal sends an error at any point, send that to the subscriber.
     */

    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACSignalBindBlock bindingBlock = block();

        __block volatile int32_t signalCount = 1;   // indicates self

        RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

        void (^completeSignal)(RACDisposable *) = ^(RACDisposable *finishedDisposable) {
            if (OSAtomicDecrement32Barrier(&signalCount) == 0) {
                [subscriber sendCompleted];
                [compoundDisposable dispose];
            } else {
                [compoundDisposable removeDisposable:finishedDisposable];
            }
        };

        void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
            OSAtomicIncrement32Barrier(&signalCount);

            RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
            [compoundDisposable addDisposable:selfDisposable];

            RACDisposable *disposable = [signal subscribeNext:^(id x) {
                [subscriber sendNext:x];
            } error:^(NSError *error) {
                [compoundDisposable dispose];
                [subscriber sendError:error];
            } completed:^{
                @autoreleasepool {
                    completeSignal(selfDisposable);
                }
            }];

            selfDisposable.disposable = disposable;
        };

        @autoreleasepool {
            RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
            [compoundDisposable addDisposable:selfDisposable];

            RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
                // Manually check disposal to handle synchronous errors.
                if (compoundDisposable.disposed) return;

                BOOL stop = NO;
                id signal = bindingBlock(x, &stop);

                @autoreleasepool {
                    if (signal != nil) addSignal(signal);
                    if (signal == nil || stop) {
                        [selfDisposable dispose];
                        completeSignal(selfDisposable);
                    }
                }
            } error:^(NSError *error) {
                [compoundDisposable dispose];
                [subscriber sendError:error];
            } completed:^{
                @autoreleasepool {
                    completeSignal(selfDisposable);
                }
            }];

            selfDisposable.disposable = bindingDisposable;
        }

        return compoundDisposable;
    }] setNameWithFormat:@"[%@] -bind:", self.name];
}

其实就是创建了一个新的signal,然后在这个信号被订阅的时候会执行我们bind时候提供的block,根据我们在bind里面返回的signal是不是空来决定是要addSignal还是addComplete。

注意哦之所以后面signal被触发的时候,可以给subscriber send next主要是因为在addSignal里面有引用subscriber哦。

所以其实bind主要做的是:

正常来说我们发送信号只有信号的订阅者才会接收到消息,而bind会将信号拦截过滤后发送到新的信号订阅者中。

它的流程是酱紫的:

  • 原信号->bind(生成新信号)
  • 原信号发送消息->bind Block 进行过滤,然后发送消息到内部一个信号中-> 转发到bind时生成的新信息号中

举个例子:

- (void)testBind {
    RACSubject *subject = [RACSubject subject];
    RACSignal * signal = [subject bind:^RACSignalBindBlock _Nonnull{
        NSLog(@"bind block");
        
        return ^RACSignal *(id _Nullable value, BOOL *stop){
            NSLog(@"signal block");
            return [RACReturnSignal return:value];
        };
    }];
    
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"收到的数据 - %@",x);
    }];
    
    [subject sendNext:@"启动自毁程序"];
}

输出:
2020-08-23 08:45:52.755609+0800 Example1[2023:32254] bind block
2020-08-23 08:45:52.756066+0800 Example1[2023:32254] signal block
2020-08-23 08:45:52.756573+0800 Example1[2023:32254] 收到的数据 - 启动自毁程序

在rac里面也有很多地方用到了bind,比如flattenMap方法,其实就是bind的封装。内部就是bind方法的调用,然后可以传入一个block 作为过滤的规则。


9. RACStream

这个part可以参考:https://www.jianshu.com/p/0e4de0eeaff7 & https://www.jianshu.com/p/64ab974445dd

RACStream中的许多定义都是抽象的,没有具体实现,需要由其子类进行实现。

例如signal其实是继承自stream的:

@interface RACSignal<__covariant ValueType> : RACStream
  • empty
+ (__kindof RACStream *)empty;
因为RAC中nil会导致crash,所以很多时候需要定义一个空对象来替代nil,一般empty都被创建为一个单例来使用。
  • bind 适用于过滤
- (__kindof RACStream *)bind:(RACStreamBindBlock (^)(void))block;
懒绑定,将block返回的RACStream绑定到自身,在调用时才执行内部操作。很多操作都是基于bind的,比如flattenMap,大多时候我们都调用bind的上层方法。

例如,创建一个signal对象发送十次值,并且在其中随机发送@" "。我们要做的是过滤掉@" "并且在接收到@"5"时终止后续发送返回一个新的signal并且发送@"a",@"b":

RACSignal * signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    //发送10次值
    for (int i = 0; i < 10; i ++) {
        //随机发送 @" "
        BOOL empty = arc4random_uniform(2);
        if (!empty || i == 5) {
            [subscriber sendNext:[NSString stringWithFormat:@"%zd",i]];
        }
        else {
            [subscriber sendNext:@" "];
        }
    }
    [subscriber sendCompleted];
    return nil;
}] bind:^RACStreamBindBlock{
    return ^id (NSString * value, BOOL * stop) {
        if ([value isEqualToString:@" "]) { //过滤@" "
            return [RACSignal empty];
        }
        else if ([value isEqualToString:@"5"]){ //接收到 @"5" 终止
            * stop = YES;
            RACSignal * result = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                [subscriber sendNext:@"a"];
                [subscriber sendNext:@"b"];
                [subscriber sendCompleted];
                return nil;
            }];
            return result;
        }
        else {
            return [RACSignal return:value];
        }
        
    };
}];

[signal subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];

[signal subscribeCompleted:^{
    NSLog(@"complete");
}];
/* 某次执行结果
2018-03-27 15:14:02.334078+0800 ReactCocoaTest[91664:2403921] 1
2018-03-27 15:14:02.334390+0800 ReactCocoaTest[91664:2403921] 3
2018-03-27 15:14:02.334562+0800 ReactCocoaTest[91664:2403921] 4
2018-03-27 15:14:02.334836+0800 ReactCocoaTest[91664:2403921] a
2018-03-27 15:14:02.334957+0800 ReactCocoaTest[91664:2403921] b
2018-03-27 15:14:02.335673+0800 ReactCocoaTest[91664:2403921] complete
*/
  • return
+ (__kindof RACStream *)return:(id)value;
把一个值包装成对应的RACStream的子类型。
  • concat 适用于连续的异步任务
- (__kindof RACStream *)concat:(RACStream *)stream;
连接两个信号,子类实现具体如何连接。

例如酱紫:

RACSignal * signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"1"];
    [[RACScheduler mainThreadScheduler]afterDelay:5 schedule:^{
        [subscriber sendCompleted];
    }];
    return nil;
}];
RACSignal * signal2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"2"];
    [[RACScheduler mainThreadScheduler]afterDelay:5 schedule:^{
        [subscriber sendCompleted];
    }];
    return nil;
}];
RACSignal * concat = [signal1 concat:signal2];
[concat subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];
[concat subscribeCompleted:^{
    NSLog(@"complete");
}];
[concat subscribeError:^(NSError *error) {
    NSLog(@"error");
}];
/*
2018-03-27 16:37:18.546755+0800 ReactCocoaTest[94470:2467226] 1
2018-03-27 16:37:24.040041+0800 ReactCocoaTest[94470:2467226] 2
2018-03-27 16:37:29.513247+0800 ReactCocoaTest[94470:2467226] complete
*/
  • zipWith 适用于连接两个信号的结果
- (__kindof RACStream *)zipWith:(RACStream *)stream;
压缩两个信号,子类实现具体如何压缩。

例如酱紫:

RACSignal * signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"1"];
    [subscriber sendNext:@"2"];
    [subscriber sendNext:@"3"];
    [subscriber sendNext:@"4"];
    [subscriber sendNext:@"5"];
    [subscriber sendNext:@"6"];
    [[RACScheduler mainThreadScheduler]afterDelay:7 schedule:^{
        [subscriber sendError:nil];
    }];
    return nil;
}];
RACSignal * signal2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"a"];
    [subscriber sendNext:@"b"];
    [[RACScheduler mainThreadScheduler]afterDelay:5 schedule:^{
        [subscriber sendNext:@"d"];
        [subscriber sendCompleted];
    }];
    return nil;
}];
RACSignal * zip = [signal1 zipWith:signal2];
[zip subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];
[zip subscribeCompleted:^{
    NSLog(@"complete");
}];
[zip subscribeError:^(NSError *error) {
    NSLog(@"error");
}];
/*
2018-03-27 17:20:15.922457+0800 ReactCocoaTest[96661:2499724] <RACTuple: 0x604000206030> (
    1,
    a
)
2018-03-27 17:20:15.922787+0800 ReactCocoaTest[96661:2499724] <RACTuple: 0x604000206010> (
    2,
    b
)
2018-03-27 17:20:21.414965+0800 ReactCocoaTest[96661:2499724] <RACTuple: 0x604000207110> (
    3,
    d
)
2018-03-27 17:20:21.415268+0800 ReactCocoaTest[96661:2499724] complete
*/   

10. RACTuple

RACTuple就和swift里面的元组一样的,随便放数据,它的内部主要就是一个可以放 id 类型的数组:

- (instancetype)initWithBackingArray:(NSArray *)backingArray {
    self = [super init];
    
    _backingArray = [backingArray copy];
    
    return self;
}

使用就很简单,可以用init,也可以用提供的快捷方式:

@class RACTwoTuple<__covariant First, __covariant Second>;
@class RACThreeTuple<__covariant First, __covariant Second, __covariant Third>;
@class RACFourTuple<__covariant First, __covariant Second, __covariant Third, __covariant Fourth>;
@class RACFiveTuple<__covariant First, __covariant Second, __covariant Third, __covariant Fourth, __covariant Fifth>;

11. RACSequence

它也是继承RACStream的,所以也有各种zip、concat、bind的操作~

RAC对OC的集合和RACTuple进行Category扩充,因此可用集合.rac_sequence,把集合快速转换成RACSequence对象
订阅RACSequence的signal,可遍历所有元素,但因为内部实现是异步执行的(for in是在当前线程),所以使用时候需要注意时间顺序。

NSArray *array = @[@1, @2, @3];
    NSDictionary *dict = @{@"key1" : @"value1", @"key2" : @"value2", @"key3" : @"value3"};
    NSString *str = @"ABC";
    NSSet *set = [NSSet setWithArray:array];
    RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:array];

    //NSArray 会返回元素
    [array.rac_sequence.signal subscribeNext:^(id _Nullable x) {
        NSLog(@"array rac_sequence : %@", x);
    }];

    //NSDictionary 会返回打包成Tuple的key、value
    [dict.rac_sequence.signal subscribeNext:^(id _Nullable x) {
        NSLog(@"dict rac_sequence : %@", x);
    }];

    //NSString 会返回单个字符
    [str.rac_sequence.signal subscribeNext:^(id _Nullable x) {
        NSLog(@"str rac_sequence : %@", x);
    }];

    //NSSet 会返回元素
    [set.rac_sequence.signal subscribeNext:^(id _Nullable x) {
        NSLog(@"set rac_sequence : %@", x);
    }];

    //RACTuple 会返回内置数组的元素
    [tuple.rac_sequence.signal subscribeNext:^(id _Nullable x) {
        NSLog(@"tuple rac_sequence : %@", x);
    }];

    //以下是输出结果,从结果可以看出,结果是乱序的
    //如果再打印当前线程,会发现每种集合都在各自的一条线程上输出,但非主线程
    //array rac_sequence : 1
    //array rac_sequence : 2
    //array rac_sequence : 3
    //str rac_sequence : A
    //set rac_sequence : 3
    //str rac_sequence : B
    //tuple rac_sequence : 1
    //set rac_sequence : 2
    //dict rac_sequence : <RACTuple: 0x610000004cf0> (key1,value1)
    //tuple rac_sequence : 2
    //str rac_sequence : C
    //set rac_sequence : 1
    //dict rac_sequence : <RACTuple: 0x610000004ce0> (key3,value3)
    //tuple rac_sequence : 3
    //dict rac_sequence : <RACTuple: 0x608000004bb0> (key2,value2)

How?

RAC的信号的用法主要有什么呢?我们在MVVM或者MVC框架中都会有绑定V和 VM或者M 的操作,这个时候可以用signal来实现,比如view来订阅signal,然后VM在获取完数据以后可以sendNext把数据传给view即可。


为什么 RAC 是 响应式 & 函数式?

首先响应体现在 signal 上面,比如当我们点击了一个button,就会发给我们一个 signal ,我们可以在接受到 signal 以后做一些处理,这个就类似于我们设置在接受到 b 改变的 signal 里面设置a = b + c,那么每次 b 变化,a 都会自然的变化,不需要再手动改啦。

函数式比较隐密,因为OC里面函数是不能作为参数传递的,函数式只能体现在block上面,所以RAC里面通过block响应next就是函数式的体现了,和swift的promise里面的.then().next()之类的很类似。

References:
https://www.jianshu.com/p/5fc71f541b1c
https://www.jianshu.com/p/d90f6715f2cc
https://medium.com/@shaistha24/functional-programming-vs-object-oriented-programming-oop-which-is-better-82172e53a526
https://www.jianshu.com/p/9eee5a0b42da

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259