BreakPoint & LLDB

前言

记录一下比较常用的一些 LLDB 调试技巧.
Note: 在这里是记录一下常用的方法, 并不是完全教程哟!
Note: 前方多图(前方高能), 流量慎入. 土豪无视.

BreakPoint


工作中使用断点对程序进行调试可以说就跟家常便饭一样, 我们几乎天天都会用到. 来看看 Xcode 中如何简单的使用断点吧.
在 Xcode 中设置断点的方法有如下三种:

  1. 在代码左侧的行数那一列中点击一下, 就会出现一个断点.
  2. 用鼠标选中你希望下断点的一行, 然后按 Command + \ 来设置断点.
  3. 使用 LLDB 指令生成断点.

下图中我们看到的蓝色的矩形, 就是表示该行设置了断点.

设置断点

如果希望一个断点暂时失效, 点击蓝色矩形区域, 此时蓝色将会变为灰色, 表示断点失效, 如下图:

使断点失效

删除一个断点也很简单, 用鼠标拖住矩形区域, 在代码区域放手就可以了, 此时你会看到一个动效并且会听到的一声. 如下图红色矩形内的动效:

删除一个断点

对一个断点进行编辑, 只需要鼠标右键点击断点, 然后选择Edit Breakpoint , 如下图:

编辑断点

进入断点编辑模式后, 我们将会看到如下的对话框:

在这里编辑断点的具体内容

在这里我们可以对断点进行编辑.
Condition: 条件, 这里可以设置断点的出发条件, 例如我们在程序中有一个变量名为 index, 在这里我们设置条件 index == 1000, 代表只有当 index 变量为 1000的时候, 断点才会被触发.
Ignore: 忽略, 在这里可以设置断点被忽略多少次以后触发.
Action: 我们可以为断点触发的时候添加事件, 譬如: 语音啊, 音效啊, LLDB 指令之类的. 我们会在实战环节使用 Action 来看看效果哈, 不要急.
Options: 把这个对勾勾上的话, 程序不会终止在断点的位置, 而是会继续运行.
来看看下面这个例子:

- (void) addLabel {
    
    UILabel *label = [[UILabel alloc] init];
    label.text = @"Test LLDB";
    [self.view addSubview: label];
}

- (void) touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self addRedView];
}

我们写了一个方法 addLabel, 用来添加一个 label对象, 每次点击 self.view 都会触发 addLabel 方法, 可以看到代码中我们并没有设置 label 对象的 frame, 所以无论我们如何点击屏幕, label 都是无法显示出来的, 我们现在为 [self.view addSubview: label] 这句代码所在的行下一个断点, 然后编辑这个断点, 如下图所示:

对断点进行编辑后, 是这个样子的

我来解释一下这个断点的意义:

  1. 首先是条件, 只有在 label.text@"Test LLDB"的时候, 断点才会被触发.
  2. 其次是忽略次数, 该断点会被忽略两次, 也就是你前两次点击屏幕的时候, 该断点是被忽略掉的.
  3. 接下来是事件, 在这个断点中, 我添加了三个事件, Sound 事件: 触发断点会有一个提示音效. Shell Command 事件: 我这里设置的是 say, 就是将下面那句话读出来, %H 代表断点被Hit 的次数. Debugger Command 事件: 可以添加 LLDB 指令, 在这里我们执行了一句代码, 给 label 对象的 frame 属性进行了赋值. Log Message 事件: 在这个事件中, 你可以选择将你输入的文字打印到控制台中 或 读出来. %H 代表断点被 Hit 的次数, %B 代表函数名.
  4. 最后自动继续运行程序.

除了普通的断点, Xcode 还提供其他类型的断点, 例如 Exception Breakpoint, 我们调试程序的过程中, 我相信很多小伙伴都遇到过那种非常头疼的 Crash, 就是程序直接Crash 到了 main 函数中, 这种问题相当的不好定位, 此时可以添加一个 Exception Breakpoint断点来捕获异常.

