ios开发内存管理(一)

1.最近学习了一下ios内存管理方面的问题感觉这个整理的比较全面,大家可以相互交流一下:

在ObjC中,对象什么时候会被释放(或者对象占用的内存什么时候会被回收利用)?

答案是:当对象没有被任何变量引用(也可以说是没有指针指向该对象)的时候,就会被释放。

那怎么知道对象已经没有被引用了呢?

ObjC采用引用计数(reference counting)的技术来进行管理:

1)每个对象都有一个关联的整数,称为引用计数器

2)当代码需要使用该对象时,则将对象的引用计数加1

3)当代码结束使用该对象时,则将对象的引用计数减1

4)当引用计数的值变为0时,表示对象没有被任何代码使用,此时对象将被释放。

与之对应的消息发送方法如下:

1)当对象被创建(通过alloc、new或copy等方法)时,其引用计数初始值为1

2)給对象发送retain消息,其引用计数加1

3)給对象发送release消息,其引用计数减1

4)当对象引用计数归0时,ObjC給对象发送dealloc消息销毁对象

下面通过一个简单的例子来说明:

场景:有一个宠物中心(内存),可以派出小动物(对象)陪小朋友们玩耍(对象引用者),现在xiaoming想和小狗一起玩耍。

新建Dog类,重写其创建和销毁的方法:

@implementation Dog

    - (instancetype)init {

        if (self = [super init]) {

            NSLog(@"小狗被派出去啦!初始引用计数为 %ld",self.retainCount);

        }

        return self;

    }

    - (void)dealloc {

        NSLog(@"小狗回到宠物中心");

        [super dealloc];

    }

@end

在main方法中创建dog对象,給dog发送消息

//模拟:宠物中心派出小狗

Dog * dog = [[Dog alloc]init];

//模拟:xiaoming需要和小狗玩耍,需要将其引用计数加1

[dog retain];

NSLog(@"小狗的引用计数为 %ld",dog.retainCount);

//模拟:xiaoming不和小狗玩耍了,需要将其引用计数减1

[dog release];

NSLog(@"小狗的引用计数为 %ld",dog.retainCount);

//没人需要和小狗玩耍了,将其引用计数减1

[dog release];

//将指针置nil,否则变为野指针

dog = nil;

输出结果为

[34691:7638855] 初始引用计数为 1

[34691:7638855] 小狗的引用计数为 2

[34691:7638855] 小狗的引用计数为 1

[34691:7638855] 销毁Dog

可以看到,引用计数帮助宠物中心很好的标记了小狗的使用状态,在完成任务的时候及时收回到宠物中心。

思考几个问题:

1)NSString引用计数问题

如果我们尝试查看一个string的引用计数

NSString * str = @"hello guys";

NSLog(@"%ld", str.retainCount);

会发现引用计数为-1,这可以理解为NSString实际上是一个字符串常量,是没有引用计数的(或者它的引用计数是一个很大的值(使用%lu可以打印查看),对它做引用计数操作没实质上的影响)。

2)赋值不会拥有某个对象

NSString * name = dog.name;

这里仅仅是指针赋值操作,并不会增加name的引用计数,需要持有对象必须要发送retain消息。

3)dealloc

由于释放对象是会调用dealloc方法,因此重写dealloc方法来查看对象释放的情况,如果没有调用则会造成内存泄露。在上面的例子中我们通过重写dealloc让小狗被释放的时候打印日志来告诉我们已经完成释放。

4)在上面例子中,如果我们增加这样一个操作

//没人需要和小狗玩耍了,将其引用计数减1

[dog release];

NSLog(@"%ld",dog.retainCount);

会发现获取到的引用计数为1,为什么不是0呢?

这是因为对引用计数为1的对象release时,系统知道该对象将被回收,就不会再对该对象的引用计数进行减1操作,这样可以增加对象回收的效率。

另外,对已释放的对象发送消息是不可取的,因为对象的内存已被回收,如果发送消息时,该内存已经被其他对象使用了,得到的结果是无法确定的,甚至会造成崩溃。

