Objective-C 代码规范

版本 内容 修订人 时间
0.1.0 草稿 黄鑫 2018/06/11
0.2.0 修改文档组织 黄鑫 2018/06/16

0. 前言


"代码是写给人看的"

例子🌰

//
//  M2User.h
//  M2API
//
//  Created by Kim on 2018/06/11.
//

// 头文件引入
#import <Foundation.h>

#import "M2Defines.h"


// 常量定义
FOUNDATION_EXPORT NSString * const M2UserErrorDomain;

/**
  性别枚举 
*/
typedef NS_ENUM(NSUInteger, M2Gender) {
    M2GenderUnknow = 0,     //!< 未知
    M2GenderMale,           //!< 男性
    M2GenderFemale          //!< 女性
};


/** 用户  */
@interface M2User : NSObject

@property (nonatomic, readonly, copy) NSString *name;       //!< 名字
@property (nonatomic, readonly, assign) NSUInteger age;     //!< 年龄
@property (nonatomic, readonly, assign) M2Gender gender;    //!< 性别

/**
  初始化
  
  @param name 用户名
  @param age 年龄
  @param gender 性别  
*/
+ (instancetype)userWithName:(NSString * __Nonnull)name 
                         age:(NSUInteger)age 
                      gender:(M2Gender)gender;

- (instancetype)initWithName:(NSString * __Nonnull)name
                         age:(NSUInteger)age
                      gender:(M2Gender)gender;

@end

// 实现 

@implementation M2User

+ (instancetype)userWithName:(NSString * __Nonnull)name 
                         age:(NSUInteger)age 
                      gender:(M2Gender)gender {
    return [[self alloc] initWithName:name age:age gender:gender];                               
}

- (instancetype)initWithName:(NSString * __Nonnull)name 
                         age:(NSUInteger)age 
                      gender:(M2Gender)gender {
    if (self = [super init]) {
        _name = name;
        _age = age;
        _gender = gender;
    }
    
    return self;
}

@end

1. 布局与风格


良好布局的目的

  • 准确表现代码的逻辑结构
  • 始终如一地表现代码的逻辑结构
  • 改善可读性
  • 经得起修改

布局技术

  • 分组 从另一个角度看,空白也是分组,也是确保相关到语句组成放在一起。
  • 空行 是指示一个程序如何组织的手段。可以用空行将相关语句各自划分成段落,分开各个子程序,突出注释部分。
  • 缩进 使用缩进形式显示程序的逻辑结构

“当程序有两到四个空格的缩进时,受试者对程序的理解分数会比毫无缩进的程序高出20%到30%。”

— 《程序缩进和可理解性》

2. 代码组织


Objective-C的类通常分成头文件和实现文件。

头文件

头文件通常包含:

  • 文件说明与版权
  • 头文件引入
  • 宏定义
  • 常量定义
  • 类型前置声明
  • 块类型定义
  • 枚举定义
  • 函数定义
  • 协议定义
  • 类定义 - 类定义通常包含
    • 类方法。
    • 属性。
    • 公开方法。
  • 分类定义
    • 分类方法。

🚧 注意:内容排列顺序与上面一致。

如下面的头文件模板所示。按照下面的顺序定义。

// 文件说明与版权
//
//  M2API2Client.h
//  M2API
//
//  Created by Kim on 2017/11/11.
//  Copyright (c) 2017 Kim Studio. All rights reserved.
//

// 头文件引入 (见下文说明)
#import <Foundation.h>

#import "M2APIClient.h"


// 宏定义 (见下文说明。必须是才使用宏)
#define M2_DEBUG  0
#define M2_TEST   1

// 常量定义
FOUNDATION_EXPORT NSString * const M2UserErrorDomain;

// 类型前置声明
@class User;

// 类型定义
typedef NSString * const M2APIHTTPMethod;

// Block类型定义
typedef void (^M2APISuccessBlock)(id response);
typedef void (^M2APIFailureBlock)(NSError *error);

// 枚举定义
typedef NS_ENUM(NSUInteger, M2DirectionType) {
    M2DirectionTypeUnknown = 0,
    M2DirectionTypeTop,
    M2DirectionTypeLeft,
    M2DirectionTypeButtom,
    M2DirectionTypeRight
};

// 协议定义
@protocol M2LoginViewDelegate : NSObject

@end

// 类定义
@interface M2User : NSObject