![设置 Exception Breakpoint]
](http://upload-images.jianshu.io/upload_images/2452150-1e05b780abe42e61.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

在 Xcode 中使用 Command + 7 来查看当前程序中的所有断点. 在这里我们也可以管理断点, 例如删除断点, 关闭断点, 编辑断点等操作. 如下图:

管理断点

来看看下面这张图, 我来依次介绍下图中按钮的功能:

控制台上面的功能按钮

按钮从左至右:

  1. 收起控制台
  2. 开启/关闭 所有断点: 蓝色代表开启, 灰色代表关闭
  3. 暂停/继续 程序: 该按钮默认行为是暂停应用程序, 如果程序当前处于暂停状态, 那么点击该按钮为允许程序正常执行下去(一直执行下去, 或遇到下一个断点).
  4. 下一步: 会以黑盒的方式执行一行代码。如果所在这行代码是一个函数调用,那么就不会跳进这个函数,而是会执行这个函数,然后继续。
  5. 进入函数
  6. 退出函数

这几个按钮的作用, 大家在程序中下个断点自己点点试试就知道了, 很简单的. 这就是 Xcode 中断点的最最最基本的应用.

LLDB


介绍

语法

po & p

  • po: 打印一个 Objective-C 对象, po 指令实际上是expression -O -- 指令的别名.
  • p: 打印类似 intfloat 等基本数据类型和类似 CGRectCGPoint 等结构体. (pprint 的缩写, 你还可以使用printprinpri)

看下面这个代码块, 在 -(void) viewDidLoad 方法中声明了几个变量, 我们用 pop 来打印一下看看效果.

// ViewController.h
@interface ViewController ()
@property (nonatomic, strong) UILabel *titleLabel;
@end


// ViewController.m 
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 1. 声明两个结构体
    CGRect  rect  = CGRectMake(0, 0, 100, 100);
    CGPoint point = CGPointMake(100, 100);
    
    // 2. 声明两个基本数据类型
    NSInteger index = 100;
    CGFloat width = 200.0f;

    // 3. 声明两个 Objective-C 对象
    NSArray *array = [NSArray arrayWithObjects: self.titleLabel, @"LLDBDemo", nil];
    NSDictionary *dictionary = @{
                                 @"kMLObject" : self.titleLabel,
                                 @"kMLTitle" : @"LLDBDemo",
                                 };

    // 4. 创建 titleLabel
    self.titleLabel = [UILabel new];
    self.titleLabel.text = @"LLDBDemo";
    self.titleLabel.font = [UIFont systemFontOfSize: 16];
    self.titleLabel.textColor = [UIColor blackColor];
    [self.view addSubview: self.titleLabel];

    // 在这里打一个断点
}
    
@end

ok, 代码片段看完了, Command+R运行程序, 当程序运行到断点终止时, 我们可以再控制台使用 pop 命令来打印我们刚才声明的变量. 效果如下:

(lldb) p rect
(CGRect) $0 = (origin = (x = 0, y = 0), size = (width = 100, height = 100))

(lldb) p point
(CGPoint) $1 = (x = 100, y = 100)

(lldb) p index
(NSInteger) $2 = 100

(lldb) p width
(CGFloat) $3 = 200

(lldb) po array
<__NSArrayI 0x610000229580>(
<UILabel: 0x7fb5a2d0b6a0; frame = (0 0; 0 0); text = 'LLDBDemo'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60800008c760>>,
LLDBDemo
)


(lldb) po dictionary
{
    kMLObject = "<UILabel: 0x7fb5a2d0b6a0; frame = (0 0; 0 0); text = 'LLDBDemo'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60800008c760>>";
    kMLTitle = LLDBDemo;
}

(lldb) po self.titleLabel
<UILabel: 0x7fb5a2d0b6a0; frame = (0 0; 0 0); text = 'LLDBDemo'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60800008c760>>

除了以上你看到的这些打印, 你还可以打印更细节的东西, 以上面代码块中的 titleLabelarray 为例:

(lldb) po self.titleLabel.frame
(origin = (x = 0, y = 0), size = (width = 0, height = 0))

(lldb) po self.titleLabel.frame.size.width
0

(lldb) po self.titleLabel.text
LLDBDemo

(lldb) po [array objectAtIndex: 1]
LLDBDemo

可以看到 pop 的功能已经很强大了是不?

