读源码-JLRoutes

JLRoutes在GitHub上有4.2k星。它是一个具有简单的基于块的API的URL路由库。 旨在让开发者能够使用较少的代码轻松处理应用程序中的复杂URL scheme。


这篇文章主要分以下三个部分:

  1. 方法注释
  2. 方法调用流程图
  3. 主要内部实现

方法注释

  • JLRoutes

JLRoutes类是此框架的主要入口点。用于访问schemes(方案, 应用的标识),管理路由和路由URL。

// 控制这个路由器如果在当前命名空间中不能匹配,是否尝试将URL与全局路由匹配。默认为NO。
@property (nonatomic, assign) BOOL shouldFallbackToGlobalRoutes;

//  任何时候调用routeURL返回NO的回调。与shouldFallbackToGlobalRoutes属性相关
@property (nonatomic, copy, nullable) void (^unmatchedURLHandler)(JLRoutes *routes, NSURL *__nullable URL, NSDictionary<NSString *, id> *__nullable parameters);

// 返回一个具有全局路由scheme的路由实例
+ (instancetype)globalRoutes;

// 返回一个具有指定scheme的路由实例
+ (instancetype)routesForScheme:(NSString *)scheme;

// 注销并删除指定scheme的路由scheme
+ (void)unregisterRouteScheme:(NSString *)scheme;

// 注销所有路由scheme
+ (void)unregisterAllRouteSchemes;

// JLRRouteDefinition为路由模型对象。通过直接插入路由模型对象来添加路由。这可能是JLRRouteDefinition的子类来提供自定义的路由逻辑
- (void)addRoute:(JLRRouteDefinition *)routeDefinition;

// 在接收scheme中注册一个默认优先级(0)的routePattern
- (void)addRoute:(NSString *)routePattern handler:(BOOL (^__nullable)(NSDictionary<NSString *, id> *parameters))handlerBlock;

//在全局scheme命名空间里注册一个routePattern,当该路由模式与URL匹配时回调handlerBlock。该block的返回值是一个BOOL值,表示handlerBlock是否实际处理了路由。如果返回NO,JLRoutes将继续尝试寻找匹配路由。
- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^__nullable)(NSDictionary<NSString *, id> *parameters))handlerBlock;

// 在接收scheme中给一个handler注册多个具有默认优先级(0)的路由模式
- (void)addRoutes:(NSArray<NSString *> *)routePatterns handler:(BOOL (^__nullable)(NSDictionary<NSString *, id> *parameters))handlerBlock;

// 从接收scheme中移除路由
- (void)removeRoute:(JLRRouteDefinition *)routeDefinition;

// 从接收scheme中移除第一个匹配此路由模式的路由
- (void)removeRouteWithPattern:(NSString *)routePattern;

// 从接收scheme中移除所有路由
- (void)removeAllRoutes;

// 使用字典风格的下标注册一个具有默认优先级(0)的路由模式
- (void)setObject:(nullable id)handlerBlock forKeyedSubscript:(NSString *)routePatten;

// 返回在接收scheme中的所有已注册路由
- (NSArray <JLRRouteDefinition *> *)routes;

// 返回所有键是scheme对应的已注册路由
+ (NSDictionary <NSString *, NSArray <JLRRouteDefinition *> *> *)allRoutes;

// 如果提供的URL将成功匹配任一个已注册的路由则返回YES。否则返回NO。
+ (BOOL)canRouteURL:(nullable NSURL *)URL;

// 如果提供的ULR将成功为当前scheme匹配任一个已注册的路由则返回YES。否则返回NO。
- (BOOL)canRouteURL:(nullable NSURL *)URL;

// 路由一个URL,为与此URL相匹配的模式调用handler blocks,直到找到了相匹配的模式,返回YES。如果没有找到匹配的路由,将调用unmatchedURLHandler(如果设置了)
+ (BOOL)routeURL:(nullable NSURL *)URL;