@property (nonatomic, readonly, copy) NSString *name;

@end
    
// 分类定义
@interface M2User <M2Debug>

- (NSString *)debugInfo;
    
@end

📝 通常使用文件模板

版权

文件头中增加版权信息。

//
//  M2API2Client.h
//  M2API
//
//  Created by Kim on 2017/11/11.
//  Copyright (c) 2017 Kim Studio. All rights reserved.
//

头文件引入

头文件引入规则顺序:

  • 系统库
  • 第三方库
  • 工程内类引入

注意:

  1. 系统库/第三方库与工程内类引入直接的分组空行
  2. 工程内类如果有多个头文件引入,也可以增加空行按功能进行分组。
//
//  M2API2Client.h
//  M2API
//
//  Created by Kim on 2017/11/11.
//  Copyright (c) 2017 Kim Studio. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <AFNetworking/AFNetworking.h>
#import <CocoaLumberjack/CocoaLumberjack.h>
#import <AFHTTPSessionManagerLogger.h>

#import "M2APIConfiguration.h"
#import "M2APIHTTPSessionManager.h"

实现文件

实现文件通常包含:

// 文件描述

// 头文件引入

// 常量定义

// 文件内私有类定义

// 类私有方法定义

// 类实现

类定义

一般类定义组成如下:

  • 协议
    • 成员变量
    • 属性
    • 类方法
    • 构造函数
    • 公开方法
    • 控件响应函数
    • 通知响应函数
    • 委托方法
    • 私有方法

空行与注释:

  1. 协议之间留2行空行。
  2. @protocol/@interface第一个属性方法后,空1行
  3. 最后一个方法@end之间空1行
  4. 类最后空1行
  5. 属性第一个方法之间空1行
  6. 属性如果有长注释,则空1行。
  7. 属性如果使用短注释,则在属性后使用//!<进行注释。
  8. 如果方法定义有注释,则空1行
  9. 如果方法定义没有注释,则可以不留空行进行分组。
  10. 方法分组之间,空1行
  11. 函数定义的注意点,见后面函数一节
////// 头文件说明
//
//  M2APIUserClient.h
//  M2UserAPI
//  Created by Kim on 2018/06/10
//  Copyright (c) 2017 Kim Studio. All rights reserved.

////// 头文件引入
#import <M2HTTPClient.h>
#import "M2APIUser.h"

////// 协议定义
@protocol M2APIUserEndPoint <NSObject>

/**
  登陆
  
  @param user 用户名
  @param password 密码
  
  @return 信号 M2APIUser
 */
- (RACSignal *)loginWithUser:(NSString *)user password:(NSString *)password;

/**
  获取用户信息
  
  @return 信号 用户信息
 */
- (RACSignal *)userInfo;

@end

/////// 类定义
/**
  用户模块客户端
 */
@interface M2APIUserClient : M2HTTPClient <M2APIUserEndPoint>

@property (nonatomic, strong) M2APIConfiguration *configuration; //!< 配置消息
@property (nonatomic, strong) M2APISigner *signer; //!< 签名类

+ (instancetype)sharedClient;
+ (instancetype)clientWithConfiguration:(M2APIConfiguration *)configuration;
- (instancetype)initWithConfiguration:(M2APIConfiguration *)configuration;

@end

类实现

类实现内部组织 使用#pargma mark -来分割功能组。一个典型的ViewController的实现功能分组有:

  • 类方法。

    • 单件函数。
    • 其他类方法。
  • Lifecycle。 对象生命周期函数

    • 对象生命周期函数。init, dealloc, descrition
  • 自定义属性。

  • UI对象的事件响应函数。

  • 公开方法。

  • 私有方法和辅助函数。

  • 通知处理函数。

  • 委托方法。

#pargma mark - Class methods
+ (instancetype)sharedInstance {}
+ (CGFloat)viewHeightForObject:(id)object {}

#pargma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}

#pargma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}

#pargma mark - IBActions
- (IBAction)onSubmitDataAction:(id)sender {}

#pargma mark - Public 
- (void)publicMethod {}

#pargma mark - Private helpers or utils
- (void)m2_privateMethod {}

#pargma mark - Notification Handlers
- (void)onEnterBackgroundHandler:(NSNotification *)notification {}

#pargma mark - Delegate methods
// 多个delegate 进行分组

📝 使用文件模板

3. 命名


