App Programming Guide for iOS读书笔记

这是阅读该指南的一些笔记,说说是笔记,其实就是把一些自己决定重要的知识给翻译了一遍,因为英文读着读着就把前面的给忘了。。。所以就打算记录一点。该文章会慢慢更新,这段时间打算将几篇重要的指南都重新看一遍,每天都会更新这些笔记,英语不大好,可能有翻译错误的地方,请大家指出,谢谢!

APP状态

在任何时间上,你的APP只有一个状态,看表Table 2-3.系统切换APP状态来响应系统事件.举例,当用户按下Home键,一个电话打来,或者其他中断的行为,当前运行的APP改变状态来响应.Figure 2-3 显示一个APP改变状态的路径.

状态 描述
没有运行 该APP没有运行或者正在运行但被系统终止
不活跃的 该APP在前台运行,但没有接受到事件,APP一般都处在这个状态
活跃 该APP在前台运行并且接受事件,这是前台运行APP正常状态
后台 该APP在后台且在执行代码,大部分APP在该状态会停留一段事件
暂停 该APP在后台且没有执行代码,系统会自动将APP设置到这个状态并不会通知APP.暂停后,APP保留在内存内但不会执行代码.当内存低时,系统会杀死APP来使前台有更多内存

大多数状态的改变都伴随着系统方法的调用。你可以在这些方法内响应状态。

  • application:willFinishLaunchingWithOptions: 启动时执行第一次代码
  • application:didFinishLaunchingWithOptions:允许在程序显示之前进行最终的初始化
  • applicationDidBecomeActive: 即将成为前台应用
  • applicationWillResignActive: 过渡状态,即将不活跃
  • applicationDidEnterBackground: 正在后台运行,会随时暂停
  • applicationWillEnterForeground: 应用从后台到前台,但不活跃
  • applicationWillTerminate: 应用正在被终止,暂停不会调用这个方法

本节讲了应用各种运行的状态和appdelgate里一些方法具体是什么时候被调用的

APP终止

APP应该随时准备被终止而不应该等待保存用户数据或者执行关键任务,系统发起的终止是APP生命周期里的一个正常环节。
如果APP正在后台运行且没有暂停,系统会在应用终止前调用applicationWillTerminate,如果系统重启,不会调用这个方法

线程和并发

系统创建主要的线程,你也可以创建额外的线程。对于iOS APP,你应该首选GCD,操作队列和其他的异步接口而不是自己创建和管理线程,使用GCD可以让你更好的专注于工作,让系统去处理线程相关的事情。

在思考线程和并发时,你应该注意以下几点

  • 包括视图,核心动画,还有很多UIKit的类通常都运行在主线程上,但也有例外,譬如图像的操作就在后台
  • 耗时操作应该在后台处理,包括网络请求,文件访问或者大量的数据处理都应该使用异步的GCD或者操作队列管理。
  • 在启动时,应该尽快的显示界面,除了必须的建立视图的操作,其他的耗时操作都应该放在后台

更多的GCD或者操作队列信息请看 Concurrency Programming Guide

本节讲了耗时操作能放在子线程就丢到子线程,除了创建视图,刷新视图等等。。

Strategies for Handling App State Transitions

可以在UIApplicationDelegate协议方法里面知道状态改变。

What to Do at Launch Time

当APP启动时(无论是前台还是后台),在application:willFinishLaunchingWithOptions:application:didFinishLaunchingWithOptions:方法内做以下几点:

  • 检查Info.plist文件
  • 初始化关键数据
  • 准备显示视图
    • OpenGL ES应该在applicationDidBecomeActive:方法内绘制
    • 显示窗口在application:willFinishLaunchingWithOptions:方法内,显示视图在application:didFinishLaunchingWithOptions:方法内

在启动时候要尽快完成,超过5秒系统会自动杀死该进程。

The Launch Cycle

应用进入运行状态流程图

应用在后台会处理事件,并会在某个时间点暂停。它仍会加载界面文件但不会显示

