ReactiveCocoa学习笔记五-RACSignal源码分析

最近事情很多, 前段时间因为在排查APP中出现的一些诡异crash, 所以抽空学了一些汇编和逆向的东西, RAC这块就落下了, 虽然偶尔也在看, 但是没法系统整理, 今天算在抽出时间了, 所以把Signal部分的代码分析分析, 后续再把其它部分补上. 还是老话, 中间有错漏 , 还希望指正.

RAC的信号世界主要由以下三个核心组件组成:
RACSignal, RACSubscriber和RACDisposable. (调度器RACScheduler算是一些辅助功能, 不在核心链路中.)
Subscriber可以订阅(subscribe)Signal
Disposable可以清除(dispose)Subscriber对Signal的订阅(以及相关资源占用)

所以先从Signal开始看, 尽量屏蔽掉其它2个部分的干扰:

RACDynamicSignal

Signal的子类, crateSignal本质上就是返回这个类的实例.
功能:

  1. 创建信号, 存储订阅block
  2. 管理订阅关系
// 1. 直接新建一个对象, 把didSubscribe的block存储起来
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    RACDynamicSignal *signal = [[self alloc] init];
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
}

// 2. 返回一个实际上为RACCompoundDisposable的Disposable对象.
// RACCompoundDisposable是组合多个Disposable对象, 并且可以一次性管理的类, 一次disposable全部清除.
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    // RACPassthroughSubscriber在订阅部分再细讲, 这里按下不表
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    if (self.didSubscribe != NULL) {
        RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
           // 订阅后在这里触发self.didSubscribe的block
            RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            // 2.1 添加调度器的disposable
            [disposable addDisposable:innerDisposable];
        }];
        // 2.2 添加信号本身的disposable
        [disposable addDisposable:schedulingDisposable];
    }
    
    return disposable;
}

RACErrorSignal, RACEmptySignal, RACReturnSignal

RACErrorSignal在订阅后直接返回Error;

RACEmptySignal在订阅后直接返回Completed;

RACReturnSignal在订阅后先sendNext:returnValue,然后sendCompleted.

RACSubject

signal的子类, 是热信号(冷热信号参考译文: 框架总览和设计指导)

功能:

  1. 本身可以看做一个信号
  2. 还可以发送事件

从代码上来看, RACSubject还持有了2个"数组", 一个是subscribers, 一个则是disposable(RACCompoundDisposable类型)

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

    _disposable = [RACCompoundDisposable compoundDisposable];
    _subscribers = [[NSMutableArray alloc] initWithCapacity:1];
    
    return self;
}
- (void)dealloc {
    [self.disposable dispose];
}

所以在发送事件的时候, 也会有遍历的过程:

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

- (void)sendError:(NSError *)error {
    [self.disposable dispose];
    
    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendError:error];
    }];
}

- (void)sendCompleted {
    [self.disposable dispose];
    
    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendCompleted];
    }];
}

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

    for (id<RACSubscriber> subscriber in subscribers) {
        block(subscriber);
    }
}

重点是subscribe:方法:

- (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];
    }
    // 返回一个Disposable, 在调用Disposable时移出订阅者
    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];
        }
    }];
}

Subject还有RACReplaySubject, RACGroupedSignal和RACBehaviorSubject子类, 这里先看看其功能, 后期再补上代码分析吧.
RACReplaySubject是存储了信号sendNext的值, 然后每次订阅都自动重放这些值.
RACGroupedSignal只是给subject一个key值, 来做分组(Signal的操作有个groupBy)
RACBehaviorSubject订阅后发送最后收到的一个值

RACSignal

常规方法还有(不包括operation里面的):

concat: ,zipWith: ,startLazilyWithScheduler: (startEagerlyWithScheduler就是直接调用了Lazily然后强制立即执行), 以及最核心的bind:

concat:

concat与Sequence里面的concat类似, 都是把对象串起来.