2、自动释放池

现在已经明确了,当不再使用一个对象时应该将其释放,但是在某些情况下,我们很难理清一个对象什么时候不再使用(比如xiaoming和小狗玩耍结束的时间不确定),这可怎么办?

ObjC提供autorelease方法来解决这个问题,当給一个对象发送autorelease消息时,方法会在未来某个时间給这个对象发送release消息将其释放,在这个时间段内,对象还是可以使用的。

那autorelease的原理是什么呢?

原理就是对象接收到autorelease消息时,它会被添加到了当前的自动释放池中,当自动释放池被销毁时,会給池里所有的对象发送release消息。

这里就引出了自动释放池这个概念,什么是自动释放池呢? 顾名思义,就是一个池,这个池可以容纳对象,而且可以自动释放,这就大大增加了我们处理对象的灵活性。

自动释放池怎样创建?

ObjC提供两种方法创建自动释放池:

方法一:使用NSAutoreleasePool来创建

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];

//这里写代码

[pool release];

方法二:使用@autoreleasepool创建

@autoreleasepool {

    //这里写代码

}

自动释放池创建后,就会成为活动的池子,释放池子后,池子将释放其所包含的所有对象。

以上两种方法推荐第一种,因为将内存交给ObjC管理更高效。

自动释放池什么时候创建?

app使用过程中,会定期自动生成和销毁自动释放池,一般是在程序事件处理之前创建,当然我们也可以自行创建自动释放池,来达到我们一些特定的目的。

自动释放池什么时候销毁?

自动释放池的销毁时间是确定的,一般是在程序事件处理之后释放,或者由我们自己手动释放。

下面举例说明自动释放池的工作流程:

场景:现在xiaoming和xiaohong都想和小狗一起玩耍,但是他们的需求不一样,他们的玩耍时间不一样,流程如下

//创建一个自动释放池

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

//模拟:宠物中心派出小狗

Dog * dog = [[Dog alloc]init];

//模拟:xiaoming需要和小狗玩耍,需要将其引用计数加1

[dog retain];

NSLog(@"小狗的引用计数为 %ld",dog.retainCount);

//模拟:xiaohong需要和小狗玩耍,需要将其引用计数加1

[dog retain];

NSLog(@"小狗的引用计数为 %ld",dog.retainCount);

//模拟:xiaoming确定不想和小狗玩耍了,需要将其引用计数减1

[dog release];

NSLog(@"小狗的引用计数为 %ld",dog.retainCount);

//模拟:xiaohong不确定何时不想和小狗玩耍了,将其设置为自动释放

[dog autorelease];

NSLog(@"小狗的引用计数为 %ld",dog.retainCount);

//没人需要和小狗玩耍了,将其引用计数减1

[dog release];

NSLog(@"释放池子");

[pool release];

//创建一个自动释放池

@autoreleasepool {

    //模拟:宠物中心派出小狗

    Dog * dog = [[Dog alloc]init];

    //模拟:xiaoming需要和小狗玩耍,需要将其引用计数加1

    [dog retain];

    NSLog(@"小狗的引用计数为 %ld",dog.retainCount);

    //模拟:xiaohong需要和小狗玩耍,需要将其引用计数加1

    [dog retain];

    NSLog(@"小狗的引用计数为 %ld",dog.retainCount);

    //模拟:xiaoming确定不想和小狗玩耍了,需要将其引用计数减1

    [dog release];

    NSLog(@"小狗的引用计数为 %ld",dog.retainCount);

    //模拟:xiaohong不确定何时不想和小狗玩耍了,将其设置为自动释放

    [dog autorelease];

    NSLog(@"小狗的引用计数为 %ld",dog.retainCount);

    //没人需要和小狗玩耍了,将其引用计数减1

    [dog release];

    NSLog(@"释放池子");

}

输出结果如下:

[34819:7801589] 初始引用计数为 1

[34819:7801589] 小狗的引用计数为 2