可以通过applicationState属性区别当前应用状态,在前台时为UIApplicationStateInactive,后台时为UIApplicationStateBackground

Launching in Landscape Mode

应用必须设置朝向,如果支持横向和纵向,那么默认是纵向。如果只支持横向,需要做到以下几点:

  • 在info.plist文件中添加UIInterfaceOrientationKey值,设置属性为UIInterfaceOrientationLandscapeLeft或者 UIInterfaceOrientationLandscapeRight.
  • 确保约束是正确的

What to Do When Your App Is Interrupted Temporarily

当应用暂时中断时,系统仍在前台但是不能接受触摸事件,但可以接受类似陀螺仪事件,你需要在applicationWillResignActive:方法内做以下操作:

  • 保存数据或者关键信息
  • 停止定时器或者其他周期性任务
  • 停止数据查询
  • 不初始化新的任务
  • 停止视频(除了AirPlay)
  • 停止游戏
  • 终止GCD或者操作队列

在应用进入活跃状态时,在applicationWillResignActive方法内你应该重启定时器,恢复队列。游戏不应该恢复,这应该让用户手动开始。

Responding to Temporary Interruptions

假如有电话打来,应用会进入inactive状态,直到用户结束通话,应用回到活跃状态或者后台。

用户拉下通知横幅会造成应用进入inactive状态。
用户按下锁屏键,系统禁用触摸事件并且使应用进入后台,这时候数据文件会被加密保护起来。

What to Do When Your App Enters the Foreground

当应用进入前台状态时,applicationWillEnterForeground:方法会撤销所有applicationDidEnterBackground:方法里的事情,并在applicationDidBecomeActive:方法里做激活任务。

UIApplicationWillEnterForegroundNotification这个枚举也可以用来监听应用进入前台状态。

Be Prepared to Process Queued Notifications

Event Notifications
配件断开或链接 EAAccessoryDidConnectNotification
屏幕旋转 UIDeviceOrientationDidChangeNotification
完整一天过去 UIApplicationSignificantTimeChangeNotification
偏好设置改变 NSUserDefaultsDidChangeNotification
语言改变或者区域改变 NSCurrentLocaleDidChangeNotification

应用在后台时仍然能调用setNeedsDisplay或者setNeedsDisplayInRect:,但是因为视图是不显示的,所以会在下次应用进入前台时更新视图。

What to Do When Your App Enters the Background

从前台进入后台,在applicationDidEnterBackground:方法里做以下几点:

  • 给你应用准备一张图片。如果用户退出前台前的那个页面包含了敏感信息,你应该隐藏或者修改这张图片在applicationDidEnterBackground:方法返回前。
  • 保存有关应用的状态信息
  • 释放不需要的内存。因为系统会杀掉占用内存大的应用,所以应该让你的应用在进入后台前释放图片资源,缓存和一些不需要的数据。

应用在applicationDidEnterBackground:方法里大概有5秒的时间来完成任务。实际上,这个方法应该尽可能快的返回。如果方法没有在时间耗尽前返回,系统会杀掉应用并释放内存。如果你需要长时间进行任务,你应该在beginBackgroundTaskWithExpirationHandler:方法里进行,并且在secondary线程上。不管你在applicationDidEnterBackground:方法内进行任何操作都必须在5秒内返回。

Note:UIApplicationDidEnterBackgroundNotification这个枚举可以知道系统进入后台

The Background Transition Cycle

当用户按下Home键,关机键或者打开其他应用,前台应用会变到inactive状态然后到background状态。这些转变结果会调用到applicationWillResignActive:applicationDidEnterBackground:方法。在applicationDidEnterBackground:方法返回后,大多数应用会在不久后变到suspended状态。如果应用需要特殊的后台任务(像播放音乐)或者执行额外的长时间任务,background状态会延长。

Prepare for the App Snapshot

applicationDidEnterBackground:方法返回不久后,系统会生成一张应用快照用于显示。同样,当应用被唤醒来处理一些后台任务,系统也许需要生成一张新的快照来反映改变。

