iOS 路由总结大全

iOS路由介绍

iOS路由目前业内流行的有两大分类:1、基于URL或protocol的注册调度型路由 2、runtime调度型路由

其中protocol类型的路由查的资料中目前只找到了一个MGJRouter,就是蘑菇街路由,但是这个主库已经被删了,所以这个类型的就不再讨论了。

下面为大家整理了一下这两种类型路由各自的原理、优缺点、常见的第三方库等,参见下表:

路由类型 原理 优点 缺点 常见第三方库
基于URL类型的路由 借鉴前端Router和系统App内跳转的方式,通过URL来完成路由请求。
较为详细的描述,就是像前端页面那样,一个URL对应一个页面,将这些对应的关系保存到一个注册表,而路由的过程就是拿着目标页面的URL,通过保存URL和页面对应关系的注册表,去查找这个URL对应的页面。
1. 服务器可以动态的控制页面跳转,可以统一处理页面出问题之后的错误处理。
2. 可以统一三端,iOS,Android,H5 / RN / Weex 的请求方式。
1.需要在load方法中注册,影响app启动速度
URL类型路由中页面和URL的对应规则是需要注册的,并且现行的第三方库关于这类操作都是在load方法里面写,而写在load方法里面是会影响App启动速度的;
2. URL涉及硬编码且其中的参数不易维护
URL链接里面关于组件和页面的名字都是硬编码,参数也都是硬编码。而且每个URL参数字段都必须要一个文档进行维护,这个对于业务开发人员也是一个负担。而且URL短连接散落在整个App四处,维护起来比较麻烦。
3. 对象类型参数需要使用字典实现
对于传递NSObject的参数,URL是不够友好的,它最多是传递一个字典。
JLRoutes
Routable-ios
HHRouter
ALRouter
基于runtime类型的路由 主要是基于Mediator模式和Target-Action模式,中间采用了runtime来完成调用。具体描述就是:请求路由时,根据路由信息中的target和action,利用objective-C的runtime特性转化生成target实例,再利用这个target实例,调用对应的action操作,完成路由。
看完是不是不太好理解,简单打比方就是把路由目标比成月饼,action就是扣月饼的模子,target就是装模子的盒子。具体意义可以参看下面的例子。
1. 无需注册
充分的利用Runtime的特性,无需注册这一步。Target-Action方案只有存在组件依赖Mediator这一层依赖关系。在Mediator中维护针对Mediator的Category,每个category对应一个Target,Categroy中的方法对应Action场景。
2. 统一组件调用入口
Target-Action方案也统一了所有组件间调用入口。
3. 有校验保证安全
Target-Action方案也能有一定的安全保证,它对url中进行Native前缀进行验证。
1. 涉及硬编码
Target_Action在Category中将常规参数打包成字典,在Target处再把字典拆包成常规参数,这就造成了一部分的硬编码。
CTMediator

下面我再针对表中常见的第三方库的详细使用方法进行介绍:

1. 基于URL类型的路由

1.1 JLRoutes

5.5k Star

1.1.1 路由准备

1.1.1.1:
在BaseViewController和BaseTabBarController中添加设置参数方法,供子方法继承(统一处理)

- (void)setParameter(NSString*)parameter;

1.1.1.2:添加接收到路由后的处理操作(统一处理)

一般放在AppDelegate中的didFinishLaunchingWithOptions方法中进行配置;需要集中管理的话得自己去写工具类统一调度


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    ...
   
    JLRoutes *routes = [JLRoutes globalRoutes];
    [routes addRoute:@"/open/:targetVc/:parameter/:openType" handler:^BOOL(NSDictionary *parameters) {
    // 获取路由中配置的要跳转的页面、参数及跳转方式
    NSString *targetVcString = parameters[@"targetVc"];
    NSString *parameter = parameters[@"parameter"];
    NSString *openType = parameters[@"openType"];
    
    // 根据页面字符串获取页面对象
    Class targetClass = NSClassFromString(targetVcString);
    id targetVc = [[targetClass alloc] init];
    
    // 传入参数
    if ([targetVc respondsToSelector:@selector(setParameter:)]) {
            [targetVc performSelector:@selector(setParameter:) withObject:parameters[@"parameter"]];
        }
    
    // 展示VC 默认push方法
    if ([openType isEqualToString:@"present"]) {
        [[UIUtil getCurrentVC] presentViewController:targetVc animated:YES completion:nil];
    } else {
      if (viewController.navigationController!=nil) {
          [viewController.navigationController pushViewController:targetVc animated:YES];
       } else if ([UIUtil getCurrentVC].navigationController !=nil) {
          [[UIUtil getCurrentVC].navigationController pushViewController:targetVc animated:YES];
       } else {
          [[UIUtil getCurrentVC] presentViewController:targetVc animated:YES completion:nil];
      }
    }
    
    return YES; 
}];
    
  ...
  }

