iOS 静态库的制作 Unity依赖iOS原生工程库

主要内容翻译自:《How to Create a Framework》


在iOS中如何创建framework呢?

跟着本文的教程,你将学会以下的技能:

  • 在Xcode中创建基本的静态库工程(static library)
  • 创建一个依赖于你创建的静态库工程的应用
  • 探索怎样将静态库工程转换为框架(framework)
  • 最后,你将学会怎样将图片资源打包到自定义的资源包( resource bundle),并在你制作的框架中进行资源引用(其中,我将补充nib文件的引用)
  • 最后的最后,我将补充如何在你制作的框架中引用第三方静态库(路径引用,非拷贝第三方库)

准备开始

这篇文章的目的不仅仅是像其他文章一样简单的介绍制作静态库,还会详细的讲解其中的原理。
首先请下载 RWKnobControl 源码。当你跟着学习创建静态库的教程后,你将学会怎样来使用它们。

什么是框架呢?(framework 库、框架)

框架或者叫做库就是资源的集合. 里面集成了一个Xcode能够很容易地纳入到工程里的一个独立的结构,里面包含了静态库和头文件。

在OS’X系统中,是可以创建动态链接库的。通过动态库链接技术,程序可以不用再次来链接库就可以实现库的更新(热更新技术的一种)。在运行时中,只会拷贝一份静态库中的代码,就可以让这份代码共享到所有使用它的进程,所以呢,这种技术可以减少内存的开销和提高系统的性能。你看,这是不是一种很牛掰的技术呢?

在iOS系统中,你不能够添加自定义的库到你的程序中(其实是可以的,但是不能上架,会被拒绝而已。所以你可以使用在企业应用的分发中。 你看,就象苹果拒绝了JSPatch一样。);所以,你只能够使用苹果提供的动态链接库。(有什么?CoreFundation..UIKit 等等)。

但是!这并不意味着在iOS中使用库是不行的。客观请看:静态链接库是可以用的哦,上面说的是动态链接库。

创建一个静态库工程

打开Xcode并选择创建一个Cocoa Touch Static Library工程。如下图:(掩饰Xcdoe工程版本为8.2.1,其他版本的界面可能有出入,但是你还是会找到我所提到的东西的,耐心点找找。)

创建静态库.png

并将工程名字命名为:RWUIControls(可不可以不是这个名字呢?后面教程使用了动态打包framework bundle的脚本,看了讲解后 你就知道能不能了。)

一个静态库工程是由编译成静态库的头文件和实现文件来组成。

为了使你的静态库用起来很简便,你只需要暴露出一个头文件就行了,是怎样的一个头文件呢?头文件里需要暴露出你需要使用者使用的类的头文件就行了。

当你创建 RWUIControls这个静态库工程的时候,工程默认为你创建了RWUIControls.h和RWUIControls.m两个文件,其中RWUIControls.m是不需要实现的,你可以删了它(move to trash)。
打开RWUIControls.h头文件,在里面添加如下库:(因为该静态库使用了UIKit的相关接口,你可以依照你制作的库的实际情况来添加你需要的库。)
#import <UIKit/UIKit.h>

当然了,你会发现你无法import。因为静态库工程无法找到。如何来解决呢,首先选择 Build Phases来展开Link Libraries面板。点击添加按钮+ 来添加你需要的库(该演示教程需要添加UIKit)

导入相关库.gif

如果就这样创建一个静态库,它是不起任何作用的,因为静态库需要联合头文件来起作用。
接下来,你需要创建一个新的构建方式属性(Build Phase),它的作用是告诉编译器哪些头文件是可以提供给外部访问的。

在Xcode的顶部菜单栏选择 Editor\Add Build Phase\Add Copy Headers Build Phase(如果你发现你找不到 Add Copy Headers Build Phase, 首先保证当前Xcode页面位于Build Phase 页面;如果你发现 AddCopyHeadersBuildPhase是灰色不可选的,先尝试点击下Xcode的空白处再来选择)

暴露头文件.gif

请牢记上图操作:将你需要提供给外部访问的头文件拽入 Copy Headers的 Public中(Private:很明显的意思是对外不开放 Project:也是不开放的哦,你只需记住Public Private 的使用。)


创建一个UI控制类

静态库工程的配置已经设置好了,你需要将你想打包到静态库的文件导入 RWUIControls下。将你之前下载的打包文件解压,找到RWKnobControl,并将它拖入到RWUIControls中 并选择 copy item。如下图:

这样的操作将会添加实现文件到编译列表中,默认情况下,头文件会被添加Project列中(也同样意味着 private)

接下来,你需要将RWKnobControl.h弄到Public中去。可以拖拽到Public,也可以如下图方式进行设置(注意红色标注):


除此之外,你需要将你想暴露出的头文件添加到静态库的头文件中。这样做的好处在于,以免使用者在库里去查找他想用的头文件,他只需要导入静态库的头文件就行了。请移步到 RWUIControls.h(这个是你创建静态库工程时自动生成的,你还记得么?),并添加如下代码:
#import <RWUIControls/RWKnobControl.h>

配置 (Configuring Build Settings)
到此为止,你已经学会了创建静态库的大部分知识了,再坚持下,跟着做以下配置,你的库对于使用者来说会更加友好。

首先,你需要为公开头文件提供一个路径。这种做法保证了静态库在使用过程中能够定位到相应的头文件。

在工程导航栏中点击项目(TARGETS),并且选择RWUIControls, 选择Build Settings标签,在搜索框中输入 public header. 双击 *Public Headers Folder Path 进行如下图的输入:

待会儿将会为你展示这个路径下的东西。

接下来,你需要做其他的设置,尤其是那些在静态库中的配置。编译器为你提供了选择,是否自动移除从来没有使用过的僵尸代码(dead code).并且你还可以选择移除debug标记等等。

为别人创建了第三方静态库,最好是将以上提到的两点设置选择为NO,如下设置:

  • Dead Code Stripping – Set this to NO
  • Strip Debug Symbols During Copy – Set this to NO for all configurations
  • Strip Style – Set this to Non-Global Symbols

command + B进行编译一下。你好像什么都没看到,那就对了,说明没有警告和错误提示。再来创建一下,这里我们选择 iOS Device 进行编译。创建完成后,你将会在Xcode工程左边的目录 Products下看到 libRWUIControls.a由之前的红色变成了黑色(如果没有,请选择模拟器编译一次,再选择真机编译一次;如果还是红色,多半是Xcode的问题,clear或者重启试试。),鼠标右键选择 Show in Finder 你将会看到如下目录:

发现什么了吗?这个include文件夹就是你之前在配置里设置的(include/$(PROJECT_NAME)),里面包含的两个头文件,就是你暴露出来的。

创建一个使用你创建的静态库的工程

在此教程下,你将创建一个使用你自己创建的静态库的工程。
先关闭静态库工程。然后我们来创建一个新工程。选择 Single View Application,并且给工程命名为UIControlDevApp.将类文件的前缀添加为RW,并且将工程修改为只支持 iPhone。最后将工程保存到RWUIControls静态库工程同一级目录中。

在UIControlDevApp工程中引入RWUIControls.xcodeproj(拖拽就行了),如下图:

诶,这种目录结构是不是好像在哪见过呢?CocoaPods工程! 这样做的好处在哪里呢,你可以在一个工作空间中修改静态库,同时也可以运行依赖该库的工程,进行效果校对。这样很方便。

接下来,请将你之前下载的代码包中的DevApp文件夹一起拖拽入 UIControlDevApp中(选择Copy),如下图:

接下来,你将在你的示意工程中配置静态库依赖:

  • 在工程导航栏中选择UIControlDevApp工程
  • 在UIControlDevApp中选择Build Phases 标签
  • 打开Target Dependencies 面板,选择+进行添加
  • 找到RWUIContols 静态库,选择添加。

为了反向关联静态库,展开Link Binary With Libraries 面板,添加 libRWUIControls.a 如下图:

添加依赖

好了,到了这一步 你可以运行你创建的工程了。如果你严格按照之前的步骤,你将会看到如下图所示的效果:

创建脚本

创建framework

迄今为止,你已经学会了创建静态库。接下来,我们将来创建framework。
在之前创建静态库的时候,你已经完成了大部分创建framework的工作,接下来,你只需要跟着以下步骤就可以完成framework的创建。(看着有点复杂)

  • framework的目录结构。framework有着一个特别的目录结构,为了让Xcode能识别出它是framework。接下来你将跟着我来创建这样的一个目录结构。

  • 切片。就目前来讲,当你编译库时,仅仅是针对当前的环境编译了相应的库(真机发布?真机调试?模拟器发布?模拟器调试?),也就是 i386,arm7,等等。为了能够让framework在各种环境下适用,我们需要将各个环境编译的库放到framework对应的目录结构下。