如果你想对显示的快照做更改,你需要调用snapshotViewAfterScreenUpdates:来更新视图,这个方法能马上更新视图,调用setNeedsDisplay方法是没用的,这个上面文档讲过了。


Strategies for Implementing Specific App Features

不同的应用有不同的需要,但是有些行为应该是相同的。下面的章节会介绍这些特定的功能如何实现。

Privacy Strategies

保护用户隐私对于应用来说是很重要的。系统已经提供了保护隐私的方法。

Protecting Data Using On-Disk Encryption

设备锁屏以后,是不能访问被保护的文件的,就算文件是应用创建的也不可以。想访问保护文件必须在设备解锁之后。

数据保护在大部分设备上是可用的,只需要遵循以下几点:

  • 文件系统必须支持数据保护,这在大部分设备上是可行的
  • 用户必须设置密码锁

使用NSDataNSFileManager类可以通过添加属性来设置保护等级。当写入新文件时,你可以使用NSDatawriteToFile:options:error:方法。对于已存在的文件,你可以使用NSFileManagersetAttributes:ofItemAtPath:error:方法,使用这些方法,你可以设置以下几个保护等级:

  • No protection -> 文件被加密但是当设备被锁屏时不能被密码保护。选择NSDataWritingFileProtectionNone这个枚举。
  • Complete -> 文件被加密且不能被访问当设备被锁屏。选择NSDataWritingFileProtectionComplete枚举。
  • Complete unless already open -> 文件被加密且不能被访问当设备被锁屏,当文件被访问时设备锁屏,应用仍然可以在锁屏状态下访问文件。选择NSDataWritingFileProtectionCompleteUnlessOpen枚举
  • Complete until first login -> 文件被加密且不能访问直到设备启动,用户解锁一次。

如果你保护了文件,那么应用会随时不能访问文件。当设备锁屏时,你可以通过几点知道是否可以访问文件:

  • 可以在app delegate里实现applicationProtectedDataWillBecomeUnavailable:applicationProtectedDataDidBecomeAvailable:方法
  • 任何对象都可以注册UIApplicationProtectedDataWillBecomeUnavailableUIApplicationProtectedDataDidBecomeAvailable通知
  • 可以通过UIApplicationprotectedDataAvailable属性来知道当前是否可以访问文件

反正就是推荐文件保护。

Identifying Unique Users of Your App

你应该区别每一个用户,并且这一行为应该是透明的。

在以下几种情况下,也许你需要这样做

  • 登录
  • 给不同的用户观看不同的广告

如果你需要在不同设备上辨认是否是一个用户,你需要提供自己的一套识别系统。

Supporting Multiple Versions of iOS

一个在多个版本运行的应用应该检查系统版本,防止在旧系统上使用了新系统的API。

有几个检查方法你可以选择:

  • 确定这个类是否存在
if ([UIPrintInteractionController class]) {
   // Create an instance of the class and use it.
}
else {
   // The print interaction controller is not available so use an alternative technique.
}
  • 判断一个类是否可以使用这个方法,通过instancesRespondToSelector:类方法或者respondsToSelector:实例方法。
  • 判断基于C语言的函数是否可用
if (UIGraphicsBeginPDFPage != NULL) {
    UIGraphicsBeginPDFPage();
}

想知道这方面更多的知识,可以看 SDK Compatibility Guide.

Preserving Your App’s Visual Appearance Across Launches

Enabling State Preservation and Restoration in Your App

状态保存和恢复不是自动的,系统必须选择使用。如果要使用这些功能,需要实现以下方法
application:shouldSaveApplicationState:application:shouldRestoreApplicationState:

一般来说,返回YES是表明功能可用。但是有些时候需要返回NO,比如应用更新了,你不应该恢复旧的页面。

The Preservation and Restoration Process

在保存和恢复过程中,应用也有少量的任务:

  • 保存过程
    • 告诉UIKit支持保存
    • 告诉UIKit什么控制器和视图需要被保存
    • 给保存的对象编码
  • 恢复过程
    • 告诉UIKit支持恢复
    • 提供需要的UIKit对象
    • 让保存的对象恢复原样