[34819:7801589] 小狗的引用计数为 3

[34819:7801589] 小狗的引用计数为 2

[34819:7801589] 小狗的引用计数为 2

[34819:7801589] 释放池子

[34819:7801589] 销毁Dog

可以看到,当池子释放后,dog对象才被释放,因此在池子释放之前,xiaohong都可以尽情地和小狗玩耍。

使用自动释放池需要注意:

1)自动释放池实质上只是在释放的时候給池中所有对象对象发送release消息,不保证对象一定会销毁,如果自动释放池向对象发送release消息后对象的引用计数仍大于1,对象就无法销毁。

2)自动释放池中的对象会集中同一时间释放,如果操作需要生成的对象较多占用内存空间大,可以使用多个释放池来进行优化。比如在一个循环中需要创建大量的临时变量,可以创建内部的池子来降低内存占用峰值。

3)autorelease不会改变对象的引用计数

自动释放池的常见问题:

在管理对象释放的问题上,自动帮助我们释放池节省了大量的时间,但是有时候它却未必会达到我们期望的效果,比如在一个循环事件中,如果循环次数较大或者事件处理占用内存较大,就会导致内存占用不断增长,可能会导致不希望看到的后果。

示例代码:


for(int i = 0; i < 100000; i ++) {

    NSString * log  = [NSString stringWithFormat:@"%d", i];

    NSLog(@"%@", log);

}

前面讲过,自动释放池的释放时间是确定的,这个例子中自动释放池会在循环事件结束时释放,那问题来了:在这个十万次的循环中,每次都会生成一个字符串并打印,这些字符串对象都放在池子中并直到循环结束才会释放,因此在循环期间内存不增长。

这类问题的解决方案是在循环中创建新的自动释放池,多少个循环释放一次由我们自行决定。

for(int i = 0; i < 100000; i ++) {

    @autoreleasepool {

        NSString * log  = [NSString stringWithFormat:@"%d", i];

        NSLog(@"%@", log);

    }

}

3、iOS的内存管理规则

3.1 基本原则

无规矩不成方圆,在iOS开发中也存在规则来约束开发者进行内存管理,总的来讲有三点:

1)当你通过new、alloc或copy方法创建一个对象时,它的引用计数为1,当不再使用该对象时,应该向对象发送release或者autorelease消息释放对象。

2)当你通过其他方法获得一个对象时,如果对象引用计数为1且被设置为autorelease,则不需要执行任何释放对象的操作;

3)如果你打算取得对象所有权,就需要保留对象并在操作完成之后释放,且必须保证retain和release的次数对等。

应用到文章开头的例子中,小朋友每申请一个小狗(生成对象),最后都要归还到宠物中心(释放对象),如果只申请而不归还(对象创建了没有释放),那宠物中心的小狗就会越来越少(可用内存越来越少),到最后一个小狗都没有了(内存被耗尽),其他小朋友就再也没有小狗可申请了(无资源可申请使用),因此,必须要遵守规则:申请必须归还(规则1),申请几个必须归还几个(规则3),如果小狗被设定归还时间则不用小朋友主动归还(规则2)。

有兴趣的读者可以思考:

以上原则可以总结成一句简洁的话,是什么呢?

3.2 ARC

在MRC时代,必须严格遵守以上规则,否则内存问题将成为恶魔一样的存在,然而来到ARC时代,事情似乎变得轻松了,不用再写无止尽的ratain和release似乎让开发变得轻松了,对初学者变得更友好。

ObjC2.0引入了垃圾回收机制,然而由于垃圾回收机制会对移动设备产生某些不好的影响(例如由于垃圾清理造成的卡顿),iOS并不支持这个机制,苹果的解决方案就是ARC(自动引用计数)。

iOS5以后,我们可以开启ARC模式,ARC可以理解成一位管家,这个管家会帮我们向对象发送retain和release语句,不再需要我们手动添加了,我们可以更舒心地创建或引用对象,简化内存管理步骤,节省大量的开发时间。

