ios技巧 -- 静态库和动态库

常用库文件格式

  • .a:静态库
  • .dylib:动态库
  • .framework:动静结合的库
  • .xcframework:特定架构下库

库(Library)说⽩了就是⼀段编译好的⼆进制代码,加上头⽂件就可以供别
⼈使⽤。

什么时候会⽤到库(Library)?

  1. 某些代码需要给别⼈使⽤,但是我们不希望别⼈看到源码,就需要以
    库的形式进⾏封装,只暴露出头⽂件。
  2. 对于某些不会进⾏⼤的改动的代码,我们想减少编译的时间,就可以
    把它打包成库,因为库是已经编译好的⼆进制了,编译的时候只需
    要 Link ⼀下,不会浪费编译时间。

什么是静态库?

静态库即静态链接库:可以简单的看成⼀组⽬标⽂件的集合。即很多⽬
标⽂件经过压缩打包后形成的⽂件。Windows 下的 .lib,Linux 和 Mac
下的 .a。Mac独有的.framework。
缺点:
浪费内存和磁盘空间,模块更新困难

什么是动态库?

与静态库相反,动态库在编译时并不会被拷⻉到⽬标程序中,⽬标程序
中只会存储指向动态库的引⽤。等到程序运⾏时,动态库才会被真正加
载进来。格式有:.framework、.dylib、.tdb。
缺点:
会导致⼀些性能损失。但是可以优化,⽐如延迟绑定(Lazy Binding)技术

什么是tdb格式?

tbd全称是text-based stub libraries,本质上就是⼀个YAML描述的⽂本⽂
件。
他的作⽤是⽤于记录动态库的⼀些信息,包括导出的符号、动态库的架构信息、动
态库的依赖信息
⽤于避免在真机开发过程中直接使⽤传统的dylib。
对于真机来说,由于动态库都是在设备上,在Xcode上使⽤基于tbd格式的伪
framework可以⼤⼤减少Xcode的⼤⼩。

Framework

Mac OS/iOS 平台还可以使⽤ Framework。Framework 实际上是⼀种打包
⽅式,将库的⼆进制⽂件,头⽂件和有关的资源⽂件打包到⼀起,⽅便管
理和分发。
Framework 和系统的 UIKit.Framework 还是有很⼤区别。系统的
Framework 不需要拷⻉到⽬标程序中,我们⾃⼰做出来的 Framework 哪怕
是动态的,最后也还是要拷⻉到 App 中(App 和 Extension 的 Bundle 是
共享的),因此苹果⼜把这种 Framework 称为 Embedded Framework。

Embedded Framework

开发中使⽤的动态库会被放⼊到ipa下的framework⽬录下,基于沙盒运⾏。
不同的App使⽤相同的动态库,并不会只在系统中存在⼀份。⽽是会在多
个App中各⾃打包、签名、加载⼀份。

Mach-o File Format

⼀个Mach-o⽂件有两部分组成:header 和data。
header:代表了⽂件的映射,描述了⽂件的内容以及⽂件所有内容所在的位置。
data:紧跟header之后,由多个⼆进制组成,one by one。

Load Commands

进制⽂件加载进内存要执⾏的⼀些指令。
这⾥的指令主要在负责我们 APP 对应进程的创建和基本设置(分配虚拟内
存,创建主线程,处理代码签名/加密的⼯作),然后对动态链接库(.dylib
系统库和我们⾃⼰创建的动态库)进⾏库加载和符号解析的⼯作。

Mach-o File Format

⼀个Mach-o⽂件有两部分组成:header 和data。
header:包含三种类型。Mach header,segment,sections
header内的section描述了对应的⼆进制信息。
注意⚠:Mach header属于header的⼀部分,它包含了整个⽂件的信息和segment信息。

Embedded Framework

开发中使⽤的Embedded Framework会被放⼊到ipa下的Frameworks⽬录下,基
于沙盒运⾏。
不同的App使⽤相同的动态库,并不会只在系统中存在⼀份。⽽是会在多
个App中各⾃打包、签名、加载⼀份。

这里我们通过file查看AFN的.a文件,打开终端
file libAFNetworking.a:查看文件的格式

libAFNetworking.a: current ar archive

ar -t libAFNetworking.a查看.a文件的合集