- (RACSignal *)concat:(RACSignal *)signal {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        // RACSerialDisposable这个类型的Disposable, 会在每次赋值的时候, 判断之前的值是否已经dispose, 如果是就对之前的值调用dispose, 所以这里会体现为, 一个dispose则后续都会被dispose掉
        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];
            // 这里会对sourceDisposable调用dispose
            serialDisposable.disposable = concattedDisposable;
        }];
        
        serialDisposable.disposable = sourceDisposable;
        // 最后返回给调用方 决定concattedDisposable的归宿
        return serialDisposable;
    }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal];
}

zip:

zip同样与RACSequence里面的zip类似, 只是把直接合成Tuple改为sendNext的时候把2个信号的值合成了一个Tuple, 注意, 只有一个信号sendNext的时候, 合成的信号是不会sendNext的:
整体代码略长, 但是大部分都是"复制"的:

- (RACSignal *)zipWith:(RACSignal *)signal {
    NSCParameterAssert(signal != nil);

    return [[RACSignal createSignal:^(id<RACSubscriber> 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];
            }
        };

        // next事件必须两者都有后备值, 命名应该也需要加上IfNecessary
        void (^sendNext)(void) = ^{
            @synchronized (selfValues) {
                if (selfValues.count == 0) return;
                if (otherValues.count == 0) return;
                // 把后备值数组第一个取出来合成Tuple 发送给调用方, 然后移出之
                RACTuple *tuple = RACTuplePack(selfValues[0], otherValues[0]);
                [selfValues removeObjectAtIndex:0];
                [otherValues removeObjectAtIndex:0];
                
                [subscriber sendNext:tuple];
                // 判断是否需要完成
                sendCompletedIfNecessary();
            }
        };
        
        RACDisposable *selfDisposable = [self subscribeNext:^(id x) {
            @synchronized (selfValues) {
                [selfValues addObject:x ?: RACTupleNil.tupleNil];
                sendNext();
            }
        } error:^(NSError *error) {
            // error事件会直接传递出去
            [subscriber sendError:error];
        } completed:^{
            @synchronized (selfValues) {
                selfCompleted = YES;
                sendCompletedIfNecessary();
            }
        }];

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

        // 显然在dispose的时候是两者都需要的, 下面的代码也符合预期
        // 但是, 有个疑问是, 为什么不用CompoundDisposable???
        // 11.29更新: 两者需要同时清理, 所以不用CompoundDisposable
        return [RACDisposable disposableWithBlock:^{
            [selfDisposable dispose];
            [otherDisposable dispose];
        }];
    }] setNameWithFormat:@"[%@] -zipWith: %@", self.name, signal];
}

startLazilyWithScheduler:block:

核心在于lazily, 因此可以把Scheduler忽略掉.