Framework 的结构

ios_framework_directory_structure-449x320.png

接下来我将创建一个脚本来动态创建静态库。首先在Xcode的工程导航栏中选中 RWUIControls工程,点击RWUIControls 静态库项目。选择 Build Phases标签,在Xcode菜单栏中选择 Editor / Add Build Phase/Add Run Script Build Phase. 如下图:

ios_framework_framework_add_run_script_build_phase-700x271.png

当前创建的Script支持 Bash脚本的运行。我们来双击 RunScript标签进行重命名为 Build Framework, 然后将以下的代码拷贝进代码框:

set -e export FRAMEWORK_LOCN="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework" # Create the path to the real Headers diemkdir -p "${FRAMEWORK_LOCN}/Versions/A/Headers" # Create the required symlinks/bin/ln -sfh A "${FRAMEWORK_LOCN}/Versions/Current"/bin/ln -sfh Versions/Current/Headers "${FRAMEWORK_LOCN}/Headers"/bin/ln -sfh "Versions/Current/${PRODUCT_NAME}" \ "${FRAMEWORK_LOCN}/${PRODUCT_NAME}" # Copy the public headers into the framework/bin/cp -a "${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/" \ "${FRAMEWORK_LOCN}/Versions/A/Headers"

代码解读:
脚本首先创建了这样的一个目录结构 RWUIControls.framework/Versions/A/Headers
接下来创建了如下的引用链接:

  • Versions/Current => A
  • Headers => Versions/Current/Headers
  • RWUIControls => Versions/Current/RWUIControls

最后,将公开的头文件拷贝到 Versions/A/Headers 目录中。其中 -a 参数表示当你做一些修改时,与此拷贝不同步,由此来保证不必要的重复编译。

接下来做如下图的编译:

右键选择工程目录里的 Products下的 libRWUIControls.a, 选择 Show in Finder. 接下来选择查看包结构,你将看到之前提到framework的结构。

但是你会发现一个问题,在目录结构中没有你想创建的静态库,别慌,接下来我们将会创建另外一个脚本。

支持多框架的创建###

iOS app需要在如下众多的框架环境下运行:

  • arm7: 支持iOS7的老设备
  • arm7s: iPhone 5 和 5C
  • arm64: 64-bit ARM processor in iPhone 5S
  • i386: For the 32-bit simulator
  • x86_64: Used in 64-bit simulator

众多设备需要不同环境下编译的静态库,好象这么做有点复杂,我们可以采取将各个平台下的静态库添加到framework中。

我们将利用 RWUIControls项目来创建。选择 RWUIControls项目(target),然后点击 Add Target 按钮,选择添加 Aggregate:

创建集合

并命名为 Framework。

备注: 为什么要这么绕呢?还记得开篇讲过iOS审核的问题吗?那么我们可以采取创建 Aggregate的方式来绕过那个麻烦的问题,同时我们还可以在此创建脚本来添加静态库。是不是很神奇?

为了保证在创建Framework前,静态库已经创建了,你需要为 Framework添加项目依赖。如下图:

添加项目依赖

最重要的步骤是,你需要创建一个支持多环境的库,接下来你应该象之前你创建一个脚本那样,为framework创建一个脚本。如下图:

创建脚本

将脚本的名字更改为 MultiPlatform Build. 并拷贝以下代码到脚本里:
#If we're already inside this script then die if [ -n "$RW_MULTIPLATFORM_BUILD_IN_PROGRESS" ]; then exit 0 fi export RW_MULTIPLATFORM_BUILD_IN_PROGRESS=1 RW_FRAMEWORK_NAME=${PROJECT_NAME}RW_INPUT_STATIC_LIB="lib${PROJECT_NAME}.a" RW_FRAMEWORK_LOCATION="${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework"

  • set -e保证了创建一个完整的framework:当脚本编译错误,将终止
  • 接下来, 这个 RW_MULTIPLATFORM_BUILD_IN_PROGRESS
    变量确保了这个脚本不会被循环引用,如果循环引用了,那么就终止。
  • 然后设置了一些变量. 注意:framework的名字必须和工程名字相同
    ,如下RWUIControls, 静态库名字为 libRWUIControls.a.

接下来创建一些函数,后面我们将使用到,将代码复制到脚本里的后面。