__.SYMDEF
AFAutoPurgingImageCache.o
AFHTTPSessionManager.o
AFImageDownloader.o
AFNetworkActivityIndicatorManager.o
AFNetworking-dummy.o
AFNetworkReachabilityManager.o
AFSecurityPolicy.o
AFURLRequestSerialization.o
AFURLResponseSerialization.o
AFURLSessionManager.o
UIActivityIndicatorView+AFNetworking.o
UIButton+AFNetworking.o
UIImageView+AFNetworking.o
UIProgressView+AFNetworking.o
UIRefreshControl+AFNetworking.o
WKWebView+AFNetworking.o

clang

NAME
       clang - the Clang C, C++, and Objective-C compiler

SYNOPSIS
       clang [options] filename ...

DESCRIPTION
       clang  is  a C, C++, and Objective-C compiler which encompasses prepro-
       cessing, parsing, optimization, code generation, assembly, and linking.
       Depending  on  which high-level mode setting is passed, Clang will stop
       before doing a full link.  While Clang  is  highly  integrated,  it  is
       important to understand the stages of compilation, to understand how to
       invoke it.  These stages are:

clang命令参数:
-x: 指定编译文件语言类型
-g: 生成调试信息
-c: 生成目标文件,只运行preprocess,compile,assemble,不链接
-o: 输出文件
-isysroot: 使用的SDK路径
1. -I<directory> 在指定目录寻找头文件 header search path
2. -L<dir> 指定库文件路径(.a.dylib库文件) library search path
3. -l<library_name> 指定链接的库文件名称(.a.dylib库文件)other link flags -lAFNetworking
-F<directory> 在指定目录寻找framework framework search path
-framework <framework_name> 指定链接的framework名称 other link flags -framework AFNetworking

将.m编译成.o文件

//-x 指定编译语言
clang -x objective-c \
//-target 指定编译平台
-target x86_64-apple-macos10.15 \
//编译成arc环境,10.6一下用-fno-objc-arc
-fobjc-arc \
//指定需要的sdk路径 并找到自己xcode目录下的sdk路径,在这使用的是MacOSX,也可以换成对应的手机或者模拟器
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
//链接库
-I./AFNetworking \
//将.m编译成.o
-c test.m -o test.o

目标文件就可以通过重定位符号表,链接重定位。所以我们只需要导入头文件即可
将.o编译成可执行文件

clang -target x86_64-apple-macos10.5 \
//编译成arc环境,10.6一下用-fno-objc-arc
-fobjc-arc \
//指定需要的sdk路径 并找到自己xcode目录下的sdk路径,在这使用的是MacOSX,也可以换成对应的手机或者模拟器
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
//链接库
-L./AFNetworking \
-lAFNetworking
//将.m编译成.o
test.o -o test

静态库其实就是一个.o文件的合集

合并静态库

libtool

添加或更新静态库的目录表

NAME
       libtool - create libraries
       ranlib - add or update the table of contents of archive libraries
libtool \
//代表要合并的是一个静态库
-static \
//输出静态库的名称
-o libCat.a \
//需要合并的静态库
libAFNetworking.a \
libSDWebImage.a

ar

ar压缩目标文件,并对其进行编号和索引,形成静态库。同时也可以解压缩静态库,查看有哪些目标文件:
ar -rc a.a a.o
-r: 像a.a添加or替换文件
-c: 不输出任何信息
-t: 列出包含的目标文件

生成workspace