Figure 5-1 显示了简单的视图控制器的层次结构。如果没有状态恢复,只有main storyboard文件里的控制器在随后的启动中恢复。当你的应用支持状态恢复,你可以保存所有的控制器

UIKit保存对象需要一个保存identifier,假如控制器没有这个identifier,他和他的子视图都不会被保存。Figure 5-2 展示了部分拥有identifier的控制器

这些功能对于应用来说也许是毫无意思的,因为UIKit本身就能简单的保存和恢复。

对于你保存的所有控制器 ,你也需要决定如果去恢复它们。UIKit 提供了两种方式去创建对象。你可以去重新创建它们或者通过 view controller 去恢复它们。restoration class实现UIViewControllerRestoration协议,并且在恢复的时候去负责寻找或者创建指定的对象。这里有几个建议:

  • 如果你的控制器是通过 main storyboard 文件加载的,就不用这样搞了,让UIKit自己去恢复吧
  • 如果不是第一种情况,那么最简单恢复办法就是让每一个控制器都实现协议。

在保存过程中,UIKit 保存对象并且写入到磁盘。每个控制器也有一次机会来保存数据。

Flow of the Preservation Process

Figure 5-3 展示了直到保存状态前的高级事件和应用是如何被影响的。在保存之前,UIKit 会调用application:shouldSaveApplicationState:方法,如果返回YES的话会开始保存视图。

下次应用启动的时候会自动寻找保存状态的文件,如果有的话就恢复。因为这些文件只适用于上次和当前的周期,在启动后会删除上次的保存状态文件。在恢复过程中有错误也会删除保存状态文件。举例,在一次恢复过程中应用崩溃了,那么系统会自动删除文件防止下次恢复再崩溃。

What Happens When You Exclude Groups of View Controllers?

Figure 5-5 展示了当导航控制器没有保存identifier,他的子控制器和视图都不会被保存

即使你不保存控制器,也不意味着所有控制器从视图层次中消失了。举个例子,如果有控制器是从 storyboard 文件中加载的,他会一直显示。

Checklist for Implementing State Preservation and Restoration

你如果想通过代码来实现保存和恢复状态,你应该看看以下几点:

  • 实现application:shouldSaveApplicationState:application:shouldRestoreApplicationState:方法
  • 给你想要保存的控制器添加非空字符串的restorationIdentifier属性
  • application:willFinishLaunchingWithOptions:展示窗口
  • 给适当的控制器指定 restoration classes
  • 使用encodeRestorableStateWithCoder:decodeRestorableStateWithCoder:编码解码控制器状态
  • 编码解码任意的版本信息或者状态信息使用application:willEncodeRestorableStateWithCoder:application:didDecodeRestorableStateWithCoder:方法
  • 数据源实现UIDataSourceModelAssociation协议,虽然这不是必须的,但是这个协议可以帮助我们保存选中的和可见的视图

Preserving the State of Your View Controllers

保存单独的控制器状态,你需要做到以下几点:

  • 必须有 restoration identifier
  • 必须在启动的时候创建或者找到新的控制器
  • 可选的来实现encodeRestorableStateWithCoder:decodeRestorableStateWithCoder:方法

Marking Your View Controllers for Preservation

UIKit 只会恢复拥有有效 restorationIdentifier 的对象。

恢复路径是从上往下。

Restoring Your View Controllers at Launch Time

在恢复过程中,UIKit 会通过几种办法去恢复:

  • 如果控制器有 restoration class ,那么 UIKit 会去恢复控制器。调用viewControllerWithRestorationIdentifierPath:coder:方法,如果返回nil的话就代表不想创建控制器,UIKit 会放弃寻找。
  • 如果控制器没有 restoration class ,UIKit 会让 app delegate 去恢复控制器。调用application:viewControllerWithRestorationIdentifierPath:coder:
  • 如果控制器已经存在正确的恢复路径,UIKit 会使用这个对象
  • 如果控制器是通过 storyboard 去加载的,UIKit 会通过 storyboard 去寻找和创建控制器。