Apple命名规则尽可能坚持,特别是与这些相关的memory management rules(NARC)。

长的,描述性的方法和变量命名是好的。

命名涉及到比较多:

  • 库名
  • 文件名
  • 类名
  • 函数名
  • 变量名

库名

设计一个库通常使用前缀+名称的方式。eg.

  • UIKit
  • AVFoundation
  • SDWebImage
  • AFNetworking

文件名

文件名命名规则与类命名规则一致:

  • 命名空间。本项目/或项目模块缩略前缀。eg. M2API, M2BL, M2PL
  • 功能名词。User,Device,File,VideoPlayer。
  • 功能分类名字。例如,
    • Client 代表DAL的网络访问客户端。
    • Data代表DAL的DTO (Data Transfer Object)。
    • 使用名词作为领域模型名称。
    • Service代表BL的业务逻辑类。
    • Item代表PL中View的VO(View Object)。
    • View代表PL的视图类。
    • Controller代表PL的中MVC模式的C控制器
    • ViewModel/Store代表PLMVVMVM或者MVCS的S

eg.

  • UIViewController.h
  • M2PLLoginView.h

命名空间

由于Objective-C 没有命名空间,所以通常使用项目名的头字母用于:

  • 常量

  • 枚举

  • C函数名

  • 全局变量名

  • 类名

  • 块类型名

前缀应由不少于3个字母组成(苹果保留所有2个字母的前缀)。可以是APP名、公司名缩写等。

例子🌰

// 宏
#debug M2_DEBUG

// 常量
FOUNDATION_EXPORT NSString * const M2UserErrorDomain;

// 别名
typedef NSString * const M2HTTPMethod;

// 块类型名
typedef void (^M2APISuccessBlock)(id response);
typedef void (^M2APIFailureBlock)(NSError *error);

// 类名
@class M2User;

尽量少使用宏来定义常量。宏通常用于编译条件。

  • 增加命名空间。
  • 单词使用大写。
  • 使用下划线连接单词。

使用

#define M2_DEBUG

不使用

#define Production
#define M2_Production

常量

常量通常使用与字符串类型常量与值类型常量。注意点:

  • 命名空间。命名空间前缀全部大写
  • 使用驼峰命名
// 头文件 .h
FOUNDATION_EXPORT NSString * const M2UserErrorDomain;
FOUNDATION const CGFloat M2UserMaxAge;

// 实现文件 .m
static NSString * const M2UserError = @"net.kim.M2UserErrorDomain"; // 跨文件使用
static const CGFloat M2UserMaxAge = 200; // 跨文件使用

static const CGFloat M2UserMinAge = 0; // 文件内部使用

块类型

块类型定义。注意点:

  • 命名。命名空间 + 功能名词 + Block
    • 命名空间。见《命名空间一节》
    • 功能名词。
    • 后缀Block。以区别其他类型。
  • 注意返回类型后需要增加一个空格

typedef void (^M2APISuccessBlock)(id response);
typedef void (^M2APIFailureBlock)(NSError *error);

4. 类


  • 类方法
  • 单例模式
  • 属性
  • 实例方法

类方法

单例模式

单例对象应该使用线程安全模式来创建共享实例。

+ (instancetype)sharedInstance {
  static id sharedInstance = nil;

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [[self alloc] init];
  });

  return sharedInstance;
}

这会防止possible and sometimes prolific crashes.

属性

公开属性

属性特性排列顺序如下:

  • 是否原子atomic/nonatomic。虽然默认为atomic, 原子访问时还是需要显式说明。
  • 读写readonly/readwrite。默认为readwrite。只读是需要显式说明。
  • 访问器getter/setter。如果是布尔类型getter,需要加is前缀。
  • 存储特性weak/strong/copy/assign。放在最后。

💡Tip 不可变性 Immutable

为了避免数据遭到不必要的修改:

  1. 不应该被外部直接修改的属性,应该声明(readonly)(可以在Extension中重新声明为(readwrite),使它对外只读,对内可读写)。
  2. 不要把NSMutable(Array/Dictionary/Set...)暴露出来,应该只留一个setter给外部使用,以免它们被其他类修改时,类自身难以察觉。
  3. 如果数据不是特别多,copy的代价不是特别大,留给其他类的getter应尽量用copy方法,返回一个不可变的对象。

例子

@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) M2Gender gender;
@property (nonatomic, readonly, getter=isLogin, assign) BOOL login;