![addFile.png](https://upload-images.jianshu.io/upload_images/2414707-87ed3515a891192e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

添加文件之前需要xcode中所有的窗口

动态库

//支持的架构
ld -dylib -arch x86_64 \
//支持最小的系统版本
-macosx_version_min 10.15 \
-syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
//system:dyld相关的链接器
-lsystem -framework Foundation \
//符号全部导出
-all_load \
libTestExample.a -o libTestExample.dylib

动态库是.o文件链接过后的产物

什么是tdb格式?

tbd全称是text-based stub libraries,本质上就是⼀个YAML描述的⽂本⽂
件。
他的作⽤是⽤于记录动态库的⼀些信息,包括导出的符号、动态库的架构信息、动
态库的依赖信息
⽤于避免在真机开发过程中直接使⽤传统的dylib。
对于真机来说,由于动态库都是在设备上,在Xcode上使⽤基于tbd格式的伪
framework可以⼤⼤减少Xcode的⼤⼩。

在向着上面的方式链接后,我们在链接动态库运行时一直会image not found

12.png

我们通过otool -l test | grep 'DYLIB' -A 5遇到DYLIB向下查找5行(-B是向上)

          cmd LC_LOAD_DYLIB
      cmdsize 72
         name TestExample (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 0.0.0
compatibility version 0.0.0
--
          cmd LC_LOAD_DYLIB
      cmdsize 96
         name /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 1675.129.0
compatibility version 300.0.0
--
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name /usr/lib/libobjc.A.dylib (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 228.0.0
compatibility version 1.0.0
--
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name /usr/lib/libSystem.B.dylib (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 1281.100.1
compatibility version 1.0.0
--
          cmd LC_LOAD_DYLIB
      cmdsize 104
         name /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 1675.129.0
compatibility version 150.0.0

我们可以看到其他的动态库的name都是一个路径TestExample只有一个名字
动态库的路径是保存在自己的mach-o中的

install_name_tool

NAME
       install_name_tool - change dynamic shared library install names

SYNOPSIS
       install_name_tool  [-change  old  new  ]  ...  [-rpath  old  new  ] ...
       [-add_rpath new ] ... [-delete_rpath new ] ... [-id name] file

DESCRIPTION
       Install_name_tool changes the dynamic shared library install names  and
       or  adds,  changes  or  deletes the rpaths recorded in a Mach-O binary.
       For this tool to work when the install names or rpaths are  larger  the
       binary  should  be  built  with  the ld(1) -headerpad_max_install_names
       option.

       -change old new
              Changes the dependent shared library install name old to new  in
              the specified Mach-O binary.  More than one of these options can
              be specified.  If the Mach-O binary does  not  contain  the  old
              install  name  in  a  specified  -change  option  the  option is
              ignored.
       -id name
              Changes the shared library  identification  name  of  a  dynamic
              shared  library  to name.  If the Mach-O binary is not a dynamic
              shared library and the -id option is specified it is ignored.

       -rpath old new
              Changes the rpath path name old to new in the  specified  Mach-O
              binary.   More  than  one of these options can be specified.  If
              the Mach-O binary does not contain the old rpath path name in  a
              specified -rpath it is an error.

       -add_rpath new
              Adds  the  rpath  path  name new in the specified Mach-O binary.
              More than one of these options can be specified.  If the  Mach-O
              binary  already  contains  the  new rpath path name specified in
              -add_rpath it is an error.

       -delete_rpath old
              deletes the rpath path name old in the specified Mach-O  binary.
              More  than one of these options can be specified.  If the Mach-O
              binary does not contains the old rpath path  name  specified  in
              -delete_rpath it is an error.

执行Install_name_tool -id 路径 目标文件

@rpath

Install_name_tool命令中提供的路径是绝对路径,有什么可以比较方便的解决路径问题呢, @rpath
@rpath代表的谁链接我,谁提供路径,这个是因为@rpath存在链接文件的mach-o中,链接时,传递给被链接的Mach-o中的@rpath
我们上面的命令可以改为Install_name_tool -id @rpath/Frameworks/TestExample.framework/TestExample TestExample然后在需要链接的文件中Install_name_tool -add_rpath 路径 目标文件
@executable_path:表示可执⾏程序所在的⽬录,解析为可执⾏⽂件的绝对路径。
@loader_path:表示被加载的Mach-O 所在的⽬录,每次加载时,都可能
被设置为不同的路径,由上层指定。

XCFramework

XCFramework:是苹果官⽅推荐的、⽀持的,可以更⽅便的表示⼀个多
个平台和架构的分发⼆进制库的格式。
需要Xcode11以上⽀持。
是为更好的⽀持Mac Catalyst和ARM芯⽚的macOS。
专⻔在2019年提出的framework的另⼀种先进格式。

iOS/iPad:arm64
iOS/iPad Simulator: x86_64 arm64
Mac Catalyst: x86_64 arm64
Mac: x86_64 arm64

和传统的framework相⽐:

  1. 可以⽤单个.xcframework⽂件提供多个平台的分发⼆进制⽂件;
  2. 与Fat Header相⽐,可以按照平台划分,可以包含相同架构的不同平
    台的⽂件;
  3. 在使⽤时,不需要再通过脚本去剥离不需要的架构体系。
  • xcodebuild
    将文件进行编译打包
xcodebuild archive -project 'SYTimer.xcodeproj' \
-scheme 'SYTimer' \
-configuration Release \
//指定编译平台
-destination 'generic/platform=iOS Simulator' \
//输出路径
-archivePath '../archives/SYTimer.framework-iphonesimulator.xcarchive' \
//将编译的产物拷贝到目录中
SKIP_INSTALL=NO

指定他的编译环境进行打包

//编译为真机环境下
xcodebuild archive -project 'SYTimer.xcodeproj' \
-scheme 'SYTimer' \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath '../archives/SYTimer.framework-iphoneos.xcarchive' \
SKIP_INSTALL=NO

平常我们打包时都是多架构打包在一块的

  • lipo:创建或操作通用文件
  • 胖二进制 :多个架构打包到一起

动态库合并只能合并不同架构的

lipo -output 
//指定输出名称
SYTimer 
-create 
//真机环境下的
../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer 
//模拟器环境下的
../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer

提取出特定架构下的动态库

lipo -output SYTimer-x86_64 -extract x86_64 ../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer

但是还需要前面,dSYM之类信息

xcframework目前只能通过命令行生成

xcodebuild -create-xcframework \
-framework '../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-framework '../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-output 'SYTimer.xcframework'

就会根据生成不同平台的framework
还需要把调试符添加进去

xcodebuild -create-xcframework \
-framework '../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-debug-symbols '/Users/xxx/xxx/archives/SYTimer.framework-iphoneos.xcarchive/BCSymbolMaps/6142AB7C-A46D-33C9-A32B-405B93A1D680.bcsymbolmap' \
-debug-symbols '/Users/xxx/xxx/archives/SYTimer.framework-iphoneos.xcarchive/BCSymbolMaps/314A0F21-A400-3D1F-ACBE-258D8A3C919F.bcsymbolmap' \
-framework '../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-debug-symbols '/Users/xxx/xxx/archives/SYTimer.framork-iphonesimulator.xcarchive/dSYMs/SYTimer.framework.dSYM' \
-output 'SYTimer.xcframework'

这里-debug-symbols需要的是绝对路径。

生成的xcframework.png

  • weak_framework

我们在 xcconfig中配置

// 2. -F: frmaework 所在的目录
FRAMEWORK_SEARCH_PATHS = $(inherited) ${SRCROOT}
// 1. -I :头文件
HEADER_SEARCH_PATHS = $(inherited) ${SRCROOT}/SYTimer.framework/Headers
// 路径
LD_RUNPATH_SEARCH_PATHS = $(inherited)
// 3. 名称
// null -》 runtime -〉 nil
// weak_import
// library
OTHER_LDFLAGS = $(inherited) -Xlinker -weak_framework -Xlinker "SYTimer"

设置了weak_framework当没有找到定义的符号是就会显示为null。

链接两个相同的静态库

OTHER_LDFLAGS = $(inherited) -l"AFNetworking" -l"AFNetworking2"
链接两个相同的静态库,不同的名字,不会发生冲突,因为引入的时候默认是-noall_load,会做个代码剥离,如果找到了就不会进行链接。

动态库链接动态库

我们通过cocoapods导入时只是生成链接器的参数,并不会真正导入。

  • 导入动态库的时候通过他给我们的脚本动态的拷贝到ipa包中。
    我们在Podfile中使用use_frameworks!
if [[ "$CONFIGURATION" == "Debug" ]]; then
  install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
  install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
fi
  • 在我们项目的target下再导入一次

如果动态库想使用APP中的符号,我们可以OTHER_LDFLAGS添加
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -Xlinker -undefined -Xlinker dynamic_lookup
使用-undefined会有风险,如果没有定义的符号也会通过,我们也可以用
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -Xlinker -U -Xlinker _OBJC_CLASS_$_LGAppObject来指定特定的符号

动态库链接静态库

Podfile中不使用use_frameworks!
可正常运行
如果想隐藏静态库的符号可使用-hidden-l
OTHER_LDFLAGS = $(inherited) -ObjC -Xlinker -hidden-l"AFNetworking"

静态库链接静态库

我们需要手动配置链接的静态库信息
LIBRARY_SEARCH_PATHSOTHER_LDFLAGS

静态库链接动态库

app = app + 静态库 所以和动态链接动态差不多

cocoapods 即导入静态库又导入动态库

platform :ios, '14.1'
target :'LGNetworkManager' do
  use_frameworks!
  # 静态库、动态库
  # 指定需要被编译成static_framework的库
  $static_framework = ['AFNetworking']
  
  pre_install do |installer|
  installer.pod_targets.each do |pod|
        if $static_framework.include?(pod.name)
            def pod.build_type;
              Pod::Target::BuildType.static_framework
            end
        end
    end
  end
  pod 'SDWebImage'
end

Module

Module(模块)-最小的代码单元
一个Module是机器代码和数据的最小单元,可以独立于其他代码单元进行链接。
通常,Module是通过编译单个原文件生成的目标文件。例如,当前的test.m被编译成目标文件test.o时,当前的目标文件就代表了一个Module。

AFN中的module

//framework module 名称 AFNetworking
framework module AFNetworking {
  umbrella header "AFNetworking-umbrella.h"
//将所有的头文件重新导出
  export *
//将上面子module全部导出
  module * { export * }
}

umbrella代表一个目录代表伞柄, 这个目录下所有.h代表伞骨

AFNetworking-umbrella.png

这一个文件里包含所有的头文件
explicit显示指明子module名称
在文件中通过@import就可以直接导入模块,例@import AFNetworking.AFHTTPSessionManager;
其实只要开启了module#include#import最后都会转换成@import
更多用法可参考官网

一般生成 framework后会自动生成一个module,如果要手动创建一个,在创建完.modulemap, 还需要在build settings中配置module file map

framework module LGOCFramework {
    umbrella "Headers"
    export *
    module * {
        export *
    }
}

展开就是下面

framework module LGOCFramework {
    // umbrella<目录>
    umbrella header "LGOCFramework.h"
  
    explicit module LGTeacher {
        header "LGTeacher.h"
        export *
    }
    explicit module LGStudent {
        header "LGStudent.h"
        export *
    }
}

swiftframework是没有桥接文件的,我们就需要module,如果我们只想在framework中使用,需要将其设置为私有的。
文件命名为xxx.private.modulemap

framework module LGSwiftFramework_Private {
    module LGOCStudent {
        header "LGOCStudent.h"
        export *
    }
}

最后在build settings中配置Private Module file map

swift通过 libtool合并静态库后在xcconfig中配置

// OTHER_CFLAGS:传递给用来编译C或者OC的编译器,当前就是clang
OTHER_CFLAGS="-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/module.modulemap" "-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/module.modulemap"

// SWIFT_INCLUDE_PATHS: 传递给SwiftC编译器,告诉他去下面的路径中查找module.file
SWIFT_INCLUDE_PATHS="${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework"  "${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework"

apinotes

我们在通过OC转swift类时通过宏定义一个个转

// 通过指定NS_SWIFT_NAME宏,我们可以添加一些详细信息以使函数清晰可见,从而使其变得如下所示:
// 规范
- (nullable NSString *)teacherNameForIndex:(NSUInteger)index NS_SWIFT_NAME(teacherName(forIndex:));

// NS_REFINED_FOR_SWIFT从现在开始,Swift的Clang Importer将做一些额外的工作并将该方法导入为私有方法,并以双下划线字符开头__,例如:
//- (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> *)options;
- (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> *)options NS_REFINED_FOR_SWIFT;

如果量太多我们就可以使用apinotes,文件为SDK名称.apinotes文件放在SDK目录下。
这个格式是yaml格式

#yaml
---
Name: OCFramework
Classes:
- Name: LGToSwift
//Swift名
  SwiftName: ToSwift
  Methods:
  - Selector: "changeTeacherName:"
    Parameters:
    - Position: 0
      Nullability: O
    MethodKind: Instance
    SwiftPrivate: true
//设置不可以使用
    Availability: nonswift
    AvailabilityMsg: "这个不能用"
  - Selector: "initWithName:"
    MethodKind: Instance
    DesignatedInit: true

具体信息可以通过官网查询

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

推荐阅读更多精彩内容