实际上,ARC不是垃圾回收,也并不是不需要内存管理了,它是隐式的内存管理,编译器在编译的时候会在代码插入合适的ratain和release语句,相当于在背后帮我们完成了内存管理的工作。

下面将自动释放池的例子转化成ARC来看看


@autoreleasepool {

    Dog * dog = [[Dog alloc]init];

    [xiaoming playWithDog:dog];

    [xiaohong playWithDog:dog];

    NSLog(@"释放池子");

}

怎么样,是不是简洁了很多,是不是很熟悉的感觉呢。

注意:

1)如果你的工程历史比较久,可以将其从MRC转换成ARC,跟上时代的步伐更好地维护

2)如果你的工程引用了某些不支持ARC的库,可以在Build Phases的Compile Sources将对应的m文件的编译器参数配置为-fno-objc-arc

3)ARC能帮我们简化内存管理问题,但不代表它是万能的,还是有它不能处理的情况,这就需要我们自己手动处理,比如循环引用、非ObjC对象、Core Foundation中的malloc()或者free()等等

有兴趣的读者可以思考:

MRC有什么缺点?ARC有什么局限性?请列举。

3.3 ARC的修饰符

ARC提供四种修饰符,分别是strong, weak, autoreleasing, unsafe_unretained。

__strong:强引用,持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil。

比如我们常用的定时器

1NSTimer * timer = [NSTimer timerWith...];

相当于

1NSTimer * __strong timer = [NSTimer timerWith...];

当不需要使用时,强制销毁定时器


[timer invalidate];

timer = nil;

__weak:弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针。

比如避免循环引用的弱引用声明:

1__weak __typeof(self) weakSelf = self;

__autoreleasing:自动释放对象的引用,一般用于传递参数

比如一个读取数据的方法

1- (void)loadData:(NSError **)error;

当你调用时会发现这样的提示


NSError * error;

[dataTool loadData:(NSError *__autoreleasing *)]

这是编译器自动帮我们插入以下代码


NSError * error;

NSError * __autoreleasing tmpErr = error;

[dataTool loadData:&tmpErr];

__unsafe_unretained:为兼容iOS5以下版本的产物,可以理解成MRC下的weak,现在基本用不到,这里不作描述。

有兴趣的读者可以思考:

1)__strong NSTimer * timer 和 NSTimer * __strong timer哪个写法是正确的, 为什么编译器不报错?

2)使用__autoreleasing可能会遇到哪些问题?

3.4 属性的内存管理

ObjC2.0引入了@property,提供成员变量访问方法、权限、环境、内存管理类型的声明,下面主要说明ARC中属性的内存管理。

属性的参数分为三类,基本数据类型默认为(atomic,readwrite,assign),对象类型默认为(atomic,readwrite,strong),其中第三个参数就是该属性的内存管理方式修饰,修饰词可以是以下之一:

1)assign:直接赋值

assign一般用来修饰基本数据类型

@property (nonatomic, assign) NSInteger count;

当然也可以修饰ObjC对象,但是不推荐,因为被assign修饰的对象释放后,指针还是指向释放前的内存,在后续操作中可能会导致内存问题引发崩溃。

2)retain:release旧值,再retain新值(引用计数+1)

retain和strong一样,都用来修饰ObjC对象。

使用set方法赋值时,实质上是会先保留新值,再释放旧值,再设置新值,避免新旧值一样时导致对象被释放的的问题。

MRC写法如下

- (void)setCount:(NSObject *)count {

    [count retain];

    [_count release];

    _count = count;

}

ARC对应写法


- (void)setCount:(NSObject *)count {

    _count = count;

}

3)copy:release旧值,再copy新值(拷贝内容)

一般用来修饰String、Dict、Array等需要保护其封装性的对象,尤其是在其内容可变的情况下,因此会拷贝(深拷贝)一份内容給属性使用,避免可能造成的对源内容进行改动。

使用set方法赋值时,实质上是会先拷贝新值,再释放旧值,再设置新值。