function build_static_library {# Will rebuild the static library as specified # build_static_library sdk xcrun xcodebuild -project "${PROJECT_FILE_PATH}" \ -target "${TARGET_NAME}" \ -configuration "${CONFIGURATION}" \ -sdk "${1}" \ ONLY_ACTIVE_ARCH=NO \ BUILD_DIR="${BUILD_DIR}" \ OBJROOT="${OBJROOT}" \ BUILD_ROOT="${BUILD_ROOT}" \ SYMROOT="${SYMROOT}" $ACTION} function make_fat_library { # Will smash 2 static libs together # make_fat_library in1 in2 out xcrun lipo -create "${1}" "${2}" -output "${3}" }

  • build_static_library
    SDK 作为一个参数, (例如 *iphoneos7.0)
    *来创建库, 大多数参数都会通过, 注意这个参数 ONLY_ACTIVE_ARCH
    是确保当前的构造版本中所有的框架环境都支持当前的SDK。
  • make_fat_library
    用 lips
    来合并两个静态库。 将输入的两个静态库合并后输出. 关于lipo的更多知识请点击lipo。

接下来就是来怎么使用以上的两个函数了,你需要知道哪些SDK,以及这些SDK的路径位置。

# 1 - Extract the platform (iphoneos/iphonesimulator) from the SDK name if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]; then RW_SDK_PLATFORM=${BASH_REMATCH[1]} else echo "Could not find platform name from SDK_NAME: $SDK_NAME" exit 1 fi
# 2 - Extract the version from the SDK if [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]; then RW_SDK_VERSION=${BASH_REMATCH[1]} else echo "Could not find sdk version from SDK_NAME: $SDK_NAME" exit 1 fi
# 3 - Determine the other platform if [ "$RW_SDK_PLATFORM" == "iphoneos" ]; then RW_OTHER_PLATFORM=iphonesimulator else RW_OTHER_PLATFORM=iphoneos fi
# 4 - Find the build directory if [[ "$BUILT_PRODUCTS_DIR" =~ (.*)$RW_SDK_PLATFORM$ ]]; then RW_OTHER_BUILT_PRODUCTS_DIR="${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}" else echo "Could not find other platform build directory." exit 1 fi

以上4个声明的意思都非常相近, 用判断来给这两个参数赋值 RW_OTHER_PLATFORM
和 RW_OTHER_BUILT_PRODUCTS_DIR.

以下讲解四个 if 的作用

  • SDK_NAME必须是 iphoneos7.0 或 iphonesimulator6.1的格式。 正则表达是确保以非数字开头的字符串。 所以它就确定为 iphoneos或者 iphonesimulator
  • 同样的道理,保证为如下, 7.0or 6.1etc.
  • 应该还是看得懂吧,判断环境进行赋值。 是iphonesimulator 还是 iphoneos
  • 从创建的版本目录中获取平台名字,用其他版本的来替换。这一步保证其他平台能够从创建的路径中查找到。

添加如下脚本代码到脚本的后面:

# Build the other platform. build_static_library "${RW_OTHER_PLATFORM}${RW_SDK_VERSION}"
# If we're currently building for iphonesimulator, then need to rebuild
# to ensure that we get both i386 and x86_64 if [ "$RW_SDK_PLATFORM" == "iphonesimulator" ]; then build_static_library "${SDK_NAME}"fi
# Join the 2 static libs into 1 and push into the .framework make_fat_library "${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \ "${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \ "${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"

  • 首先对你之前创建的函数的调用,目的是构建起他版本
  • 如果你当前创建的模拟器版本,Xcode会默认只创建如下两种库:i386或x86_64.为了能够创建这两种架构的库,第二步调用 build_static_library来创建 iphonesimulatorSDK, 并且确保两种库已经创建。
  • 最后来调用 make_fat_library函数,让静态库融入到framework中的路径里。

坚持!最后一步了,将以下代码添加到脚本的最后面:

# Ensure that the framework is present in both platform's build directories cp -a "${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}" \ "${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}"
# Copy the framework to the user's desktop ditto "${RW_FRAMEWORK_LOCATION}" "${HOME}/Desktop/${RW_FRAMEWORK_NAME}.framework"

  • 第一条命令是确保framework存在于两种路径里
  • 最后一步是将创建好的framework拷贝到你的桌面。当然,这一步是可选的。但是这样会很方便你去使用。

好了,所有的操作都搞定了,只需要选择 Framework进行编译一下(随便你选什么设备都行):

你可以查看framework包内容:

查看framework目录结构

如何来检查当前拷贝出的framework是否支持所有环境的呢?
打开你的终端(terminal),如下图操作:


验证framework支持的平台.png

你将看见列出了五种环境,armv7 armv7s i386 x86_arm64 OK 搞定。

怎么使用?这里我就不赘述了,直接将framework拖入到你工程的 framework目录下,然后导入头文件就行了。

以上我们讲解了静态库的打包,但是我们还没讲解资源的打包(图片、字体、nib)。

创建一个Bundle

打开UIControlDevApp工程,选择RWUIControls子工程(还记得之前我们在一个工作空间里管理两个工程吗?)。点击 Add Target按钮:

创建Bundle.gif

当前你创建的Bundel默认是 OSX的,你需要将它更改为 iOS。 操作如下图:

修改bundle支持iOS.png

接下来你需要更改产品名字(后面脚本会用到,更改和静态库的名字一样),如下图:

修改名字.png

默认情况下,当你在Bundle中添加两倍图时,系统会将它编译为多分辨率的格式 TIFF,这样是你不希望的,因为会导致奇怪的问题。你可以做如下图的设置:

修改bundle配置.png

好了,请记住:当你编译framework时,bundle也需要被framework引用。如下图操作:

添加bundle引用.gif

好了,我们最后可以像framework的脚本一样,添加一个拷贝Bundle到桌面的操作,添加如下代码到 MuiltiPlatform Build 脚本中:

# Copy the resources bundle to the user's desktop ditto "${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.bundle" \ "${HOME}/Desktop/${RW_FRAMEWORK_NAME}.bundle"

选择framework 进行编译,你会在你的桌面上发现两个包:xxx.framework, xxx.bundle

好了创建Bundle已经成功,但是如何使用呢?

图片的引用:
UIImage *image = [UIImage imageNamed:@"RWUIControls.bundle/RWRibbon"];

nib的引用:
NSString * bundlePath =[[NSBundle mainBundle] pathForResource:@“RWUIControls" ofType:@“bundle”];
NSBundle * bundle = [NSBundle bundleWithPath:bundlePath];
[UIStoryboard storyboardWithName:@“xxx” bundle:bundle];

引用xib也是同样的道理,把mainBundle替换为你创建的bundle就行了。

但是!请注意

这里我加一点补充:如果xib/storyBoard中的文件与你创建的类进行了关联,例如:stroyBoard中的 MainViewController 与你创建的 MainViewController class进行了关联,你需要将 MainViewController.h 文件给暴露出来(这个类在静态库中,静态库引用了你创建的Bundle,你需要在静态库中将 MainViewController.h拖入到 Public中)。

补充知识点:###

如果你有个非常大的工程B,你想将它作为主工程A的插件(第三方库),并且B中使用了Cocoapods来管理第三方库,那么按照之前讲解之作framework的流程来看,你需要将B中的代码全部拖到 framework 工程中进行编译。

试想,如果B工程是多个人开发或者需要经常更新,怎么办?还是拷贝代码到framework工程吗?太麻烦了是吧?

猜想下,我们可以创建一个framework工程C,如果C与B在同一个工作空间中(workspace), 并且C中引用(是引用,不是拷贝)B中所有的代码,那么C是不是可以随着B的更新而更新呢? 答案是:尝试下~(当然是可以的)

且看下图:

工程结构.png

如果你在 SystematicAnatomyControls(静态库)中引用第三方库 如:
#import <AFNetworking/AFNetworking.h>
你在编译时,Xcode会告诉你无法找到。为什么?(跨工程路径问题,找不到Cocopods中的库)
怎么解决呢?

先在主工程中做如下操作:

设置Pods宏路径.png

PODS_ROOT 为你自定义添加的,添加方式如下:

添加自定义设置.png

讲解:/../表示回退到上一级目录,我为什么这么做?--参照Pods的做法,主工程依赖Pods的内容,那么主工程也得找到Pods中的文件,下图是我工程的目录情况,同样也告诉你我为什么用 /../ 这种方式来查找文件。

工作空间路径.png

然后我们来做如下图操作:

配置Header Search Path.png

讲解:表示从哪里查询头文件;“ ” 冒号的意思是忽略路径中的空格。


本文主要参照《How to Create a Framework》;
如翻译有误,请指正。
在文末我做了相应的补充,希望能够帮到你。
本文所采用的技术已做相应的论证,主要使用于Unity导出的Xcode工程依赖原生工程静态库。
如有其它相关问题,欢迎畅讨!

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

推荐阅读更多精彩内容