Swift 5.1 新特性

翻译自:What's New in Swift 5.1?

Getting Started

Swift 5.1 与 Swift 5 不仅在源代码上兼容,而且由于从 Swift 5 开始 ABI 就稳定了,Swift 5 及后续版本在二进制层面也是兼容的。

Swift 5.1 在 Swift 5 引入的 ABI稳定性之上又增加了模块稳定性。ABI 稳定保证了应用程序运行时的兼容性,而模块稳定性保证了编译时框架的兼容性。这意味着你可以在任意版本的编译器上使用一个第三方框架,而不是仅在构建它的版本的编译器上使用。

文中每个新特性部分都包含有 Swift Evolution 提案的编号,类似这样 [SE-00001]。你可以单击每个链接的标签来查看它的更改详情。

Note: 如果您需要重新了解 Swfit 5 的亮点,可以查看 What's New in Swift 5

Language Improvements

此版本对语言做了许多新改进,包括 不透明的结果类型(opaque result types)、函数构建器(function builders)、属性包装器(property wrappers) 等等。

1. Opaque Result Types

在 Swift 5 中,你可以使用协议作为返回类型。

创建一个新的 Playground,通过导航栏 View -> Navigators -> Show Project Navigator,打开 Project Navigator。右击 Sources 文件夹,选择 New File 并命名为 BlogPost。使用名为 BlogPost的新协议替换该文件的内容。

public protocol BlogPost {
  var title: String { get }
  var author: String { get }
}

右击顶层的 playground 并选择 New Playground Page。将新页面命名为 Opaque Tutorials 并粘贴如下内容:

// 1
struct Tutorial: BlogPost {
  let title: String
  let author: String
}

// 2
func createBlogPost(title: String, author: String) -> BlogPost {
  guard !title.isEmpty && !author.isEmpty else {
    fatalError("No title and/or author assigned!")
  }
  return Tutorial(title: title, author: author)
}

// 3
let swift4Tutorial = createBlogPost(title: "What's new in Swift 4.2?",
                                    author: "Cosmin Pupăză")
let swift5Tutorial = createBlogPost(title: "What's new in Swift 5?", 
                                    author: "Cosmin Pupăză")

逐步分析:

  1. 声明 Tutorialtitleauthor,因为 Tutorial 实现了 BlogPost 协议。
  2. 定义一个函数 createBlogPost(title:author:) ,检查 titleauthor 是否有效,若有效则返回 Tutorial
  3. 使用函数 createBlogPost(title:author:) 来创建 swift4Tutorialswift5Tutorial

您可以重用 createBlogPost(title:author:) 的原型和逻辑来创建 Screencasts

右击顶层的 playground 并选择 New Playground Page。将新页面命名为 Opaque Screencasts 并粘贴如下内容:

struct Screencast: BlogPost {
  let title: String
  let author: String
}

func createBlogPost(title: String, author: String) -> BlogPost {
  guard !title.isEmpty && !author.isEmpty else {
    fatalError("No title and/or author assigned!")
  }
  return Screencast(title: title, author: author)
}

let swift4Screencast = createBlogPost(title: "What's new in Swift 4.2?", 
                                      author: "Josh Steele")           
let swift5Screencast = createBlogPost(title: "What's new in Swift 5?", 
                                      author: "Josh Steele")

Screencast 实现了 BlogPost 协议,所以你可以从 createBlogPost(title:author:)中返回 Screencast 并使用 createBlogPost 来创建 swift4Screencastswift5Screencast

导航到 Sources 文件夹下的 BlogPost.swift ,使 BlogPost 文件遵守 Equatable 协议:

public protocol BlogPost: Equatable {
  var title: String { get }
  var author: String { get }
}

此时,你会得到一个编译错误:BlogPost 只能被用作泛型约束。这是因为 Equatable 协议有一个名为 Self 的关联类型。具有关联类型的协议不能用作类型,即使他们看起来像类型。相反,它们有点像类型占位符,表示 “它可以是遵守此类型的任何类型”。

Swift 5.1 允许你使用 opaque result types [SE-0244],来将这类协议当做常规类型。

Opaque Tutorials 页面,添加 some 关键字到 createBlogPost 的返回类型中,表示它会返回 BlogPost 协议的具体实现。