// 在特定scheme内路由一个URL,为与此URL相匹配的模式调用handler blocks,直到找到了相匹配的模式,返回YES。如果没有找到匹配的路由,将调用unmatchedURLHandler(如果设置了)
- (BOOL)routeURL:(nullable NSURL *)URL;

// 在任一个路由scheme内路由一个URL,调用handler blocks(模式与URL匹配),找到了则返回YES。其他参数传递给匹配的路由block
+ (BOOL)routeURL:(nullable NSURL *)URL withParameters:(nullable NSDictionary<NSString *, id> *)parameters;

// 在任一个路由scheme内路由一个URL,调用handler blocks(模式与URL匹配),找到了则返回YES。其他参数传递给匹配的路由block
- (BOOL)routeURL:(nullable NSURL *)URL withParameters:(nullable NSDictionary<NSString *, id> *)parameters;

// 配置详细日志记录,默认为NO
+ (void)setVerboseLoggingEnabled:(BOOL)loggingEnabled;

// 返回当前详细日志记录启用状态。默认为NO
+ (BOOL)isVerboseLoggingEnabled;

// 配置是否应将“+”替换为解析值的空格。默认为YES。
+ (void)setShouldDecodePlusSymbols:(BOOL)shouldDecode;

// 返回是否应该将"+"替换为解析值得空格。默认为YES。
+ (BOOL)shouldDecodePlusSymbols;

// 配置URL主机是否始终被视为路径组件。默认为NO。
+ (void)setAlwaysTreatsHostAsPathComponent:(BOOL)treatsHostAsPathComponent;

// 返回URL主机是否始终被视为路径组件。默认为NO。
+ (BOOL)alwaysTreatsHostAsPathComponent;

// 配置创建路由对象时使用的默认类。默认是JLRRouteDefinition类
+ (void)setDefaultRouteDefinitionClass:(Class)routeDefinitionClass;

// 返回当创建路由对象时使用的默认类。默认是JLRRouteDefinition类。
+ (Class)defaultRouteDefinitionClass;

  • JLRParsingUtilities
// 根据是否解码"+"来返回value的值
+ (NSString *)variableValueFrom:(NSString *)value decodePlusSymbols:(BOOL)decodePlusSymbols;

// 根据是否解码“+”来返回查询参数
+ (NSDictionary *)queryParams:(NSDictionary *)queryParams decodePlusSymbols:(BOOL)decodePlusSymbols;

// 为路由模式展开可选路由模式
+ (NSArray <NSString *> *)expandOptionalRoutePatternsForPattern:(NSString *)routePattern;

  • JLRRouteDefinition

此类表示注册路由的具体模型对象,包括URL scheme,路由模式,和优先级。这个类可以被子类化来自定义路由解析行为,通过重写-routeResponseForRequest:方法。-callHandlerBlockWithParameters也可以被重写来自定义传入handler block的参数。

// 此路由应用的URL scheme, 如果是全局的则是JLRoutesGlobalRoutesScheme
@property (nonatomic, copy, readonly) NSString *scheme;

// 路由模式
@property (nonatomic, copy, readonly) NSString *pattern;

// 此路由模式的优先级
@property (nonatomic, assign, readonly) NSUInteger priority;

// 路由模式路径组件
@property (nonatomic, copy, readonly) NSArray <NSString *> *patternPathComponents;

// 当发现匹配时调用的block
@property (nonatomic, copy, readonly) BOOL (^handlerBlock)(NSDictionary *parameters);

// 检查路由模型是否相等
- (BOOL)isEqualToRouteDefinition:(JLRRouteDefinition *)routeDefinition;

// 创建一个新的路由模型。已经创建的路由模型可以直接添加到JLRoutes实例中。
// 参数一:完整的路由模式('/foo/:bar')
// 参数二:路由优先级,默认为0
// 参数三:当发现匹配成功处理事件的回调
- (instancetype)initWithPattern:(NSString *)pattern priority:(NSUInteger)priority handlerBlock:(BOOL (^)(NSDictionary *parameters))handlerBlock

// 当为给定的scheme已经注册了路由时调用
- (void)didBecomeRegisteredForScheme:(NSString *)scheme;