💡Tip 控件属性命名

  • 控件功能名词 + 后缀不带命名空间的控件类型名

例子

@interface M2PLoginView : UIView

@property (nonatomic, weak) IBOutlet UILabel *userLabel; //!< 用户名标签
@property (nonatomic, weak) IBOutlet UITextField *userTextField; //!< 用户名输入

@end

私有属性

如果私有属性在模块内部可以访问,则使用私有头文件。

eg. M2User_Private.h 在有需要使用到的实现文件引入即可。

🚧 注意:私有头文件,在模块外部不可访问。生成库时需要注意忽略改头文件。

例子

/// 实现文件

@interface M2User ()

@property (nonatomic, copy)NSString *name;
@property (nonatomic, assign)NSUInteger age;
@property (nonatomic, assign)M2Gender gender;

@end
    
@implementation M2User
    
// ...
    
@end

自定义属性

下面是一个懒加载的自定义属性:

- (NSMultableDictionary *)extraInfo {
    if (!_extraInfo) {
        _extraInfo = [NSMutableDictionary dictionary];
    }
    
    return _extraInfo
}

自定义属性设置

- (void)setExtraInfo:(NSDictionary *)extraInfo {
    _extraInfo = extraInfo;
    // Do something else.
    // 其他副作用。
}

🚧 注意:在自定义属性内增加副作用需要特别注意。需要在属性增加注释说明。

成员变量

私有属性,一般定义在类的实现文件。

/// 实现文件

@interface M2User () {
    BOOL _status;
}

@end
    
@implementation M2User
    
// ...
    
@end

标识位

@interface Fool () {
    struct {
        BOOL step1Done;
        BOOL step2Done;
    } _flags;
}


// 使用
_flag.step1Done = YES;
_flag.step1Done = NO;

类初始化方法

@interface Airplan

+ (instancetype)airplanWithType:(AirplanType)type;

@end
    
@implementation Airplan

关于更多instancetype信息,请查看NSHipster.com

Init方法

Init方法应该遵循Apple生成代码模板的命名规则。返回类型应该使用instancetype而不是id

- (instancetype)init {
    if (self = [super init]) {
        // ...
    }
    
    return self;
}

// 或者
- (instancetype)initWithName:(NSString *)name {
    self = [super init];
    if (self) {
        // ...
    }
    
    return self;
}

查看关于instancetype的文章Class Constructor Methods

方法定义

响应函数

规则:

  • 控件响应函数:前缀on + 功能动作 + 后缀Action
  • 通知响应函数:前缀on + 通知 + 后缀Handler

使用

// 控件响应函数
- (IBAction)onLoginAction:(id)sender {
    // ...
}

// 通知响应函数
- (void)onEnterBackgroundHandler:(NSNotification *)notification {
    // ...
}

不使用

- (IBAction)login:(id)sender {
    // ...
}

- (void)enterBackground:(NSNotification *)noti {
    // ...
}

分类

@interface M2APIUserClient (User)

// 登陆
- (RACSignal *)loginWithUser:(NSString *)user password:(NSString *)password;

// 登出
- (RACSignal *)logout;

@end

5. 子程序


变量[TODO]

布尔值

Objective-C使用YESNO。因为truefalse应该只在CoreFoundation,C或C++代码使用。既然nil解析成NO,所以没有必要在条件语句比较。不要拿某样东西直接与YES比较,因为YES被定义为1和一个BOOL能被设置为8位。

这是为了在不同文件保持一致性和在视觉上更加简洁而考虑。

使用

if (someObject) {}
if (![anotherObject boolValue]) {}

不使用

if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}
if (isAwesome == YES) {} // Never do this.
if (isAwesome == true) {} // Never do this.

如果BOOL属性的名字是一个形容词,属性就能忽略"is"前缀,但要指定get访问器的惯用名称。例如:

@property (assign, getter=isEditable) BOOL editable;

文字和例子从这里引用Cocoa Naming Guidelines

条件语句if/else

条件语句主体为了防止出错应该使用大括号包围,即使条件语句主体能够不用大括号编写(如,只用一行代码)。这些错误包括添加第二行代码和期望它成为if语句;还有,even more dangerous defect可能发生在if语句里面一行代码被注释了,然后下一行代码不知不觉地成为if语句的一部分。除此之外,这种风格与其他条件语句的风格保持一致,所以更加容易阅读。

使用