func createBlogPost(title: String, author: String) -> some BlogPost {

类似的,在 Opaque Screencasts 页面,使用 some 关键字告诉编译器 createBlogPost 返回某种类型的 BlogPost

func createBlogPost(title: String, author: String) -> some BlogPost {

你可以从 createBlogPost 返回实现了 BlogPOst 的任何具体类型,例如 TutorialScreencast

现在,你可以检查前面创建的 tutorailsscreencasts 是否相同。在 Opaque Tutorials 底部,粘贴如下代码来检查 swift4Tutorialswift5Tutorial 是否相等:

let sameTutorial = swift4Tutorial == swift5Tutorial

Opaque screencasts 底部,粘贴如下代码来检查 swift4Screencastswift5Screencast 是否相等:

let sameScreencast = swift4Screencast == swift5Screencast

2. Implicit Returns From Single-Expression Functions

在 Swift 5 中,单表达式函数中如果有返回值,需要使用 return

extension Sequence where Element == Int {
  func addEvenNumbers() -> Int {
    return reduce(0) { $1.isMultiple(of: 2) ? $0 + $1 : $0 }
  }

  func addOddNumbers() -> Int {
    return reduce(0) { $1.isMultiple(of: 2) ? $0 : $0 + $1 }
  }
}

let numbers = [10, 5, 2, 7, 4]
let evenSum = numbers.addEvenNumbers()
let oddSum = numbers.addOddNumbers()

addEvenNumbers()addOddNumbers() 中使用 reduce(_:_:) 来决定 Sequence 中偶数和奇数的总和。

Swift 5.1 可省略单表达式函数中的 return,此时它们的行为类似于单行闭包 [SE-0255]:

extension Sequence where Element == Int {
  func addEvenNumbers() -> Int {
    reduce(0) { $1.isMultiple(of: 2) ? $0 + $1 : $0 }
  }

  func addOddNumbers() -> Int {
    reduce(0) { $1.isMultiple(of: 2) ? $0 : $0 + $1 }
  }
}

这样的代码更简洁,更容易遵循。

Note: 想要了解更多关于 reduce(_:_:) 函数的知识吗,可以查看这篇教程:An Introduction to Functional Programming in Swift.

3. Function Builders

Swift 5.1 使用函数构建器(function builders) 来实现构建器模式 [SE-XXXX]:

@_functionBuilder
struct SumBuilder {
  static func buildBlock(_ numbers: Int...) -> Int {
    return numbers.reduce(0, +)
  }
}

@_functionBuilder 来标记 SumBuilder ,使 SumBuilder 成为函数构建器类型。函数构建器是特殊的函数类型,其中每个表达式(字面量、变量名、函数调用、if 语句等)都被分别处理并用于生成单个值。例如,你可以编写一个函数,其中每个表达式将表达式的结果添加到数组中,从而形成自己的数组字面量。

Note: 在函数构建器的标记是 @_functionBuilder ,因为此提议尚未获得批准。一旦获得批准,期望标记为 @functionBuilder

您可以通过实现 特定名称和类型签名 的不同静态函数来创建函数构建器。buildBlock(_: T...) 是其中唯一必须实现的一个函数。还有一些函数,可以处理 if 语句、可选值和其他可被视为表达式的结构。

您可以使用 函数构造器创建的类型名,来标记 函数或者闭包:

func getSum(@SumBuilder builder: () -> Int) -> Int {
  builder()
}

let gcd = getSum {
  8
  12
  5
}

getSum 闭包 处理被传入的每个表达式(此例中是3个数字),将处理的结果传递给构建器,并将构建器隐式返回。函数构建器和它的隐式返回特性,是 SwiftUI 简洁语法的组成部分。它们还允许您创建自己领域的特定语言。

译者注:以下代码可能更好理解:

// Original source code:
@TupleBuilder
func build() -> (Int, Int, Int) {
1
2
3
}

// This code is interpreted exactly as if it were this code:
func build() -> (Int, Int, Int) {
let _a = 1
let _b = 2
let _c = 3
return TupleBuilder.buildBlock(_a, _b, _c)
}

4. Property Wrappers

在 Swift 5 中使用计算属性时,你可能会处理很多样板代码:

var settings = ["swift": true, "latestVersion": true]

struct Settings {
  var isSwift: Bool {
    get {
      return settings["swift"] ?? false
    }
    set {
      settings["swift"] = newValue
   }
  }

  var isLatestVersion: Bool {
    get {
      return settings["latestVersion"] ?? false
    }
    set {
      settings["latestVersion"] = newValue
    }
  }
}

var newSettings = Settings()
newSettings.isSwift
newSettings.isLatestVersion
newSettings.isSwift = false
newSettings.isLatestVersion = false

isSwiftisLatestVersionsettings 中获取并设置给定 key 的值。在 Swift 5.1 中可通过属性包装器(Property wrappers) [SE-0258] 来消除重复代码:

// 1
@propertyWrapper
struct SettingsWrapper {
  let key: String
  let defaultValue: Bool

  // 2
  var wrappedValue: Bool {
    get {
      settings[key] ?? defaultValue
    }
    set {
      settings[key] = newValue
    }
  }
}

// 3
struct Settings {
  @SettingsWrapper(key: "swift", defaultValue: false) var isSwift: Bool
  @SettingsWrapper(key: "latestVersion", defaultValue: false) 
    var isLatestVersion: Bool
}

它的工作原理如下:

  1. 使用 @propertyWrapper 标记 SettingsWrapper,使它成为属性包装器类型。
  2. 使用 wrappedValue 来获取和设置 settings 中的 key
  3. 使用 @SettingsWrapper 标记 isSwiftisLatestVersion ,并实现对应的属性包装器。
Working with computed properties the Swifty way!.png

5. Synthesizing Default Values for Initializers in Structures

Swift 5 默认不会在结构体中为属性设置初始化的值,所以我们需要像这样定义初始化值:

struct Author {
  let name: String
  var tutorialCount: Int![Diffing collections like a pro in Swift 5.1!.png](https://upload-images.jianshu.io/upload_images/1610911-ff96a7fe1bb157ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


  init(name: String, tutorialCount: Int = 0) {
    self.name = name
    self.tutorialCount = tutorialCount
  }
}

let author = Author(name: "George")

我们给构造初始化器提供默认参数 tutorialCount = 0。因此在创建 author 时仅传递参数 name,并且 author.tutorialCount 默认为 0。

Swift 5.1 让我们可以直接为结构体属性设置默认值,所以我们不必再通过自定义初始化器达到此效果 [SE-0242]:

struct Author {
  let name: String
  var tutorialCount = 0
}

代码更干净简洁了。

6. Self for Static Members

在 Swift 5 中,不能使用 Self 表示数据类型的静态成员,所以我们不得不使用它的类型名代替:

struct Editor {
  static func reviewGuidelines() {
    print("Review editing guidelines.")
  }

  func edit() {
    Editor.reviewGuidelines()
    print("Ready for editing!")
  }
}

let editor = Editor()
editor.edit()

如上所示,Editor 命名可能会发生变化,且命名冗长,难以维护和阅读。

在 Swift 5.1 [SE-0068] 中,你可以使用 Self 来替换实例方法中的 Editor 引用:

struct Editor {
  static func reviewGuidelines() {
    print("Review editing guidelines.")
  }

  func edit() {
    Self.reviewGuidelines()
    print("Ready for editing!")
  }
}

现在,使用 Self 来调用 reviewGuidelines() 方法。

7. Creating Uninitialized Arrays

在 Swift 5.1 [SE-0245] 中,我们可以创建未初始化的数组了:

// 1
let randomSwitches = Array<String>(unsafeUninitializedCapacity: 10) {
  buffer, initializedCount in
  // 2
  for i in 0..<5 {
    buffer[i] = Bool.random() ? "on" : "off"
  }
  // 3
  initializedCount = 5
}

逐步分析:

  1. 使用 init(unsafeUninitializedCapacity:initializingWith:) 方法创建一个具有一定容量的数组 randomSwitches
  2. 遍历 randomSwitches 并使用 random() 设置每个元素的状态。
  3. 设置 randomSwitches 初始化了的元素的数量。

8. Diffing Ordered Collections

Swift 5.1 使我们可以区分出有序集合之间的差异 [SE-0240]:

let operatingSystems = ["Yosemite",
                        "El Capitan",
                        "Sierra",
                        "High Sierra",
                        "Mojave",
                        "Catalina"]
var answers = ["Mojave",
               "High Sierra",
               "Sierra",
               "El Capitan",
               "Yosemite",
               "Mavericks"]

operatingSystems 包括了从 Swift 1 到现在的所有操作系统版本,从旧到新排序。

answers 逆序列出了它们,并增添或删减了部分元素。

求差异集合的Api要求我们使用 Swift 5.1 及以上版本:

#if swift(>=5.1)
  let differences = operatingSystems.difference(from: answers)
  let sameAnswers = answers.applying(differences) ?? []
  // ["Yosemite", "El Capitan", "Sierra", "High Sierra", "Mojave", "Catalina"]

使用 difference(from:) 方法,从 operatingSystemsanswers 中取得它俩之间的差异 differences ,并且使用 applying() 方法将此差异应用到 answers 中,最终得到和 operatingSystems 相同元素和顺序的数组 sameAnswers

或者,你也可以不使用 applying() 方法,而是手动实现:

  // 1
  for change in differences.inferringMoves() {
    switch change {
      // 2
      case .insert(let offset, let element, let associatedWith):
        answers.insert(element, at: offset)
        guard let associatedWith = associatedWith else {
          print("\(element) inserted at position \(offset + 1).")
          break
        }
        print("""
              \(element) moved from position \(associatedWith + 1) to position 
              \(offset + 1).
              """)
      // 3
      case .remove(let offset, let element, let associatedWith):
        answers.remove(at: offset)
        guard let associatedWith = associatedWith else {
          print("\(element) removed from position \(offset + 1).")
          break
        }
        print("""
              \(element) removed from position \(offset + 1) because it should be 
                at position \(associatedWith + 1).
              """)
    }
  }
#endif

逐步分析:

  1. 使用 inferringMoves() 来确定差异中的移动元素,并遍历它们。
  2. 如果 change 类型是 .insert(offset:element:associatedWith:),则向 answers 数组中位于 offset 处 添加元素 element。如果 associatedWith 值不为 nil,则将此次操作视为 answers 数组内部的元素位置移动,而不是从外部新加的元素。
  3. 如果 change 类型是 .remove(offset:element:associatedWith:),则向 answers 数组中位于 offset 处 删除元素 element。如果 associatedWith 值不为 nil,则将此次操作视为 answers 数组内部的元素位置移动,而不是将该元素从数组中删除了。
Diffing collections like a pro in Swift 5.1!.png

9. Static and Class Subscripts

Swift 5.1 允许我们在类型(class / struct / enum) 中声明 staticclass subscripts [SE-0254]:

// 1
@dynamicMemberLookup
class File {
  let name: String

  init(name: String) {
    self.name = name
  }

  // 2
  static subscript(key: String) -> String {
    switch key {
      case "path":
        return "custom path"
      default:
        return "default path"
    }
  }

  // 3
  class subscript(dynamicMember key: String) -> String {
    switch key {
      case "path":
        return "custom path"
      default:
        return "default path"
    }
  }
}

// 4
File["path"]
File["PATH"]
File.path
File.PATH

逐步分析:

  1. @dynamicMemberLookup 标记 File ,以使 File 支持点语法的动态成员查找。
  2. 创建一个静态下标方法,返回默认值或自定义的值。
  3. 定义一个 class 类型的动态成员查找方法。
  4. 用相应的语法调用这两种下标。

Note: 想要了解更多关于 Swift subscripts 的知识吗?请看这里 Custom Subscripts in Swift.

10. Dynamic Member Lookup for Keypaths

Swift 5.1 实现了基于 keypaths 的动态成员查找方法 [SE-0252]:

// 1
struct Point {
  let x, y: Int
}

// 2
@dynamicMemberLookup
struct Circle<T> {
  let center: T
  let radius: Int

  // 3
  subscript<U>(dynamicMember keyPath: KeyPath<T, U>) -> U {
    center[keyPath: keyPath]
  }
}

// 4
let center = Point(x: 1, y: 2)
let circle = Circle(center: center, radius: 1)
circle.x
circle.y

逐步分析:

  1. Point 声明 xy 属性。
  2. @dynamicMemberLookup 标记 Circle ,以使 Circle 支持点语法的动态成员查找。
  3. 创建一个 使用 keypaths 的泛型下标方法,用来获取 Circlecenter 的属性。
  4. 使用动态成员查找来代替keypaths,直接在 circle 上调用 center 的属性。

Note: 想要了解更多关于动态成员查找的知识吗?请看 Dynamic Features in Swift.

11. Keypaths for Tuples

在 Swift 5.1 中,我们可以为元组使用 keypaths 了:

// 1
struct Instrument {
  let brand: String
  let year: Int
  let details: (type: String, pitch: String)
}

// 2
let instrument = Instrument(brand: "Roland",
                            year: 2019,
                            details: (type: "acoustic", pitch: "C"))
let type = instrument[keyPath: \Instrument.details.type]
let pitch = instrument[keyPath: \Instrument.details.pitch]

逐步分析:

  1. Instrument 声明 brand, yeardetails 属性。
  2. 使用 keypaths,从 instrumentdetails 属性中获取 typepitch

12. Equatable and Hashable Conformance for Weak and Unowned Properties

Swift 5.1 为有 weakunowned 存储属性的结构体 自动合成 EquatableHashable 一致性。

假设有如下两个类:

class Key {
  let note: String

  init(note: String) {
    self.note = note
  }
}

extension Key: Hashable {
  static func == (lhs: Key, rhs: Key) -> Bool {
    lhs.note == rhs.note
  }

  func hash(into hasher: inout Hasher) {
    hasher.combine(note)
  }
}

class Chord {
  let note: String

  init(note: String) {
    self.note = note
  }
}

extension Chord: Hashable {
  static func == (lhs: Chord, rhs: Chord) -> Bool {
    lhs.note == rhs.note
  }

  func hash(into hasher: inout Hasher) {
    hasher.combine(note)
  }
}

KeyChord 都遵守 EquatableHashable 协议,因为它们实现了 ==(lhs:rhs:)hash(into:) 方法。

如果你在结构体中使用这种类型,Swift 5.1 将自动为你合成 Hashable 一致性,而不需要你手动实现 ==(lhs:rhs:)hash(into:) 方法:

struct Tune: Hashable {
  unowned let key: Key
  weak var chord: Chord?
}

let key = Key(note: "C")
let chord = Chord(note: "C")
let tune = Tune(key: key, chord: chord)
let chordlessTune = Tune(key: key, chord: nil)
let sameTune = tune == chordlessTune
let tuneSet: Set = [tune, chordlessTune]
let tuneDictionary = [tune: [tune.key.note, tune.chord?.note], 
                      chordlessTune: [chordlessTune.key.note, 
                      chordlessTune.chord?.note]]

Tune 是遵守 EquatableHashable 协议的,因为 Tune 的属性 keychord 是遵守 EquatableHashable 的。

因为 Tune 遵守 Hashable 协议,所以你可以比较 tunechordlessTune ,把它们加入 tuneSet 集合中,并使用它们作为 tuneDictionary 字典的keys。

13. Ambiguous Enumeration Cases

Swift 5.1 会为模棱两可的枚举类型生成警告⚠️。

// 1
enum TutorialStyle {
  case cookbook, stepByStep, none
}

// 2
let style: TutorialStyle? = .none

逐步分析:

  1. TutorialStyle 定义不同的 style。
  2. Swift 发出一个警告,因为在这儿, .none 对于编译器来说是不明确的,它可以是 Optional.none ,也可以是 TutorialStyle.none

14. Matching Optional Enumerations Against Non-optionals

在 Swift 5 中,对于 可选的枚举类型,我们需要使用 可选模式 来匹配 不可选的值:

// 1
enum TutorialStatus {
  case written, edited, published
}

// 2
let status: TutorialStatus? = .published

switch status {
  case .written?:
    print("Ready for editing!")
  case .edited?:
    print("Ready to publish!")
  case .published?:
    print("Live!")
  case .none:
    break
}

逐步分析:

  1. TutorialStatus 声明所有可能的状态。
  2. 使用可选模式匹配来匹配 status ,因为我们把它定义为了可选值。

Swift 5.1 移除了此例中的可选模式匹配:

switch status {
  case .written:
    print("Ready for editing!")
  case .edited:
    print("Ready to publish!")
  case .published:
    print("Live!")
  case .none:
    break
}

代码更加清晰,且容易理解了。

Note: 想要了解更多关于模式匹配的知识吗?请看这里 Pattern Matching in Swift.

15. New Features for Strings

Swift 5.1 为字符串添加了一些急需的功能 [SE-0248]:

UTF8.width("S")
UTF8.isASCII(83)

现在,你可以确定 Unicode 标量值得 UTF-8 编码宽度,并检查给定的码元是否是一个 ASCII 标量。查看提案中其他可能使用的APIs [SE-0248].

16. Contiguous Strings

Swift 5.1 实现了对连续字符串的重要的更改 [SE-0247]:

var string = "Swift 5.1"
if !string.isContiguousUTF8 {
  string.makeContiguousUTF8()
}

你可以使用 isContiguousUTF8 来检查 UTF-8 编码的字符串在内存上是否是连续的,并使用 makeContiguousUTF8() 方法来使它成为连续的字符串。去提案中查看你还可以通过连续的字符串做些什么 [SE-0247]。

17. Converting Tuple Types

Swift 5.1 改进了元组类型的转换:

let temperatures: (Int, Int) = (25, 30)
let convertedTemperatures: (Int?, Any) = temperatures

现在可以把 temperatures 赋值给 convertedTemperatures,因为此例中 (Int, Int) 可以转换为 (Int?, Any)

18. Tuples with Duplicate Labels

在 Swift 5 中,可以用重复的标签声明元组中的元素:

let point = (coordinate: 1, coordinate: 2)
point.coordinate

此例中,我们并不能明确的知道 point.coordinate 是返回了第一个还是第二个元素。因此 Swift 5.1 不允许元组中声明重复的标签。

19. Overloading Functions with Any Parameters

对于只有一个参数的重载函数,Swift 5 更倾向于调用带 Any 参数的函数 而非 带泛型参数的函数:

func showInfo(_: Any) -> String {
  return "Any value"
}

func showInfo<T>(_: T) -> String {
  return "Generic value"
}

showInfo("Swift")

在 Swift 5 中, showInfo() 将返回 "Any value"

而在 Swift 5.1 中, showInfo() 将返回 Generic value

20. Type Aliases for Autoclosure Parameters

在 Swfit 5 中,不能为自动闭包参数 @autoclosure 声明别名:

struct Closure<T> {
  func apply(closure: @autoclosure () -> T) {
    closure()
  }
}

函数 apply(closure:) 中的参数 closure 使用了自动闭包签名。

在 Swift 5.1 中,可以在函数 apply(closure:) 中使用自动闭包类型参数的别名:

struct Closure<T> {
  typealias ClosureType = () -> T

  func apply(closure:  @autoclosure ClosureType) {
    closure()
  }
}

这次,函数 apply(closure:) 的参数 closure 使用了 ClosureType 类型来声明。

21. Returning Self From Objective-C methods

在 Swift 5 中,如果 **class ** 中含有 @objc 的方法并返回了 Self,则该 class 必须继承于 NSObject

class Clone: NSObject {
  @objc func clone() -> Self {
    return self
  }
}

例中 Clone 继承于 NSObject 因为 clone() 方法返回了 Self

在 Swift 5.1 中,将不再必须继承 NSObject

class Clone {
  @objc func clone() -> Self {
    self
  }
}

现在 Clone 不必继承任何类了。

22. Stable ABI Libraries

您可以在 Swift 5.1 中使用 -enable-library-evolution 来更改库类型,而不破坏它的 ABI。标记为 @frozen 的结构体和枚举不能 添加、删除 或 重新排序存储属性和方法 [SE-0260]。

最后

Swift 5.1 在 Swift 5 的基础上添加了许多很棒的功能。它也为 Swift语言带来了模块稳定性,并实现了 WWDC 中引入的新框架,如:SwiftUI 和 Combnine。

您可以在 Swift 官方的 CHANGELOG 或者 Swift standard library differences 中了解到更多关于 Swift 语言的变动。

您也可以在 Swift Evolution 中查看Swift下个版本会带来什么新特性。在这里,您可以为当前在审阅的提案提供反馈,甚至可以自己提出提案,为Swift语言舔砖加瓦!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容