// 为提供的JLRRouteRequest创建并提供一个JLRRouteResponse。此响应指定是否匹配。
// 参数: JLRRouteRequest用来创建一个响应
// 返回值:一个JLRRouteResponse实例,表示尝试将请求匹配到路由模型的结果
- (JLRRouteResponse *)routeResponseForRequest:(JLRRouteRequest *)request;

// 使用给定的参数调用handlerBlock。可以被子类重写。
// 参数:传递给handlerBlock的参数
// 返回值:调用handlerBlock的返回值(如果被视为已经处理了则返回YES,否则返回NO)
- (BOOL)callHandlerBlockWithParameters:(NSDictionary *)parameters;

// 创建并返回完整的匹配参数集合,作为有效匹配的一部分传递。子类可以重写此方法来修改匹配参数,或者直接调用它来生成期望值。
// 参数request: 被路由的请求
// 参数routeVariables: 解析的路由变量(又名 '/route/:param'路由被'/foo/bar'路由会创建['param' : 'bar'])
// 返回值:完整的匹配参数集合,左右有效匹配的一部分传递。
- (NSDictionary *)matchParametersForRequest:(JLRRouteRequest *)request routeVariables:(NSDictionary <NSString *, NSString *> *)routeVariables;

// 为给定的请求创建并返回一个默认的基本匹配参数。不包含任何解析字段
// 参数request: 被路由的请求
// 返回值:给定请求的默认匹配参数。只包含JLRoutePatternKey,JLRouteURLKey和JLRouteSchemeKey的键/值对
- (NSDictionary *)defaultMatchParametersForRequest:(JLRRouteRequest *)request;

// 为给定的请求解析并返回路由变量
// 参数request: 从中解析变量值的请求
// 返回值:如果是匹配的则返回解析的路由变量,如果没有匹配则返回nil
- (nullable NSDictionary <NSString *, NSString *> *)routeVariablesForRequest:(JLRRouteRequest *)request;

// 将value解析为变量名称,包括如果需要删除任何额外的字符。
// 参数value: 应该被解析为变量名称的原始字符串值
// 返回值: 变量名称,用作分析路由变量中键/值对的键
- (NSString *)routeVariableNameForValue:(NSString *)value;

// 将value解析为变量值,包括如果需要删除任何额外的字符
// 参数value: 应该被解析为变量值得原始字符串值
// 返回值:变量值,用作分析路由变量中键/值对的值
- (NSString *)routeVariableValueForValue:(NSString *)value;

  • JLRRouteHandler

是创建handler blocks的帮助类,用于传递给addRoute:调用。这对于希望单独的类或对象成为深层链接路由的处理程序的情况特别有用。比如可能是一个你想要实例化和呈现的视图控制器类来响应深层链接路由。

// 创建并返回一个在weak属性目标对象上调用handleRouteWithParameters:方法的block。从此方法返回的block应该作为addRoute:的handler block。
// 参数weakTarget: 应该处理匹配路由的目标对象
// 返回值 提供的weakTarget的新处理块
// 讨论:目标对象的拥有者没有变化,只有一个弱指针在block中被捕获。如果该对象被重新分配,则handler将不再被调用(但路由将保持注册,除非明确移除)
+ (BOOL (^__nonnull)(NSDictionary<NSString *, id> *parameters))handlerBlockForWeakTarget:(__weak id <JLRRouteHandlerTarget>)weakTarget;

// 创建并返回一个创建新实例的targetClass(此targetClass必须遵守JLRRouteHandlerTarget协议)的block,然后在它上调用handleRouteWithParameters:方法。然后创建的对象作为参数传递给完成的block块。从此方法返回的block应该作为addRoute:的handler block。
// 参数targetClass: 要处理路由请求的目标类。必须遵守JLRRouteHandlerTarget协议。
// 参数completionHandler: 创建了一个新targetClass实例后调用的完成块。
// 返回值 用于创建targetClass实例的新的handler block。
// 讨论:JLRoutes不会保留或拥有此创建的对象。预计通过完成的回调传递的创建对象将被调用应用程序使用并拥有。
+ (BOOL (^__nonnull)(NSDictionary<NSString *, id> *parameters))handlerBlockForTargetClass:(Class)targetClass completion:(BOOL (^)(id <JLRRouteHandlerTarget> createdObject))completionHandler;
  • JLRRouteHandlerTarget