expression & expr & e

在我们调试程序的时候, 经常会有需要修改一个变量值得场景. 普通的调试方法, 我们可能会添加一行代码, 然后重新 Command + R 运行程序, 但这必然会无畏的消耗很多时间. 此时使用 expression 指令就非常的方便. 举例来说: 我们创建一个 UIView 实例, 添加到 self.view 中, 代码如下:

    UIView *redView = [[UIView alloc] init];
    redView.frame = CGRectMake(20, 40, 100, 100);
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview: redView];

我们可以在 [self.view addSubview: redView]; 这行代码的位置下一个断点, 然后执行如下的两行命令:

e redView.frame = CGRectMake(100, 100, 200, 200)
e redView.backgroundColor = [UIColor blueColor]

然后让程序继续运行起来看看效果, 可以看到原本应该为红色的 view 现在变成了蓝色, 并且 view 原本的位置和大小也发生了改变. 所以expression 指令不仅会改变调试器中的值, 它实际上是真正的改变了程序中的值, 有了这个东西, 代码调试起来可就太爽了. 有些时候你可能不想继续运行程序, 但是仍然想看到你修改的效果, 那怎么办? 此时就应该执行完你的修改之后, 刷新一下界面, 代码如下:

e redView.frame = CGRectMake(100, 100, 200, 200)
e redView.backgroundColor = [UIColor blueColor]
e [CATransaction flush]

刷新页面之后, 你无需继续运行程序, 就可以马上看到效果.

call

call 指令代表着调用某个方法. 实际上callpprint这三个指令都是 expression 指令的别名, 实际上的运行效果是一样的, 举例来说明, 看如下代码块:

(lldb) print self.view
(UIView *) $2 = 0x00007f9769509660
(lldb) expression self.view
(UIView *) $3 = 0x00007f9769509660
(lldb) call self.view
(UIView *) $4 = 0x00007f9769509660
(lldb) e self.view
(UIView *) $5 = 0x00007f9769509660
(lldb) p self.view
(UIView *) $6 = 0x00007f9769509660

可以很清楚地看到, 这几个指令实际上的效果是一样的.

$符号

上文中简介p 的时候, 我们看到代码块中会有这样的东西, 例如: (NSInteger) $2 = 100(CGFloat) $3 = 200. 这些以$符号开头的东东实际就是 LLDB 的命名空间的产物, 我们可以用这些东东来进行调试, 来看看下面这段:

// ViewController.m 
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    CGRect  rect  = CGRectMake(0, 0, 100, 100);

    // 在这里打一个断点
}
    
@end

viewDidLoad 中随便声明了一个CGRect 变量, 然后在下方打上一个断点, 我们用如下指令调试一下:

(lldb) p rect
(CGRect) $3 = (origin = (x = 0, y = 0), size = (width = 100, height = 100))
(lldb) e $3 = CGRectMake(10,10,10,10)
(CGRect) $4 = (origin = (x = 10, y = 10), size = (width = 10, height = 10))
(lldb) p rect
(CGRect) $5 = (origin = (x = 10, y = 10), size = (width = 10, height = 10))
(lldb) 

可以看到, 当我们 p rect 的时候, 打印的值是我们最初赋的值, 然后我们给 $3赋了一个CGRectMake(10,10,10,10)之后, 再来 p rect, 可以看到此时的 rect 变量已经被我们改变了.

Variable 变量

在某些场景中, 我们调试代码的时候可能需要创建新的变量来辅助我们, 此时我们仍然不需要修改代码后Command+R 重新跑程序, LLDB 同样提供了相应的方法. 我们可以像正常写代码一样, 创建一个 UIView 的实例, 然后将它添加到 self.view 中, 但是唯一不同的是, 声明的变量名需要以美元符号$开头. 看下面这个代码块:

(lldb) expression UIView *$view = [[UIView alloc] init]
(lldb) expression $view.backgroundColor = [UIColor blackColor]
(lldb) expression $view.frame = CGRectMake(0, 300, 100, 100);
(lldb) expression [self.view addSubview: $view]

此时我们将程序继续运行, self.view 中就会新增了一个黑色的view.

thread backtrace & bt 查看调用堆栈