1.1.1.3:页面处理传参

HnxxtNewsParentViewController里继承方法处理传参(页面各自处理)

- (void)setParameter(NSString*)parameter
{
  NSData *jsonData = [parameter dataUsingEncoding:NSUTF8StringEncoding];
    NSError *err;
    NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData
                                                        options:NSJSONReadingMutableContainers
                                                          error:&err];
  self.param1 = [dict valueForKey:xxx];
}

1.1.2 路由使用

1.1.2.1:跳转到页面,无参数(页面各自处理)

NSURL *targetURL = [NSURL URLWithString:@"xxt://open/HnxxtNewsParentViewController"];
[JLRoutes routeURL:url];

1.1.2.2:跳转到页面,有参数(页面各自处理)

NSURL *targetURL = [NSURL URLWithString:[NSString stringWithFormat:@"xxt://open/HnxxtNewsParentViewController/%@",[dict JSONString]];
[JLRoutes routeURL:url];

1.1.2.2:跳转到页面,有参数,指定present方式(页面各自处理)

NSURL *targetURL = [NSURL URLWithString:[NSString stringWithFormat:@"xxt://open/HnxxtNewsParentViewController/%@/present",[dict JSONString]];
[JLRoutes routeURL:url];

1.1.3 兼容外部调用打开APP并跳转到页面(统一处理)

在AppDelegate中的 openURL 方法统一处理


- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options
{
  //url的值即为外部调用的,比如浏览器H5链接,xxt://open/HnxxtNewsParentViewController/{key:value}/push
  return [JLRoutes routeURL:url];
}


1.2 JSDVCRouter

JLRoutes的封装

参考: iOS 组件化-路由解耦思想 JLRoutes 实战篇(一)App内控制器跳转

GitHub工程

1.2.1 路由准备

1.添加VC配置到路由表

#import "JSDVCRouterConfig.h"
//跳转方式、动画等常量
NSString* const kJSDVCRouteSegue = @"JSDVCRouteSegue";//跳转方式
NSString* const kJSDVCRouteAnimated = @"JSDVCRouteAnimated";//是否需要动画
NSString* const kJSDVCRouteFromOutside = @"JSDVCRouteFromOutside";//是否来自外部跳转
//跳转方式区分:push,present
NSString* const kJSDVCRouteSeguePush = @"push";
NSString* const kJSDVCRouteSegueModal = @"present";


//类名,title,特殊标志位,是否登录标志位等,只有类名是必须的,其他都可以不配置
NSString* const kJSDVCRouteClassName = @"class";
NSString* const kJSDVCRouteClassTitle = @"title";
NSString* const kJSDVCRouteClassFlags = @"flags";
NSString* const kJSDVCRouteClassNeedLogin = @"needLogin";
// 定义vc 路由的路径
NSString* const HnxxtNewsParentViewController = @"/hnxxt/HnxxtNewsParentViewController";


@implementation JSDVCRouterConfig

//将vc路径 和vc类名等信息进行配置
+ (NSDictionary *)configMapInfo {
    
    return @{
        HnxxtNewsParentViewController: @{kJSDVCRouteClassName: @"HnxxtNewsParentViewController",
                             kJSDVCRouteClassTitle: @"消息父页面",
                             kJSDVCRouteClassFlags: @"",
                             kJSDVCRouteClassNeedLogin: @"",
        }
    };
}

@end

1.2.2 路由使用

//在vc中的跳转调用:可以在parameters中配置跳转方式,跳转动画等属性,及页面参数等
//1、跳转到页面,无参数,默认push跳转
[JSDVCRouter openURL: HnxxtNewsParentViewController];

//2、跳转到页面,带参数,默认push跳转
[JSDVCRouter openURL: HnxxtNewsParentViewController parameters:@{@"param1": @"value1"}];

//3、跳转到页面,带参数,配置跳转方式为push,展示动画等
[JSDVCRouter openURL: HnxxtNewsParentViewController parameters:@{@"param1": @"value1", kJSDVCRouteSegue: kJSDVCRouteSeguePush, kJSDVCRouteAnimated:@(YES)}];
 

1.2.3 兼容外部调用打开APP并跳转到页面(统一处理)

在AppDelegate中的 openURL 方法统一处理

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options
{
    //按照约定的url解析后,组合参数、跳转动画等信息,再调用内部路由方法:
  return [JSDVCRouter openURL: HnxxtNewsParentViewController parameters:@{kJSDVCRouteSegue: kJSDVCRouteSegueModal, @"param1": @"value1"}];
}

1.3 Routable-ios

1.8k Star

1.3.1 路由准备

1.3.1.1 在AppDelegate中的didFinishLaunchingWithOptions方法中进行路由注册及导航栏设置;需要集中管理的话得自己去写工具类统一调度

@implementation UPAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UINavigationController *nav = [[UINavigationController alloc] initWithNibName:nil bundle:nil];
    //注册:默认注册  参数可以在url里边设置,也可以用另一个注册方法的API设置参数
    [[Routable sharedRouter] map:@"/hnxxt/HnxxtNewsParentViewController/:key1" toController:[HnxxtNewsParentViewController class]];
    //另外 注册时可以通过设置UPRouterOptions来配置目标vc需要的参数,跳转方式,跳转动画等
       // 详情参看下面UPRouterOptions的初始化方法介绍
    [[Routable sharedRouter] map:@"/hnxxt/HnxxtNewsParentViewController" toController:[HnxxtNewsParentViewController class] withOptions:[UPRouterOptions routerOptions]];
    
    //可以在注册的时候设置导航栏;或者在跳转的地方,跳转前设置导航栏也行
    [[Routable sharedRouter] setNavigationController:nav];

    [self.window setRootViewController:nav];
    [self.window makeKeyAndVisible];
    
    return YES;
}

@end

//UPRouterOptions 的标准初始化方法
/**
 @param presentationStyle  目标UIViewController弹出时的风格`UIModalPresentationStyle`
 @param transitionStyle   目标UIViewController动画风格 `UIModalTransitionStyle` 
 @param defaultParams  目标UIViewController的参数,也即可以通过url配置的参数
 @param isRoot  是否设置目标vc为当前vc栈的根vc
 @param isModal 是否模态弹出 
 */
+ (instancetype)routerOptionsWithPresentationStyle: (UIModalPresentationStyle)presentationStyle
                                   transitionStyle: (UIModalTransitionStyle)transitionStyle
                                     defaultParams: (NSDictionary *)defaultParams
                                            isRoot: (BOOL)isRoot
                                           isModal: (BOOL)isModal;

1.3.1.2 在目标UIViewController中实现方法:initWithRouterParams: ,也就是如果用这个库,工程里的vc都得实现这个方法

@implementation HnxxtNewsParentViewController

// params will be non-nil
- (id)initWithRouterParams:(NSDictionary *)params {
      if ((self = [self initWithNibName:nil bundle:nil])) {
        self.param1 = [params objectForKey:@"key1"];
      }
   return self;
}

@end

1.3.2 路由使用

在需要跳转的地方调用:

NSString *url = @"/hnxxt/HnxxtNewsParentViewController/param1";
//直接跳转
[[Routable sharedRouter] open:url];
//通过api传入额外的参数,看源码这个extraParams的参数也会被在目标vc的initWithRouterParams方法中解析,并且它的
//优先级比url中参数优先级高,同名会覆盖url中设置的参数
[[Routable sharedRouter]open:url animated:animated extraParams:extraParams]];