遵守此协议的类才可以被用来作为路由处理目标

// 从JLRoutes路由里通过传递匹配的路由参数初始化一个遵守此协议的类的实例。
// 参数:初始化对象时传递的匹配参数。这些从JLRoutes handler block里传递。
// 返回值:遵守此协议的类的实例
- (instancetype)initWithRouteParameters:(NSDictionary <NSString *, id> *)parameters;

// 为成功的路由匹配调用
// 参数:传递给handler block的匹配参数
// 返回值: 如果路由已经处理则返回YES,如果应该尝试匹配不同的路由则返回NO
- (BOOL)handleRouteWithParameters:(NSDictionary<NSString *, id> *)parameters;

  • JLRRouteRequest

表示用来路由一个URL的请求模型。它被分解为路径组件和查询参数,然后由JLRRouteDefinition使用它们来尝试匹配

// 被路由的URL
@property (nonatomic, copy, readonly) NSURL *URL;

// URL的路径组件
@property (nonatomic, strong, readonly) NSArray *pathComponents;

// URL的查询参数
@property (nonatomic, strong, readonly) NSDictionary *queryParams;

// 路由请求选项,通常由框架全局选项配置
@property (nonatomic, assign, readonly) JLRRouteRequestOptions options;

// 作为匹配参数字典的一部分传递的其他参数
@property (nonatomic, copy, nullable, readonly) NSDictionary *additionalParameters;

// 创建一个新路由请求
// 参数URL: 用来路由的URL
// 参数options: 指定解析行为的选项位掩码。
// 参数additionalParameters: 包含在针对此请求的任何匹配字典中的其他参数
// 返回值:一个新的路由请求实例
- (instancetype)initWithURL:(NSURL *)URL options:(JLRRouteRequestOptions)options additionalParameters:(nullable NSDictionary *)additionalParameters

  • JLRRouteResponse

尝试路由JLRRouteRequest的响应

// 指示响应是否匹配
@property (nonatomic, assign, readonly, getter=isMatch) BOOL match;

// 匹配参数(一个无效的响应为nil)
@property (nonatomic, copy, readonly, nullable) NSDictionary *parameters;

// 检查路由响应是否相等
- (BOOL)isEqualToRouteResponse:(JLRRouteResponse *)response;

// 创建一个无效的匹配响应(不匹配)
+ (instancetype)invalidMatchResponse;

// 使用给定的参数创建一个有效的匹配响应
+ (instancetype)validMatchResponseWithParameters:(NSDictionary *)parameters;

方法调用流程图

方法调用流程图

主要内部实现

// 根据scheme返回路由实例(JLRoutes实例)
+ (instancetype)routesForScheme:(NSString *)scheme
{
    // 声明JLRoutes实例对象routesController
    JLRoutes *routesController = nil;

    // 创建可变全局字典JLRGlobal_routeControllersMap,只创建一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        JLRGlobal_routeControllersMap = [[NSMutableDictionary alloc] init];
    });

    // 如果字典中没有key为schme对应的Value, 就创建一个,否则直接获取
    if (!JLRGlobal_routeControllersMap[scheme]) {
        routesController = [[self alloc] init]; // 初始化routesController
        routesController.scheme = scheme;// 将scheme设置为routesController的Scheme
        JLRGlobal_routeControllersMap[scheme] = routesController; // JLRGlobal_routeControllersMap字典中添加key为scheme和value为routesController的键值对
    }
    // 从JLRGlobal_routeControllersMap中根据key为scheme获取对应的routesController并返回
    routesController = JLRGlobal_routeControllersMap[scheme];

    return routesController;
}