bt 指令就是查看调用堆栈的信息, 调用堆栈信息在程序运行到断点时 或 崩溃时, 在 Xcode 左侧导航区域中会自动显示出来, 如下图:

Xcode 调用堆栈

图中可以清晰的看到, 程序当前正处于 Thread1addRedView方法中 (这玩意不会看的请自行 Google吧宝贝儿) . 除了在 Xcode 左侧导航区域中我们可以查看调用堆栈, 我们同样还可以使用 LLDB 为我们提供的 bt 指令进行查看. bt 指令只会查看当前线程的调用堆栈, 如果你希望查看全部的调用堆栈, 那么就需要使用 bt all 指令了. (Note: 左侧数字代表了堆栈块的编号, 这个一会我们会用到.)

frame 相关指令

讲解 frame 相关指令之前, 先来看一小段示例代码:

- (void) viewDidLoad {
    [super viewDidLoad];
    
    // 1. Create Blue View
    UIView *blueView = [[UIView alloc] initWithFrame: CGRectMake(20, 20, 100, 100)];
    [blueView setBackgroundColor: [UIColor blueColor]];
    [self.view addSubview: blueView];
    
    // 2. Add Red View
    [self addRedView];
}

- (void) addRedView {
    
    UIView *redView = [[UIView alloc] initWithFrame: CGRectMake(20, 140, 100, 100)];
    [redView setBackgroundColor: [UIColor redColor]];
    [self.view addSubview: redView]; // 断点所在行
}

Command+R 运行, 程序将会停止在 [self.view addSubview: redView]; 这一行.

frame info & fr info

查看当前堆栈信息, 以上文提到示例代码为例执行以下命令:

(lldb) frame info
frame #0: 0x000000010a952676 LLDBDemo`-[ViewController addRedView](self=0x00007f8f28409eb0, _cmd="addRedView") + 230 at ViewController.m:88

可以看到, frame info 指令可以查看当前所在堆栈的信息. 其中包括方法名文件名行号等信息.

frame select & fr sel

在工作中, 我们可能会有这样的需求, 在调试一个相对较复杂的程序时, 我们可能会打很多断点, 然后一个断点一个断点的追, 但是有时操作失误错过了某个断点, 我们又要重新来过, 这同样会消耗很多无畏的时间. 以上文的示例代码为例, 此时程序由于断点的原因停在了 [self.view addSubview: redView]; 这一行, 并且刚才我们也使用 frame info 指令查看了当前的堆栈信息, 我们当前处在addRedView方法中, 如果此时我希望修改 viewDidLoad 方法中的 blueView变量怎么办呢? 我用先 po 一下试试:

(lldb) po blueView
error: use of undeclared identifier 'blueView'

结果显然是不行的, 因为当前堆栈中, 并没有 blueView, 也就是说如果我们希望对 blueView 进行任何操作, 我们需要做的第一步, 就是切换到 blueView 对应的堆栈当中, 那我们如何切换呢? 还记得bt 指令么? 我们先用 bt 指令查看一下调用堆栈信息, 如下图:

上面这张图我只截取了一部分, 可以看到 *代表的就是当前堆栈. 还可以看出, viewDidLoad 方法的堆栈编号为 #1. 此时我们使用指令frame select 1 就能切换到 viewDidLoad 方法所在的堆栈块中:

(lldb) frame select 1
frame #1: 0x0000000108124502 LLDBDemo`-[ViewController viewDidLoad](self=0x00007f8d95905bc0, _cmd="viewDidLoad") + 354 at ViewController.m:81
   78       [self.view addSubview: blueView];
   79       
   80       // 2. Add Red View