以下代码展示了如果在恢复过程中创建控制器。

+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
                      coder:(NSCoder *)coder {
   MyViewController* vc;
   UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
   if (sb) {
      vc = (PushViewController*)[sb instantiateViewControllerWithIdentifier:@"MyViewController"];
      vc.restorationIdentifier = [identifierComponents lastObject];
      vc.restorationClass = [MyViewController class];
   }
    return vc;
}

Inter-App Communication

应用之间可以通过 URL 来进行通信。

Supporting AirDrop

AirDrop 可以发送图片,文件,URLs,和其他类型的数据给附近的设备。AirDrop 通过 peer-to-peer 网络来寻找附近的设备。

Sending Files and Data to Another App

如果想通过 AirDrop 发送文件和数据,使用UIActivityViewController对象。当你创建了这个控制器,
你可以指定你想显示什么。你也可以显示自定义对象,只要遵守UIActivityItemSource协议。

你可以通过excludedActivityTypes属性来指定不显示的类型。当显示一个 activity view controller 在iPad上时,你必须使用popover

在iPhone上显示 activity view controller

- (void)displayActivityControllerWithDataObject:(id)obj {

   UIActivityViewController* vc = [[UIActivityViewController alloc]

                                initWithActivityItems:@[obj] applicationActivities:nil];

    [self presentViewController:vc animated:YES completion:nil];
}

Receiving Files and Data Sent to Your App

使用 AirDrop 接受文件你需要注意以下几点:

  • 在 Xcode 里,声明你的应用能打开的文件
  • 在 app delegate 里,实现application:openURL:sourceApplication:annotation:方法来接受数据
  • 在 Documents/Inbox 里查看文件,如果需要的话把文件移出来(需要修改的话)

传输到 Documents/Inbox 中的文件你只能读取或者删除,但不能修改

Using URL Schemes to Communicate with Apps

Apple 内置了很多 URL schemes。如果你的 URL 定义了一个和 Apple 相同的东西,那么在你应用启动时会打开 Apple 的应用。

Sending a URL to Another App

当你实现自定义的 URL scheme 来发送数据给别的应用,调用openURL:方法。

以下代码展示了一个应用如何打开另一个应用

NSURL *myURL = [NSURL URLWithString:@"todolist://www.acme.com?Quarterly%20Report#200806231300"];

[[UIApplication sharedApplication] openURL:myURL];

如果应用自定义了一个 URL scheme ,请看Implementing Custom URL Schemes

PS: iOS9新增了一个白名单,苹果规定开发者只能设置50个 URL scheme 来打开别的应用,在openURL:之前还需要做个判断canOpenURL:,返回到前一个应用不用这样做。

Implementing Custom URL Schemes

如果你的应用可以接收特定的 URLs,你应该在系统里注册相应的 URL schemes。

Registering Custom URL Schemes

在应用里注册 URL 类型,你应该在 Info.plist 文件中加入CFBundleURLTypes key。这个 key 包含一个字典数组,每一个都定义了一个 URL schemes。

Key Value
CFBundleURLName 字符串包含了一个 URL scheme。为了确保唯一性,推荐指定一个反向域名格式的标识,举个例子, com.acme.myscheme。
CFBundleURLSchemes 字符串数组包含了 URL scheme 名字,举个例子,http, mailto, tel, 和 sms。

Note: 如果多个第三方应用使用了相同的 URL scheme,目前还没有好的办法来解决。

Handling URL Requests