实际上,遵守NSCopying的对象都可以使用copy,当然,如果你确定是要共用同一份可变内容,你也可以使用strong或retain。

@property (nonatomic, copy) NSString * name;

4)weak:ARC新引入修饰词,可代替assign,比assign多增加一个特性(置nil,见上文)。

weak和strong一样用来修饰ObjC对象。

使用set方法赋值时,实质上不保留新值,也不释放旧值,只设置新值。

比如常用的代理的声明

1@property (weak) id<mydelegate> delegate;</mydelegate>

Xib控件的引用

1@property (weak, nonatomic) IBOutlet UIImageView *productImage;

5)strong:ARC新引入修饰词,可代替retain

可参照retain,这里不再作描述。

有兴趣的读者可以思考:

1)各个属性修饰词和3.3中的修饰词的对应关系?

2)属性的本质是什么?

3.5 block的内存管理

iOS中使用block必须自己管理内存,错误的内存管理将导致循环引用等内存泄漏问题,这里主要说明在ARC下block声明和使用的时候需要注意的两点:

1)如果你使用@property去声明一个block的时候,一般使用copy来进行修饰(当然也可以不写,编译器自动进行copy操作),尽量不要使用retain。

@property (nonatomic, copy) void(^block)(NSData * data);

2)block会对内部使用的对象进行强引用,因此在使用的时候应该确定不会引起循环引用,当然保险的做法就是添加弱引用标记。

__weak typeof(self) weakSelf = self;

有兴趣的读者可以深入了解:

1、block的内部实现原理是什么?

2、从内存位置来看block有几种类型?它们的内存管理方式各是怎样的?

3、对于不同类型的外部变量,block的内存管理都是怎样的?

4 经典内存泄漏及其解决方案

虽然ARC好处多多,然而也并无法避免内存泄漏问题,下面介绍在ARC中常见的内存泄漏。

4.1 僵尸对象和野指针

僵尸对象:内存已经被回收的对象。

野指针:指向僵尸对象的指针,向野指针发送消息会导致崩溃。

野指针错误形式在Xcode中通常表现为:Thread 1:EXC_BAD_ACCESS,因为你访问了一块已经不属于你的内存。

例子代码:(没有出现错误的筒子多运行几遍,因为获取野指针指向的结果是不确定的)


Dog * dog = [[Dog alloc]init];

NSLog(@"before");

NSLog(@"%s",object_getClassName(dog));

[dog release];

NSLog(@"after");

NSLog(@"%s",object_getClassName(dog));

运行结果:


[15184:5811062] before

[15184:5811062] Dog

[15184:5811062] after

(lldb)

可以看到,当运行到第六行的时候崩溃了,并給出了EXC_BAD_ACCESS的提示。

解决方案:

对象已经被释放后,应将其指针置为空指针(没有指向任何对象的指针,给空指针发送消息不会报错)。

然而在实际开发中实际遇到EXC_BAD_ACCESS错误时,往往很难定位到错误点,幸好Xcode提供方便的工具給我们来定位及分析错误。

1)在product-scheme-edit scheme-diagnostics中将enable zombie objects勾选上,下次再出现这样的错误就可以准确定位了。

运行结果:


[15169:5801945] before

[15169:5801945] Dog

[15169:5801945] after

[15169:5801945] _NSZombie_Dog

可以看到,当运行到第六行时并没有崩溃,并给出了NSZombie的提示。

2)在Xcode-open developer tool-Instruments打开工具集,选择Zombies工具可以对已安装的应用进行僵尸对象检测。

4.2 循环引用

循环引用是ARC中最常出现的问题,对于可能引发循环引用的一些原因在前一篇文章iOS总结篇:影响控制器正常释放的常见问题中有提及,大家可以看看。

一般来讲循环引用也是可以使用工具来检测到的,分为两种:

1)在product-Analyze中使用静态分析来检测代码中可能存在循环引用的问题。