-> 81       [self addRedView];
   82   }
   83   
   84   - (void) addRedView {

我们切换到了 blueView 对应的堆栈中, 就可以对blueView 变量进行想要的操作了. 例如:

(lldb) po blueView
<UIView: 0x7f8d9350b660; frame = (20 20; 100 100); layer = <CALayer: 0x608000028c80>>

Perfect! 完美!

thread return

thread return 指令有一个可选参数, 该参数接收一个表达之, 调用thread return 指令后将会直接跳出当前栈帧, 并且返回表达式的值. 这意味这函数剩余的部分不会被执行。这会给 ARC 的引用计数造成一些问题,或者会使函数内的清理部分失效。但是在函数的开头执行这个命令,是个非常好的隔离这个函数,伪造返回值的方式 。(查看中文原文, 英文原文)

假设我们有一个方法, 是用来判断传入的 MLPerson 对象是否是男生的, 但是现在我们希望该方法, 无论何时都返回 YES, 此时我们只需要在该方法的最前面下一个断点, 然后执行 thread return YES 就 OK 了, 如下:

- (BOOL) isBoy:(MLPerson *) person {
    
  // 在这里下一个断点, 并且执行 thread return YES 指令.
    
    return person.gender;
}

breakpoint

本文最开始的部分已经介绍过如何使用 Xcode 的 UI 界面来设置断点, 在这部分介绍如何使用 LLDB 来下断点. (breakpoint 这一部分内容出自这里).

breakpoint set -n

根据方法名设置断点, 假如我们希望给所有类中的 addLabel 方法下一个断点:

(lldb) breakpoint set -n addLabel
Breakpoint 2: 4 locations.
breakpoint set -f

针对某一文件中的某一方法设置断点, 如果方法没有写在文件中(例如父类中, Category 中), 那么设置该断点将会失败.

(lldb) breakpoint set -f ViewController.m -n addLabel 
Breakpoint 3: where = LLDBDemo`-[ViewController addLabel] + 16 at ViewController.m:93, address = 0x000000010b81f480
breakpoint set -l

针对某一文件中的某一行设置断点.

(lldb) breakpoint set -f ViewController.m -l 101
Breakpoint 5: where = LLDBDemo`-[ViewController touchesBegan:withEvent:] + 96 at ViewController.m:101, address = 0x000000010b81f5a0
breakpoint set -c

设置条件断点(对于条件断点不明确的小伙伴请在本文中第一部分查看).

(lldb) breakpoint set -f ViewController.m -n isNilString: -c string.length
Breakpoint 9: where = LLDBDemo`-[ViewController isNilString:] + 39 at ViewController.m:107, address = 0x000000010b81f637
breakpoint set -o

设置一个单次断点, 该断点只会触发一次:

(lldb) breakpoint set -f ViewController.m -n addLabel -o
Breakpoint 10: where = LLDBDemo`-[ViewController addLabel] + 16 at ViewController.m:93, address = 0x000000010b81f480
breakpoint list

使用该指令查看设置了哪些断点, 如下:

(lldb) br li 
Current breakpoints:
19: name = 'addLabel', locations = 1, resolved = 1, hit count = 4
  19.1: where = LLDBDemo`-[ViewController addLabel] + 16 at ViewController.m:93, address = 0x000000010b81f480, resolved, hit count = 4 
breakpoint disable & breakpoint enable

我们可以使用这两个指令设置断点是否开启(是否可用), 如下:

// 让断点 19 暂时失效
(lldb) breakpoint disable 19
1 breakpoints disabled.

// 让断点 19 生效
(lldb) breakpoint enable 19
1 breakpoints enabled.
breakpoint delete

该指令代表删除断点, 我们可以删除对应编号的断点, 如下:

(lldb) breakpoint delete 19
1 breakpoints deleted; 0 breakpoint locations disabled.

我们也可以删除所有的断点, 删除所有断点的时候, 我们会得到一个提示, 让我们确认是否删除所有断点, 此时我们输入y代表确认删除, 如下:

(lldb) breakpoint delete
About to delete all breakpoints, do you want to do that?: [Y/n] y
All breakpoints removed. (1 breakpoint)

如果你觉得这个提示太烦了, 你也可以使用 -f 指令来直接删除所有断点, 如下:

(lldb) breakpoint delete -f
All breakpoints removed. (1 breakpoint)

breakpoint command

在某些特定的时候, 当一个断点被触发了之后, 我们可能需要执行一些指令. 比如每次触发断点, 我们都会打印一下堆栈信息, 此时我们可以为断点需要添加bt 指令, 这样就可以避免每次触发断点后, 我们再手动输入指令了.

breakpoint command add

想为一个断点添加命令, 首先我们必须要创建一个断点, 如下:

(lldb) breakpoint set -f ViewController.m -n addLabel 
Breakpoint 19: where = LLDBDemo`-[ViewController addLabel] + 16 at ViewController.m:93, address = 0x000000010b81f480

通过设置断点, 我们可以看到, 当前这个断点的编号为 19. 那么接下来, 我们就为编号为19的断点添加指令, 如下:

(lldb) breakpoint command add -o "bt" 19

此时, 编号为 19 的断点, 就已经增加了一条 bt 指令, 当每次触发该断点的时候, 都会在控制台输出堆栈信息. 在上面代码块中的 -o指令的完整写法是--one-liner, 表示增加一条指令. 如果我们需要给该断点增加更多的指令, 此时我们就不要使用 -o 命令了, 应该像如下这么写:

(lldb) breakpoint command add 19
Enter your debugger command(s).  Type 'DONE' to end.
> bt
> continue
> DONE

在这里我们为编号为 19 的断点增加了两条指令分别是 btcontinue, 当我们指令输入完毕, 再输入 DONE 就代表结束. Note: 多次对同一个断点添加指令, 后面的指令则会覆盖前面的指令.

breakpoint command list

使用该指令, 可以查看某一断点中附加的指令, 我们尝试查看一下编号为 19 的断点中附加的指令, 如下:

(lldb) breakpoint command list 19
Breakpoint 19:
    Breakpoint commands:
      bt
      continue
breakpoint command delete

使用该指令, 可以删除某一断点中附加的指令, 我们尝试删除一下编号为 19 的断点中附加的指令, 如下:

(lldb) breakpoint command delete 19
(lldb) breakpoint command list 19
Breakpoint 19 does not have an associated command.

breakpoint这一部分中的命令, 其实完全可以使用 Xcode 为我们提供的 UI 界面来实现, 更直观, 更方便. 所以这部分基本上就是从这篇文章中摘抄过来的. 有兴趣的同学可以看看原文, 写的还是挺 Nice 的.

流程控制

流程控制这个东西, 实际上上文中也有提到过, 还记得这张图么:

如果已经忘了这几个按钮的作用了, 那就翻到本文最初的位置进行查看.
按钮从左往右依次对应的指令为:

  1. process continue & continue & c
  2. thread step-over & next & n
  3. thread step-in & step & s
  4. step-out & finish

常用快捷键

Note: 这部分同样出自这里

功能 命令
暂停/继续 Command + Ctrl + Y
断点设置/删除 Command + \
断点失效/生效 Command + Y
控制台显示/隐藏 Command + Shift + Y
光标切换到控制台 Command + Shift + C
清空控制台内容 Command + K

实战


说了这么多, 终于到了实战的时候了, 有人说, 为什么一个 LLDB 操作还要实战呢? 原因在于.... 这里面真的好多坑啊, 没有想象中的那么简单.

暂时先写这么多, 未完待续


Lemon龙说:

如果您在文章中看到了错误 或 误导大家的地方, 请您帮我指出, 我会尽快更改

如果您有什么疑问或者不懂的地方, 请留言给我, 我会尽快回复您

如果您觉得本文对您有所帮助, 您的喜欢是对我最大的鼓励

如果您有好的文章, 可以投稿给我, 让更多的 iOS Developer 在简书这个平台能够更快速的成长


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

推荐阅读更多精彩内容

  • 转载 与调试器共舞 - LLDB 的华尔兹: https://objccn.io/issue-19-2/ 推荐:i...
    F麦子阅读 3,325评论 0 10
  • LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。平时用Xc...
    CoderSC阅读 1,346评论 0 2
  • [转]浅谈LLDB调试器文章来源于:http://www.cocoachina.com/ios/20150126/...
    loveobjc阅读 2,484评论 2 6
  • LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。平时用Xc...
    小笨狼阅读 20,432评论 31 187
  • 宝宝很无聊,因为天很热,妈妈不再身边,吃不到喜欢的稀饭咸菜。 姑娘我很无聊,因为我是条老狗,看不到喜欢的书,下不到...
    纸字吹阅读 331评论 0 0