每个应用都有自己自定义的 URL scheme 并且也该知道如何去处理它们。你应该在 delegate 中实现以下方法:

  • 使用application:willFinishLaunchingWithOptions:application:didFinishLaunchingWithOptions:方法来检查 URL 的信息并决定是否打开。如果其中一个方法返回 NO,你应用处理 URL 的代码不会被调用。
  • 使用application:openURL:sourceApplication:annotation:方法来打开文件。(iOS9废弃了,应该使用application:openURL:options:

Figure 6-1 展示了在一个要求打开网址的应用上显示修改好的启动顺序

Figure 6-2 展示了切换到前台状态打开 URL

Displaying a Custom Launch Image When a URL is Opened

应用支持自定义的 URL schemes 可以提供自定义的启动图片。当系统启动你的应用通过 URL 时并没有有效的快照,他会显示你指定的启动图片。指定一个启动图片,提供一张使用以下名字格式的 PNG 图片:

basename代表了基本的图片名字,假如名字是 Default,url_scheme 是你的 URL scheme 的一部分名字,假如 URL scheme 是 myapp,那么你的启动图片名字就是 Default-myapp@2x.png。


Performance Tips

本章讲述整体性能的几点。

Reduce Your App’s Power Consumption

功耗始终是移动设备上的一个大问题。你可以通过优化以下几个功能来提高电池寿命:

  • CPU
  • WiFi,蓝牙,基带
  • 定位
  • 加速剂
  • 访问硬盘

你应该经常使用 Instruments 来优化算法。但是即使最优算法也会对电池寿命有意向。你应该在写代码的时候注意以下几点:

  • 避免使用 polling。polling 不会让 CPU 进入休眠。应该使用NSRunLoop或者NSTime代替。
  • 设置idleTimerDisabled属性为 NO,这个属性默认就是 NO,当用户不再输入时会关闭屏幕。如果你需要屏幕不被关闭,你应该修改代码而不是设置属性为 YES。
  • 尽可能的合并一些任务来增加 CPU 空闲时间。因为一些很小的任务常常会唤醒 CPU。
  • 避免经常访问硬盘。
  • 需要多少内容绘制多少内容。绘制很耗电,不要依靠硬件来控制帧数,应该是内容需要几帧绘制几帧。
  • 如果你使用UIAccelerometer来接收加速计时间。PS:看了下好像这个类在5.0就废弃了。

访问网络数据是很耗电的,通过以下几点来使访问数据次数最小化:

  • 仅在需要的时候访问网络并且不 poll 服务。
  • 当你必须访问网络时,发送最少的数据量。
  • 发送数据而不是发送数据包。系统会在空闲的时候关闭 WiFi 和 无线电。当你使用NSURLSession来进行多个上传或下载任务时,应该让他们同时进行而不是一个个来。系统会自动管理队列。
  • 尽可能的使用WiFi来访问网络。
  • 如果使用定位,你应该使用最适合的 distance filter 和精确度

Instruments 应用包含了几个收集电池信息的 Instruments。还可以收集指定硬件耗电量。你也可以使用诊断记录来收集信息。PS:这个在手机设置里可以看。

Use Memory Efficiently

系统的可用内存会影响应用的性能。

Observe Low-Memory Warnings

系统发出内存警告时应该移除不需要的对象。回应这个警告是很重要的否则系统可能会终止应用。系统通过以下几个 API 来发送内存警告:

  • app delegate 的applicationDidReceiveMemoryWarning:
  • UIViewControllerdidReceiveMemoryWarning
  • UIApplicationDidReceiveMemoryWarningNotificationnotification通知
  • GCD 的DISPATCH_SOURCE_TYPE_MEMORYPRESSURE类型。这是你唯一能用来辨别内存压力大不大。

收到内存警告你应该释放掉不需要的内存。例如清理缓存,释放图片。如果你有一个不用的大数据,应该把它写进磁盘。

你可以通过UIApplicationDidReceiveMemoryWarningNotification通知来直接删除不需要的资源。

Reduce Your App’s Memory Footprint

Table 7-1 减少应用内存占用空间

Tip Actions to take
清楚内存泄露 在iOS中内存是很关键的资源,你的应用不应该有内存泄露。使用 Instruments 查看是否有内存泄露。
资源文件尽可能的小 文件写入到磁盘之前会在内存中存在。尽可能的压缩图片文件(PNG 图片是 iOS 首选的图片格式)。
懒加载资源 在需要使用要资源时再加载。

后面这些很多都在之前的文档有讲到,感兴趣的朋友可以自己去翻阅一下。

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

推荐阅读更多精彩内容