// 参数一: 路由模式
// 参数二:优先级
// 参数三: 处理回调
- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary<NSString *, id> *parameters))handlerBlock
{
    // 为routePattern路由模式展开可选路由模式
    NSArray <NSString *> *optionalRoutePatterns = [JLRParsingUtilities expandOptionalRoutePatternsForPattern:routePattern];
    
    // 根据传入参数创建JLRRouteDefinition路由模型对象
    JLRRouteDefinition *route = [[JLRGlobal_routeDefinitionClass alloc] initWithPattern:routePattern priority:priority handlerBlock:handlerBlock];

    // 如果optionalRoutePatterns大于0, 即有可选路由模式
    if (optionalRoutePatterns.count > 0) {
    
        // there are optional params, parse and add them
        for (NSString *pattern in optionalRoutePatterns) {
        
            // 遍历路由模式,根据路由模式和其他参数创建路由模型对象
            JLRRouteDefinition *optionalRoute = [[JLRGlobal_routeDefinitionClass alloc] initWithPattern:pattern priority:priority handlerBlock:handlerBlock];
            
            // 添加到mutableRoutes数组中视为注册
            [self _registerRoute:optionalRoute];
            // 打印日志
            [self _verboseLog:@"Automatically created optional route: %@", optionalRoute];
        }
        return;
    }
    // 如果optionalRoutePatterns为空数组,则将传递进来的路由模式生成的路由模型添加到mutableRoutes数组
    [self _registerRoute:route];
}

// 私有方法, 注册路由
// 参数: 路由模型对象
// 将routeDefinition路由模型对象按优先级从高到低添加到mutableRoutes数组中,并将self的scheme赋值给该路由模型的scheme
- (void)_registerRoute:(JLRRouteDefinition *)route
{


    // 如果路由模型对象(路由模式)的优先级为0 或者 mutableRoutes数组为0
    if (route.priority == 0 || self.mutableRoutes.count == 0) {
        //mutableRoutes数组添加路由模型对象
        [self.mutableRoutes addObject:route];
    } else { // 否则
        NSUInteger index = 0;
        BOOL addedRoute = NO; // 标记是否已添加

        // search through existing routes looking for a lower priority route than this one
        // 在现有路由模型数组里寻找比这个更低优先级的路由模型
        for (JLRRouteDefinition *existingRoute in [self.mutableRoutes copy]) { // 此处用copy拷贝一份数组为不可变,如果不copy,在下方给self.mutableRoutes插入模型时会造成崩溃,注意:遍历数组时不能修改数组。
            // 如果传递近来的路由模型优先级高于mutableRoutes数组中某个已经存在的路由模型优先级,则插入到数组中
            if (existingRoute.priority < route.priority) {
                // if found, add the route after it
                [self.mutableRoutes insertObject:route atIndex:index];
                addedRoute = YES; // 标记为已经添加
                break; // 退出循环
            }
            index++;
        }


        // if we weren't able to find a lower priority route, this is the new lowest priority route (or same priority as self.routes.lastObject) and should just be added
        //addedRoute为NO,则说明传递进来的模型优先级低于数组中每一个模型优先级,就添加到数组的最后一个
        if (!addedRoute) {
            [self.mutableRoutes addObject:route];
        }
    }
    // 通过上面插入排序的方法,保证mutableRoutes数组中的JLRRouteDefinition对象是按照优先级的从高到底排列的

    // 将JLRoutes的scheme赋值给传递进来的路由模型对象的scheme
    [route didBecomeRegisteredForScheme:self.scheme];
}