// Good!
if (!error) {
    return success;
}

不使用

// Bad
// 没有花括号,容易多些空行,造成逻辑提前返回。
if (!error)
    return success;

if (!error) return success;

if (error != nil)
    return success

多条件情而且单行过长的情况下,使用换行。条件符放行最后。

使用

// Good! 
if (direction == M2Left ||
    direction == M2Right) {
    // ...
} 

不使用

// Bad! 
if (direction == M2Left 
    || direction == M2Right) {
    // ...
} 

使用

if (user.isHappy) {
    // Do something
} else {
    // Do something else
}

不使用

if (user.isHappy)
{
    // ...
}
else {
    // ...
}

Switch-Case

- (void)handleMessage:(M2Message *)message {
    
    // 注意花括号与break。
    M2MessageType type = message.type;
    switch(type) {
        case M2MessageChat: {
            // ...
        } break;
        
        case M2MessageNotify: {
            // ...
        } break;
        case M2MessageSystem: {
            // ...
        } break;
            
        default:
           break;
    }
}

三元操作符 ?:

当需要提高代码的清晰性和简洁性时,三元操作符?:才会使用。单个条件求值常常需要它。多个条件求值时,如果使用if语句或重构成实例变量时,代码会更加易读。一般来说,最好使用三元操作符是在根据条件来赋值的情况下。

Non-boolean的变量与某东西比较,加上括号()会提高可读性。如果被比较的变量是boolean类型,那么就不需要括号。

使用

NSInteger value = 5;
result = (value != 0) ? x : y;

Bool isHorizontal = YES;
result = isHorizontal ? x : y;

NSString *name = nil;
result = name ?: @"";

不使用

BOOL result = value!=0 ?x:y;

block

使用

typedef void (^M2SuccessBlock)(id response);

M2SuccessBlock successBlock = ^(id response) {
    // Do something.
};

不使用

// 可读性不够强,另外可能导致过长行。
void (^successBlock)(id response) = ^(id response) {
    // Do something.
};

使用

// 风格1
[userClient loginWithUser:user password:password success:^(id user) {
    // do something. 
} failure:^(NSError *error) {
    // ...
}];


// *******************************************
// 如果success/failure块过长,则可以前缀定义块。
// 风格2
M2APISuccessBlock successBlock = ^(id response) {
  // do something.  
};

M2APIFailureBlock failureBlok = ^(NSError *error) {
  // ...  
};

// 单行过长,则换行。
[userClient loginWithUser:user 
                 password:password 
                  success:successBlock 
                  failure:failureBlock];

字面值

使用

// 数组
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];

// 字典
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};

// Number
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @10018;

不使用

// 数组
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];

// 字典
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];

// Number
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingStreetNumber = [NSNumber numberWithInteger:10018];

数组

// 数组
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];

names[]

字典

// 字典创建
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};

// 访问
NSString *product = productManagers[@"iPhone"];

// 
NSMutableDictionary *user = [NSMutableDictionary dictionary];

// 设置
user[@"name"] = @"Bob";
user[@"title"] = @"IT Manager";
user[@"age"] = @25;

// 迭代
[user enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
   NSLog(@"key : %@, value : %@", key, obj);     
}];

CGRect 函数

使用

// Good!
CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
CGRect frame = CGRectMake(0.0, 0.0, width, height);

不使用

// Bad
CGRect frame = self.view.frame;

CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;
CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };

注释

当需要注释时,注释应该用来解释这段特殊代码为什么要这样做。任何被使用的注释都必须保持最新或被删除。

一般都避免使用块注释,因为代码尽可能做到自解释,只有当断断续续或几行代码时才需要注释。例外:这不应用在生成文档的注释

空格

空行

6. 资源


iOS应用包含多种资源文件

  • storyboard/xib
  • 图片
  • 字符串
  • 字体
  • 多媒体

storyboard/xib

图片

字符串

字体

多媒体

7. 模块


8. Xcode工程


物理文件应该与Xcode工程文件保持同步来避免文件扩张。任何Xcode分组的创建应该在文件系统的文件体现。代码不仅是根据类型来分组,而且还可以根据功能来分组,这样代码更加清晰。

尽可能在target的Build Settings打开"Treat Warnings as Errors,和启用以下additional warnings。如果你需要忽略特殊的警告,使用 Clang's pragma feature

9. 辅助工具


  • Spacecommander

参考:

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