+ (RACSignal *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block {
    NSCParameterAssert(scheduler != nil);
    NSCParameterAssert(block != NULL);
    
    // 虽然写在前面, 但是其实是第二部分, 先看下面return部分 再看这边
    // 创建一个RACMulticastConnection, 在调用connect的时候才会"激活", 所以是lazily的
    RACMulticastConnection *connection = [[RACSignal
        createSignal:^ id (id<RACSubscriber> subscriber) {
            // 创建一个信号, 直接调用参数的block
            block(subscriber);
            return nil;
        }]
        // 转换为replaySubject, 这种类型的subject可以重放事件
        // 之所以用这种subject是因此普通的Signal就是"重放式"的, 而一般的RACMulticastConnection则不是, 具体参考框架总览篇
        multicast:[RACReplaySubject subject]];
    
    // 返回一个信号, 在收到订阅后把订阅者传递给源信号(也就是上面创建的信号)
    return [[[RACSignal
        createSignal:^ id (id<RACSubscriber> subscriber) {
            [connection.signal subscribe:subscriber];
            // 调用connect才"激活"信号(本质上就是订阅之)
            // 所以eagerly的方法会在返回该信号后转换为RACMulticastConnection 后直接connect, 以达到eagerly的目的
            [connection connect];
            return nil;
        }]
        // 调度到指定的调度器
        subscribeOn:scheduler]
        setNameWithFormat:@"+startLazilyWithScheduler: %@ block:", scheduler];
}

bind:

bind涉及到很多的Signal Operation, 这里的内容比较丰富, 但是都可以和Sequence在概念上映射上, 而且不影响核心链路, 所以先不看了, 到时候再另开一篇专门来看好了.

RACMulticastConnection

从头文件对这个类的解释是:

A multicast connection encapsulates the idea of sharing one subscription to a
signal to many subscribers. This is most often needed if the subscription to
the underlying signal involves side-effects or shouldn't be called more than
once.

Multicast可以翻译为多播(和广播broadcast类似, 只不过multicast是有指向性的)

所以上面的话核心的意思是: 在多个订阅者间共享同一个订阅关系, 以此来使得副作用只被触发一次. 也就是说, 看起来是订阅了多次, 但是实际上只会由RACMulticastConnection对源信号订阅一次, 所有的订阅者实际上是订阅了RACMulticastConnection对象

先看看RACMulticastConnection的头文件:

// RACMulticastConnection.h
@property (nonatomic, strong, readonly) RACSignal *signal;
- (RACDisposable *)connect;
- (RACSignal *)autoconnect;
// RACMulticastConnection+Private.h
- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject;

持有了一个信号, 提供了1个私有的初始化方法和2个方法公共的connect方法, 而且看起来都是有同样作用的方法, 只是返回值类型有所区别.

直接从代码层面来看看原理:

私有属性:

@interface RACMulticastConnection () {
    RACSubject *_signal;  // 对外是一个signal 内部会转换为Subject, 因为connection既要接受其它Subscriber的订阅, 又要订阅源信号, 且保证了副作用只被触发一次
    int32_t volatile _hasConnected;  // 标记是否已经connect
}
@property (nonatomic, readonly, strong) RACSignal *sourceSignal;
// 对这里使用RACSerialDisposable而不是普通类型的Disposable应该是为了更防止内存泄露. 在调用方使用connect方法获取Disposable进行另外的复制后也可以保证资源被合理清除
@property (strong) RACSerialDisposable *serialDisposable;

提供的初始化方法没有什么特别的, 只是属性初始化而已, 所以直接看connect和autoconnect:

- (RACDisposable *)connect {
    // 判断是否已经connect过
    BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);

    if (shouldConnect) {
        // 需要connect则直接订阅这个信号
        self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
    }
    // 返回disposable
    return self.serialDisposable;
}
- (RACSignal *)autoconnect {
    // volatile是关闭编译器的的相关优化, 每次取subscriberCount值都从其地址取
    __block volatile int32_t subscriberCount = 0;

    // 不直接订阅"源信号", 而是创建一个信号, 在有新订阅后自动进行connect
    return [[RACSignal
        createSignal:^(id<RACSubscriber> subscriber) {
            OSAtomicIncrement32Barrier(&subscriberCount);
            // 转换为对subject的订阅
            RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];
            // 触发对源信号的订阅(如果还未)
            RACDisposable *connectionDisposable = [self connect];

            return [RACDisposable disposableWithBlock:^{
                [subscriptionDisposable dispose];
                // 所有的订阅者都已经dispose后再dispose connect本身
                if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) {
                    [connectionDisposable dispose];
                }
            }];
        }]
        setNameWithFormat:@"[%@] -autoconnect", self.signal.name];
}

11.29补充:RACSubject子类们

RACReplaySubject

前面讲RACSubject的时候说了它是热信号, 它的didSubscribeBlock只会被触发一次, 后续再订阅就不会被触发了, 如果有需要仍然要触发, 那么就要用到它的子类RACReplaySubject
先看看头文件:

// 最多存储几条记录, 后面的会挤掉前面的
+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity;

很简单, 就一个返回实例的类方法来控制记录的数量, 如果不传, 默认是NSUIntegerMax这么多个, 可以认为是无限多个.

再看看私有属性:

@interface RACReplaySubject ()
// 只读属性, 初始化之后就不允许修改了
@property (nonatomic, assign, readonly) NSUInteger capacity;