1.3.3 兼容外部调用打开APP并跳转到页面(统一处理)

查看源码是没有提供处理外部应用打开当前app的相关路由方法,只提供了一个:openExternal:(NSString *)url 方法,该方法只是用来打开app外部应用的方法。所以对于外部打开app处理还是同上述一致:

在AppDelegate中的 openURL 方法统一处理


- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options
{
  //url的值即为外部调用的,比如浏览器H5链接,xxt://open/HnxxtNewsParentViewController/{key:value}/push
  //将url信息处理成 runtable-iOS 库合适的跳转信息进行跳转处理
  return [[Routable sharedRouter]open:url animated:animated extraParams:extraParams]];
}

1.4 HHRouter

1.6k Star

1.4.1 路由准备

1.4.1.1 在AppDelegate的didFinishLaunchingWithOptions方法中进行路由注册;统一管理也是需要自己定义工具类

     
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  //注册:参数传递直接在url中拼接,同前面的库url传参一致;没有找到对象类型参数的传参方法
  [[HHRouter shared] map:@"/hnxxt/HnxxtNewsParentViewController/:key1" toControllerClass:[HnxxtNewsParentViewController class]];
   return YES;
}

@end

1.4.2 路由使用

在需要跳转的地方调用

HnxxtNewsParentViewController *targetVc = (HnxxtNewsParentViewController*)[[HHRouter shared] matchController:@"/hnxxt/HnxxtNewsParentViewController/param1"];
//参数会被放到vc的params中, HHRouter 中对UIViewController写了个分类,给vc加了参数params,url中的参数都会放到这个
//parmas中
targetVc.key1 = targetVc.params[key1];
//跳转和工程现行跳转一致,HHRouter只是通过路由生成UIViewController对象,后续跳转需要自己搞
[self.navigationController pushViewController: targetVc animated:YES];