2)在Xcode-open developer tool-Instruments打开工具集,选择Leaks工具可以对已安装的应用进行内存泄漏检测,此工具能检测静态分析不会提示,但是到运行时才会出现的内存泄漏问题。

Leaks工具虽然强大,但是它不能检测到block循环引用导致的内存泄漏,这种情况一般需要自行排查问题(考验你的基本功时候到了),傻瓜式的方案当然是重写对象的dealloc方法来监测对象是否正常释放,来确认没有形成循环引用。

由于ARC中循环引用出现的几率相对较大,很多大神或者团队都提供了很多解决此问题的思路和方法,甚至开发了插件和类库来帮助开发者更好地检测问题,有兴趣的读者可以研究一下,是否好用,孰好孰坏就由读者自行评判了。

4.3 循环中对象占用内存大

这个问题常见于循环次数较大,循环体生成的对象占用内存较大的情景。

例子代码:我需要10000个演员来打仗


for(int i = 0; i < 10000; i ++) {

    Person * soldier = [[Person alloc]init];

    [soldier fight];

}

该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏,解决方法和上文中提到的自动释放池常见问题类似:在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。

for(int i = 0; i < 10000; i ++) {

    @autoreleasepool {

    Person * soldier = [[Person alloc]init];

    [soldier fight];

    }

}

然而有时候autoReleasePool也不是万能的:

例子:假如有2000张图片,每张1M左右,现在需要获取所有图片的尺寸,你会怎么做?

如果这样做


for(int i = 0; i < 2000; i ++) {

    CGSize size = [UIImage imageNamed:[NSString stringWithFormat:@"%d.jpg",i]].size;

    //add size to array

}

用imageNamed方法加载图片占用Cache的内存,autoReleasePool也不能释放,对此问题需要另外的解决方法,当然保险的当然是双管齐下了


for(int i = 0; i < 2000; i ++) {

        @autoreleasepool {

        CGSize size = [UIImage imageWithContentsOfFile:filePath].size;

        //add siez to array

    }

}

4.4 无限循环

这个是比4.3更极端的情况,无论你出于什么原因,当你启动了一个无限循环的时候,ARC会默认该方法用不会执行完毕,方法里面的对象就永不释放,内存无限上涨,导致内存泄漏。

例子:

NSLog(@"start !");

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        BOOL isSucc = YES;

        while(isSucc) {

        [NSThread sleepForTimeInterval:1.0];

        NSLog(@"create an obj");

    }

});

输出结果为

[7026:3555827] start !

[7026:3556236] create an obj

[7026:3556236] create an obj

[7026:3556236] create an obj

[7026:3556236] create an obj

[7026:3555827] dealloc

[7026:3556236] create an obj

[7026:3556236] create an obj

[7026:3556236] create an obj

可以看到,当控制器释放后该循环还在继续。

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

推荐阅读更多精彩内容

  • 在此特此声明:一下所有链接均来自互联网,在此记录下我的查阅学习历程,感谢各位原创作者的无私奉献 ! 技术一点一点积...
    远航的移动开发历程阅读 11,076评论 12 197
  • 播客节目《新闻酸菜馆》最新一期的话题是吐槽《舌尖上的中国2》。主持们说,《舌尖1》是新的尝试,没有被过分干扰,讲的...
    努力攒钱的二花阅读 1,050评论 3 5
  • 感觉2017年刚开始就是拼命地活着。 12号刚带完辅导班,匆匆忙忙和曦曦吃个饭,收拾到13号凌晨5点,稀稀拉拉的东...
    白藜芦醇阅读 202评论 3 1
  • 首先男女主人公贝儿和王子一个貌美一个帅气颜值高。 其次城堡建筑风格华美高贵,贝儿居住的乡村生活气息浓厚 再次影片先...
    乘格帆阅读 434评论 0 2
  • 脂类是人体需要的重要营养素之一,它与蛋白质、碳水化合物是产能的三大营养素,在供给人体能量方面起着重要作用。 ...
    约梵阅读 927评论 0 0