// 私有方法 匹配路由,是否已路由
// 参数一:路由URL
// 参数二:parameters
// 参数三: 执行block
- (BOOL)_routeURL:(NSURL *)URL withParameters:(NSDictionary *)parameters executeRouteBlock:(BOOL)executeRouteBlock
{
    if (!URL) { // 如果URL为空
        return NO;
    }

    [self _verboseLog:@"Trying to route URL %@", URL];

    BOOL didRoute = NO; // 标记是否已经路由

    JLRRouteRequestOptions options = [self _routeRequestOptions]; // 获取路由请求选项
    // 创建路由请求
    JLRRouteRequest *request = [[JLRRouteRequest alloc] initWithURL:URL options:options additionalParameters:parameters];

    for (JLRRouteDefinition *route in [self.mutableRoutes copy]) {
        // check each route for a matching response
        //根据request查找对应匹配的response, 是否匹配成功
        JLRRouteResponse *response = [route routeResponseForRequest:request];
        if (!response.isMatch) { // 如果匹配不成功,下一个
            continue;
        }

        // 匹配成功
        [self _verboseLog:@"Successfully matched %@", route];

        if (!executeRouteBlock) {
            // if we shouldn't execute but it was a match, we're done now
            // 没有执行block立即返回
            return YES;
        }

        [self _verboseLog:@"Match parameters are %@", response.parameters];

        // Call the handler block
        // 使用响应的参数调用handlerBlock
        didRoute = [route callHandlerBlockWithParameters:response.parameters];

        if (didRoute) { // 如果已路由,中断循环
            // if it was routed successfully, we're done - otherwise, continue trying to route
            break;
        }
    }

    if (!didRoute) {
        [self _verboseLog:@"Could not find a matching route"];
    }

    // if we couldn't find a match and this routes controller specifies to fallback and its also not the global routes controller, then...
    // 1. 如果没有路由
    // 2. 尝试将URL与全局路由匹配
    // 3. 当前self不是全局路由
    if (!didRoute && self.shouldFallbackToGlobalRoutes && ![self _isGlobalRoutesController]) {
        [self _verboseLog:@"Falling back to global routes..."];
        // 尝试用全局路由来匹配
        didRoute = [[JLRoutes globalRoutes] _routeURL:URL withParameters:parameters executeRouteBlock:executeRouteBlock];
    }

    // if, after everything, we did not route anything and we have an unmatched URL handler, then call it
    // 1. 没有路由
    // 2. 执行路由block
    // 3. 有不匹配路由的回调
    if (!didRoute && executeRouteBlock && self.unmatchedURLHandler) {
        [self _verboseLog:@"Falling back to the unmatched URL handler"];
        // 回调不匹配路由
        self.unmatchedURLHandler(self, URL, parameters);
    }

    // 返回是否已路由
    return didRoute;
}


总结:

  1. JLRouts中有一个全局可变字典JLRGlobal_routeControllersMap, 该字典是以schemekey,以JLRoutes对象为value
  2. JLRoutes对象有一个可变数组mutableRoutes,里面存放着JLRRouteDefinition对象,该对象的schemeJLRoutesscheme,并且有一个优先级属性priority;
  3. mutableRoutes每次添加JLRRouteDefinition对象时都会进行插入排序,优先级从高到底。
  4. 调用routesForScheme:方法时,会将scheme添加到JLRGlobal_routeControllersMap字典中。
  5. 调用addRoute:方法时,会创建JLRRouteDefinition路由模型对象添加到mutableRoutes数组中。JLRRouteDefinition路由模型对象会保存addRoute:传递进来的handlerBlock
  6. 调用routeURL:方法时,会根据传递进来的参数创建JLRRouteRequest对象,并遍历mutableRoutes数组,找到与之匹配的JLRRouteResponse对象,处理handlerBlock

有写得不好的地方还希望多多指点哈~

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,490评论 18 139
  • 介绍 : JLRoutes是一个调用极少代码 , 可以很方便的处理不同URL schemes以及解析它们的参数,并...
    CoderLF阅读 1,608评论 0 3
  • 介绍 : JLRoutes是一个调用极少代码 , 可以很方便的处理不同URL schemes以及解析它们的参数,并...
    一支烟一只猿阅读 11,423评论 3 17
  • 原文链接:https://github.com/halfrost/Halfrost-Field/blob/mast...
    hament阅读 5,633评论 1 31
  • 如果一个人说喜欢你,请等到他对你百般照顾时再相信; 如果他答应带你去一个地方,等他订好机票再开心; 如果他说要娶你...
    开心的霏霏阅读 412评论 0 2