// 有一个存储收到值的数组, sendNext的值都会存在这里
@property (nonatomic, strong, readonly) NSMutableArray *valuesReceived;
@property (nonatomic, assign) BOOL hasCompleted;
@property (nonatomic, assign) BOOL hasError;
@property (nonatomic, strong) NSError *error;

@end

先看看发送事件的处理吧:

- (void)sendNext:(id)value {
    @synchronized (self) {
        // 保留sendNext的值记录
        [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
        [super sendNext:value];
        // 如果有负载上线, 且已经超过负载则删除多余的内容
        // 作者选用了更加保险的措施, 一般来说应该remove掉第一个就够用了
        // 难道是为了防止调用方用KVC的形式来修改出现一些问题么???
        if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
            [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
        }
    }
}

// completed和error都是多设置了一下标志位
- (void)sendCompleted {
    @synchronized (self) {
        self.hasCompleted = YES;
        [super sendCompleted];
    }
}

- (void)sendError:(NSError *)e {
    @synchronized (self) {
        self.hasError = YES;
        self.error = e;
        [super sendError:e];
    }
}

再看看订阅的方法:

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    // 组合调度器的disposable和订阅返回的disposable
    RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
    // scheduler的代码之后再看吧, 反正就是在后台线程中执行block
    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
        @synchronized (self) {
            // 如果未dispose 把之前记录的值都发给订阅者
            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 {
                // 执行订阅block, 添加disposable
                RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
                [compoundDisposable addDisposable:subscriptionDisposable];
            }
        }
    }];

    [compoundDisposable addDisposable:schedulingDisposable];

    return compoundDisposable;
}

RACBehaviorSubject与RACSubject

两者都比较简单, 都是RACSubject的精简版, RACBehaviorSubject的是sendNext一直都只发送一个值, 直接看源码:

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    RACDisposable *subscriptionDisposable = [super subscribe:subscriber];

    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
        @synchronized (self) {
            // 订阅后立即发送该值
            [subscriber sendNext:self.currentValue];
        }
    }];
    
    // 这里没有使用CompoundDisposable, 原本我以为只是不同的人偏好不同, 但是看了下, 和上面的ReplySubject作者是同一个人, 所以之后要好好看看为什么...(问了一下作者本人, 回答是这两者没有什么本质区别)
    return [RACDisposable disposableWithBlock:^{
        [subscriptionDisposable dispose];
        [schedulingDisposable dispose];
    }];
}

- (void)sendNext:(id)value {
    @synchronized (self) {
        self.currentValue = value;
        [super sendNext:value];
    }
}

附上作者本人的回答:

TwitterWithJoshAber.png

我想仅仅是因为它(RACBehaviorSubject)太老了. RACBehaviorSubject写在RACCompoundDisposable之前, 而且它也不需要RACCompoundDisposable的任何功能, 所以也就没有必要去更新它了.

而RACGroupedSignal就更加简单了, 比Subject多加了一个key的属性作为自己的唯一标识

+ (instancetype)signalWithKey:(id<NSCopying>)key {
    RACGroupedSignal *subject = [self subject];
    subject.key = key;
    return subject;
}

等operation的时候再看它的具体作用好了.

10.2补充: RACChannel

channel 是有挺有意思的概念, 按照头文件来说, channel 可以看做是一个双向的 connection, 先看段测试代码来了解一下具体有什么作用吧:

- (void)testChannel
{
    RACChannel *channel = [[RACChannel alloc] init];
    
    [channel.leadingTerminal subscribeNext:^(id x) {
        NSLog(@"leading: %@", x);
    }];
    
    [channel.followingTerminal subscribeNext:^(id x) {
        NSLog(@"following: %@", x);
    }];
    [channel.leadingTerminal sendNext:@"1"];
    [channel.followingTerminal sendNext:@"2"];
}
// 输出为:
following: 1
leading: 2

可想而知, leading 发送的事件会传递给 following, following 发送的会传递给 leading.

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

推荐阅读更多精彩内容