1.4.3 兼容外部调用打开APP并跳转到页面(统一处理)

查看源码也是没有提供处理外部应用打开当前app的相关路由方法,所以对于外部打开app处理还是同上述一致:

在AppDelegate中的 openURL 方法统一处理


- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options
{
  //url的值即为外部调用的,比如浏览器H5链接,xxt://open/HnxxtNewsParentViewController/{key:value}/push
  //将url信息处理成 HHRouter 库合适的跳转信息进行跳转处理
  HnxxtNewsParentViewController *targetVc =  [[HHRouter shared] matchController:@"/hnxxt/HnxxtNewsParentViewController/param1"];
  targetVc.key1 = targetVc.params[key1];
  [self.navigationController pushViewController: targetVc animated:YES];
  return YES;
}

1.5 ALRouter

8 star

参考HHRouter实现的路由,优化了传参方式,不再通过url传参,而是通过方法传参

1.5.1 路由准备

在AppDelegate的didFinishLaunchingWithOptions方法中进行路由注册:

  • 可以通过plist文件进行统一注册
  • 可以直接注册,直接注册时如需统一管理还是得自己写工具类
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
      //注册方式1 : plist文件注册:
      [ALRouter loadConfigPlist:nil];
      //注册方式2: 也可以直接注册某个controller
      [ALRouter regist:@"/hnxxt/HnxxtNewsParentViewController" toControllerClass:[HnxxtNewsParentViewController class]];
      return YES;
}

@end

1.5.2 路由使用

//不带参数
 HnxxtNewsParentViewController *targetVc = (HnxxtNewsParentViewController*)[ALRouter openURL:@"/hnxxt/HnxxtNewsParentViewController"];
//带参数 接收参数处理同HHRouter,也是实现UIViewController的分类,利用vc中的params接收参数
HnxxtNewsParentViewController *targetVc = (HnxxtNewsParentViewController*)[ALRouter openURL:@"/hnxxt/HnxxtNewsParentViewController" withParams:@{"key1":"param1"}];
targetVc.param1 = targetVc.params[key1];
//跳转同样需要自己实现
[self.navigationController pushViewController:[ALRouter openURL:@"/hnxxt/HnxxtNewsParentViewController" ] animated:YES completion:nil];

1.4.3 兼容外部调用打开APP并跳转到页面(统一处理)

查看源码也是没有提供处理外部应用打开当前app的相关路由方法,所以对于外部打开app处理还是同上述一致:

在AppDelegate中的 openURL 方法统一处理

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options
{
  //url的值即为外部调用的,比如浏览器H5链接,xxt://open/HnxxtNewsParentViewController/{key:value}/push
  //将url信息处理成 ALRouter 库合适的跳转信息进行跳转处理
HnxxtNewsParentViewController *targetVc = (HnxxtNewsParentViewController*)[ALRouter openURL:@"/hnxxt/HnxxtNewsParentViewController" withParams:@{"key1":"param1"}];
targetVc.param1 = targetVc.params[key1];
[self.navigationController pushViewController:targetVc animated:YES completion:nil];

  return YES;
} 

2. 基于runtime类型的路由

2.1 CTMediator

3.8k Star

2.1.1 路由准备

假如路由目标vc叫 HnxxtNewsParentViewController:

1、新建Target_Hnxxt类,在这个类里去写方法提供HnxxtNewsParentViewController的实例

2、创建 CTMediator 的 Category,比如叫CTMediator+Hnxxt,在这个类里提供对外调用的路由方法

这个库也不需要初始化操作;另外这里在Target_Hnxxt和CTMediator+Hnxxt中都对路由进行了统一管理;Target_Hnxxt使CTMediator和具体的路由目标类进行了解耦,详细示例如下:

//1、新建Target_Hnxxt类
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
    
@interface Target_Push : NSObject
- (UIViewController *)Action_GetHnxxtNewsParentViewController:(NSDictionary *)params {
      HnxxtNewsParentViewController *targetVc = [[HnxxtNewsParentViewController alloc] init];
      //可以利用params,设置targetVc的参数
             targetVc.param1 = params[key1];
      return targetVc;
}
@end
//2、创建 CTMediator 的 Category,比如叫CTMediator+Hnxxt
#import "CTMediator+Hnxxt.h"
//kMediatorTarget字符串 是 Target_Hnxxt.h 中的 Hnxxt 部分
NSString * const kMediatorTarget = @"Hnxxt";
//kMediatorAction是 Target_Hnxxt.h 中 定义的 Action_GetHnxxtNewsParentViewController 函数名的 GetHnxxtNewsParentViewController 部分
NSString * const kMediatorAction = @"GetHnxxtNewsParentViewController";
@implementation CTMediator (Hnxxt)
- (UIViewController *)routeToHnxxtNewsParentViewController:(NSDictionary *)params{
   UIViewController *viewController = [self performTarget:kMediatorTarget
                                                   action:kMediatorAction
                                                   params:params
                                        shouldCacheTarget:NO];
       if ([viewController isKindOfClass:[UIViewController class]]) {
          GGLog(@"%@ 实例化页面成功", NSStringFromSelector(_cmd));
         return viewController;
       }else{
         GGLog(@"%@ 未能实例化页面", NSStringFromSelector(_cmd));
         return [[UIViewController alloc]init];
      }
}
@end

2.1.2 路由使用

//在需要跳转的地方调用
NSDictionary *params = @{@"key1":@"param1"};
HnxxtNewsParentViewController * targetVc = (HnxxtNewsParentViewController*)[[CTMediator sharedInstance] routeToHnxxtNewsParentViewController:params];
//跳转时也需要自己处理
[self.navigationController pushViewController:targetVc animated:YES];

2.1.3 兼容外部调用打开APP并跳转到页面(统一处理)

在AppDelegate中的 openURL 方法进行处理,思路也是将远程的url转换为本地的跳转方式

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options
{
  //url的值即为外部调用的,格式类似:xxt://Target_Hnxxt/Action_GetHnxxtNewsParentViewController?key1=param1
  //直接调用库的方法,这个方法内部也是解析这个url,然后换成本地调用
  return [[[CTMediator sharedInstance] performActionWithUrl:url completion:nil] boolValue];
} 

总结:

以上就是对目前iOS 比较流行的路由库的笼统介绍,期间涉及到的第三方库的使用细节,还需要大家在用到的时候再仔细对照官方文档和demo,也可以参考下面这些我总结的相关技术博客,比官方文档更贴合实际使用:

参考资料:

iOS 组件化 —— 路由设计思路分析

iOS 组件化-路由解耦思想 JLRoutes 实战篇(一)App内控制器跳转

routable-ios源码解析

iOS开发 — HHRouter路由数据传递开发分享

CTMediator: iOS应用架构谈 组件化方案

CTMediator:在现有工程中实施基于CTMediator的组件化方案

CTMediator 的初体验

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

推荐阅读更多精彩内容