raywenderlich.com官方Swift代碼規范指南

本文翻譯于The Official raywenderlich.com Swift Style Guide。目前更新到 Swfit4.2。

前言:作為一名開發人員,須知好的代碼規范,不僅能夠提升代碼的可讀性、提升開發效率同時也會對讓團隊間的開發溝通效果得到加強。針對Swift語言raywenderlich.com給出來一份較為完善的開發指南。本文就是基于原文翻譯而成。英文水平較好這可點擊上方鏈接,查看原文。

[TOC]

開發警告??

開發者應該努力做到代碼沒有警告的通過編譯,此規則會由許多的代碼編寫風格來決定,例如使用#selector類型來代替字符串字母量、盡量避免使用標記了DEPRECATED的API等。

命名規范

描述性和一致性的命名會使代碼更易于閱讀和理解。蘋果官方的API設計指南中也描述了Swift的命名約定,一些關鍵點包括:

  • 命名應當清晰。
  • 命名清晰度的重要性大于簡潔性,盡可能的表達清晰而不用害怕命名過長。
  • 使用駝峰法命名。
  • 命名針對類型和協議使用大寫字母開頭,其它都已小寫字母開頭。
  • 盡量包含所需要的單詞,同時省略不必要的單詞。
  • 命名的名稱應當基于當前語義,而不是類型。
  • 對信息缺失的命名可以適當補充。
  • 命名盡量使用流暢的語法
  • 工廠方法以make開頭
  • 命名方法考慮以下因素
    • 不可變(non-mutating)的動詞方法后接 -ed、-ing。
    • 不可變(non-mutating)的名詞方法遵守formX
    • boolean 類型的方法閱讀應當類似斷言。只有是、否兩種含義。
    • 描述某些東西的協議應該被理解為名詞(例如Collection)。
    • 描述一個協議能力應該使用后綴命名able,ible或ing (例如Equatable,ProgressReporting)。
  • 使用通俗易懂的術語,無論是新手還是專家都能輕松接受。
  • 一般來說避免使用縮寫。
  • 使用既定成俗的命名。
  • 首選方法和屬性來代替函數。
  • 通常縮略語和首字母縮略詞應根據案例慣例統一大寫或小寫。
  • 為具有相同含義的方法指定相同的基名。
  • 避免方法的返回值過多。
  • 將方法名稱命名部分放在第一個參數名中。
  • 選擇好的方法參數名稱,見文知意,起到好的說明效果。
  • 對閉包和元組類型參數說明
  • 利用好默認參數。

類前綴

Swift類型由包含它們的模塊自動命名,并且不應添加類前綴,例如RW。如果來自不同模塊的兩個名稱發生沖突,則可以通過在類型名稱前加上模塊名稱來消除歧義。但是,只有在存在混淆的時才指定模塊名稱,這在平時開發中應該很少出現。

import SomeModule

let myClass = MyModule.UsefulClass()

Delegates

創建自定義委托方法時,未命名的第一個參數應該是代理源。

推薦:

func  namePickerView(_  namePickerView:NamePickerView,didSelectName  name:String)
func  namePickerViewShouldReload(_  namePickerView:NamePickerView)- >  Bool

不推薦:

func didSelectName(namePicker: NamePickerViewController, name: String)
func namePickerShouldReload() -> Bool

類型推導

使用Swift的類型推導功能,減少代碼量,提升代碼簡潔性
推薦:

let selector = #selector(viewDidLoad)
view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)

不推薦:

let selector = #selector(ViewController.viewDidLoad)
view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)

泛型

泛型類型的參數應該具有描述性的,按駝峰命名。當一個泛型類名沒有一個有意義的名稱,可以使用傳統的單大寫字母代替,例如T,U或V。
推薦:

struct Stack<Element> { ... }
func write<Target: OutputStream>(to target: inout Target)
func swap<T>(_ a: inout T, _ b: inout T)

不推薦:

struct Stack<Element> { ... }
func write<Target: OutputStream>(to target: inout Target)
func swap<T>(_ a: inout T, _ b: inout T)

語法

使用美式的語法,美式語法會更加匹配蘋果的API。

推薦:

let color = "red"

不推薦:

let colour = "red"

代碼結構

使用extensions來組織你的代碼結構,每個extension中應該是一整塊的相關代碼。每個extensions之前都應添加// MARK: -注釋。

協議一致性

遵守協議時,針對每個協議下的方法添加單獨的擴展,使得協議方法組合在一起。

推薦:

class MyViewController: UIViewController {
  // class stuff here
}

// MARK: - UITableViewDataSource
extension MyViewController: UITableViewDataSource {
  // table view data source methods
}

// MARK: - UIScrollViewDelegate
extension MyViewController: UIScrollViewDelegate {
  // scroll view delegate methods
}

不推薦:

class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
  // all methods
}

在UIKit中的Viewcontroller中可以使用Extension來分離自定義訪問器和IBAction。

未使用的代碼

應當刪除未使用的代碼包括Xcode的模板代碼和默認的注釋、去除沒有意義的代碼。
推薦:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return Database.contacts.count
}

不推薦:

override func didReceiveMemoryWarning() {
  super.didReceiveMemoryWarning()
  // Dispose of any resources that can be recreated.
}

override func numberOfSections(in tableView: UITableView) -> Int {
  // #warning Incomplete implementation, return the number of sections
  return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  // #warning Incomplete implementation, return the number of rows
  return Database.contacts.count
}

最小import原則

只導入所需的模塊,例如當你只需要Foundation時,無需導入UIKit,當你需要UIKit時,無需再重復導入Foundation

推薦:

import UIKit
var view: UIView
var deviceModels: [String]

推薦:

import Foundation
var deviceModels: [String]

不推薦:

import UIKit
import Foundation
var view: UIView
var deviceModels: [String]

不推薦:

import UIKit
var deviceModels: [String]

代碼間隔

使用2個空格而不是制表符縮進以節省空間,Xcode中的設置可以按如下配置:

  • 方法括號和其他大括號(if/ else/ switch/while等)始終保持左括號與語法在相同的行上,右括號換行。
  • 您可以通過選擇一些代碼(或Command-A以選擇所有代碼)然后再按Control-I來重新縮進。

推薦:

if user.isHappy {
  // Do something
} else {
  // Do something else
}

不推薦:

if user.isHappy
{
  // Do something
}
else {
  // Do something else
}
  • 方法之間應該只有一個空行,以幫助視覺清晰度和組織。
  • 開閉的大括號和代碼之間不應該有空行
  • :一般左邊沒有空格,右邊一個空格。除了三目運算符? :、空字典[:]、和#selector的語法如addTarget(_:action:)

推薦:

class TestDatabase: Database {
  var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
}

不推薦:

class TestDatabase : Database {
  var data :[String:CGFloat] = ["A" : 1.2, "B":3.2]
}
  • 每行代碼應該限定在70個字符以內
  • 避免在行尾隨意的添加空格
  • 每個文件的末尾添加一個換行符

注釋

使用注釋來說明代碼塊的作用,注釋應當實時的更新或者刪除。

避免代碼內聯的塊注釋,代碼應該做到自我解釋,無需多的注釋說明就能看懂。

避免使用C語言的注釋風格(/* ... */)。使用 // 或者 ///來添加注釋。

類和結構體

類和結構體的選擇?

結構體是值語義,當我們定義一個沒有特性的事物的時候可以采用Struct類型,一個包含[a,b,c]的數組和另一個包含[a,b,c]的數組是可以互相替換的。無論你使用第一個數組還是第二個數組都表達的是同一個東西,所以Array就是一個Struct類型。

類是引用語義,當定義一個具有特性或者有特殊聲明周期的事物時采用Class類型。當你定義人的時候應該定義為類,兩個同姓名且同生日的人對象在含義上卻是兩個不同的東西。

Example 展示

規范的類定義

class Circle: Shape {
  var x: Int, y: Int
  var radius: Double
  var diameter: Double {
    get {
      return radius * 2
    }
    set {
      radius = newValue / 2
    }
  }

  init(x: Int, y: Int, radius: Double) {
    self.x = x
    self.y = y
    self.radius = radius
  }

  convenience init(x: Int, y: Int, diameter: Double) {
    self.init(x: x, y: y, radius: diameter / 2)
  }

  override func area() -> Double {
    return Double.pi * radius * radius
  }
}

extension Circle: CustomStringConvertible {
  var description: String {
    return "center = \(centerString) area = \(area())"
  }
  private var centerString: String {
    return "(\(x),\(y))"
  }
}

上面的例子展示了一下的代碼規范:

  • 指定屬性,變量,常量,參數聲明和其他語句的類型,在冒號前沒有空格而后面有空格。例如x: IntCircle: Shape
  • 在同一行代碼中定義相同類型的變量。如:var x: Int,y: Int
  • 縮進getter和setter定義以及屬性觀察器。
  • 不要添加修飾符,例如internal它們已經是默認值。同樣,重寫方法時也不要重復訪問修飾符。
  • 在Extension中添加額外的功能(例如打印)。
  • centerString使用private訪問控制隱藏實現細節。

使用self

為了代碼簡潔,應當在Swift中避免顯式使用self來訪問屬性及調用方法。

僅在編譯器要求使用self的時候使用(在@escaping閉包中、初始化器中用于消除參數中相同名稱的屬性歧義)

計算屬性

為簡明起見,如果計算屬性是只讀的,則省略get子句。只有在提供set子句時才需要get子句。

推薦:

var diameter : Double {
   return radius *  2 
}

不推薦:

var diameter : Double {
   get {
     return radius *  2 
  } 
}

final標記

特定的情況下使用final標記,當你明確知道你的類或者屬性成員是終極的時候可以使用final標記。在下面的示例中,Box具有特定目的并且不希望在派生類中進行自定義。標記它就final清楚了。

// 將泛型類型轉換成引用類型
final class Box<T> {
  let value: T
  init(_ value: T) {
    self.value = value
  }
}

函數聲明

在左括號下面添加一行簡短函數聲明。

func reticulateSplines(spline: [Double]) -> Bool {
  // reticulate code goes here
}

對于名稱和參數都較多的函數,將每個參數放在一個新行上,并在后續行中添加一個額外的縮進:

func  reticulateSplines(
   spline:[ Double ], 
   adjustmentFactor:Double,
   translateConstant:Int,comment:String 
)- >  Bool {
   // reticulate code goes here
}

使用()不要使用(Void)來表示參數為空,在閉包和函數返回時使用Void而不是()

推薦:

func updateConstraints() -> Void {
  // magic happens here
}

typealias CompletionHandler = (result) -> Void

不推薦:

func updateConstraints() -> () {
  // magic happens here
}

typealias CompletionHandler = (result) -> ()

函數調用

和函數聲明一樣,在函數調用時候如果單行就能表示就是用單行:

let success = reticulateSplines(splines)

如果調用函數參數過多時,每個參數換行并在后續行中添加一個額外的縮進:

let success = reticulateSplines(
  spline: splines,
  adjustmentFactor: 1.3,
  translateConstant: 2,
  comment: "normalize the display")

閉包表達式

僅當參數中只有一個閉包表達式參數且是最后一個參數的時候才使用尾隨閉包。

推薦:

UIView.animate(withDuration: 1.0) {
  self.myView.alpha = 0
}

UIView.animate(withDuration: 1.0, animations: {
  self.myView.alpha = 0
}, completion: { finished in
  self.myView.removeFromSuperview()
})

不推薦:

UIView.animate(withDuration: 1.0, animations: {
  self.myView.alpha = 0
})

UIView.animate(withDuration: 1.0, animations: {
  self.myView.alpha = 0
}) { f in
  self.myView.removeFromSuperview()
}

對于上下文清晰的單表達式閉包,使用隱式返回:

attendeeList.sort { a, b in
  a > b
}

使用尾隨閉包的鏈式方法應該清晰,易于在上下文中閱讀。關于間距,換行符以及何時使用命名與匿名參數的決定由作者自行決定。例子:

let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.index(of: 90)

let value = numbers
  .map {$0 * 2}
  .filter {$0 > 50}
  .map {$0 + 10}

類型

盡可能使用Swift的原生類型和表達式,Swift提供了Swift提供了與Objective-C的橋接,因此您仍然可以根據需要調用所有OC方法。

推薦:

let width = 120.0                                    // Double
let widthString = "\(width)"                         // String

一般:

let width = 120.0                                    // Double
let widthString = (width as NSNumber).stringValue    // String

不推薦:

let width: NSNumber = 120.0                          // NSNumber
let widthString: NSString = width.stringValue        // NSString

在繪圖的代碼中使用CGFloat類型,避免過多轉換使代碼更簡潔。

常量

如果變量的值將不會改變時始終使用let而不是var

Tip:你可以一開始就是用 let來定義所有的內容,當編譯器編譯錯誤的時候在改為var

你可以在一個類型里面去定義常量而不是在類型的實例變量中去使用類型屬性。使用static let聲明類型常量比直接聲明全局變量更推薦。這樣可以更好區分全局變量和實例屬性。

推薦:

enum Math {
  static let e = 2.718281828459045235360287
  static let root2 = 1.41421356237309504880168872
}

let hypotenuse = side * Math.root2

不推薦:

let e = 2.718281828459045235360287  // 污染全局命名空間
let root2 = 1.41421356237309504880168872

let hypotenuse = side * root2 // 什么 root2?

靜態方法和可變類型屬性

靜態方法和類型屬性跟全局函數和全局變量的工作原理類似,應當謹慎使用。當功能的作用域是一個特定類型或需要與 Objective-C 交互時,它們才非常有用。

可選類型

在可能出現 nil 值的情況下,使用 ?聲明變量和函數返回類型為可選類型。

當訪問一個可選值時,如果值僅被訪問一次或在鏈中有許多可選項時,使用可選鏈:

self.textContainer?.textLabel?.setNeedsDisplay()

當一次性解包和執行多個操作更方便時,使用可選綁定:

if let textContainer = self.textContainer {
// do many things with textContainer
}

在命名可選變量和屬性時,需避免類似optionalStringmaybeView 這樣的命名,因為他們的可選性已經體現在類型聲明中了。

對于可選綁定,適當時使用原始名稱,而不是使用像 unwrappedView 或 actualLabel 這樣的名稱。

不推薦:

var subview: UIView?
var volume: Double?

// later on...
if let subview = subview, let volume = volume {
  // 使用展開的 subview 和 volume 做某件事
}

推薦:

var optionalSubview: UIView?
var volume: Double?

if let unwrappedSubview = optionalSubview {
  if let realVolume = volume {
    // 使用 unwrappedSubview 和 volume 做某件事
  }
}

懶加載(延遲初始化)

在更細粒度地控制對象聲明周期時考慮使用延遲初始化。 對于 UIViewController ,延遲初始化視圖是非常正確的。你也可以直接調用 { }() 的閉包或調用私有工廠方法。例如:

lazy var locationManager: CLLocationManager = self.makeLocationManager()

private func makeLocationManager() -> CLLocationManager {
  let manager = CLLocationManager()
  manager.desiredAccuracy = kCLLocationAccuracyBest
  manager.delegate = self
  manager.requestAlwaysAuthorization()
  return manager
}

注意:

  • 因為沒有發生循環引用,所以這里不需要 [unowned self]。
  • 位置管理器向用戶彈出申請權限是影響用戶體驗的,所以細顆粒地控制在這里是有意義的。

類型推斷

優先選擇簡潔緊湊的代碼,讓編譯器為單個實例的常量或變量推斷類型。類型推斷也適合于小(非空)的數組和字典。如需使用特定類型,需提前聲明。如 CGFloat 或 Int16。

推薦:

let message = "Click the button"
let currentBounds = computeViewBounds()
var names = ["Mic", "Sam", "Christine"]
let maximumWidth: CGFloat = 106.5

不推薦:

let message: String = "Click the button"
let currentBounds: CGRect = computeViewBounds()
let names = [String]()

空數組和空字典的類型前置聲明

為空數組和空字典使用類型注釋。(對于分配給大型、多行文字的數組和字典,使用類型前置聲明。)

推薦:

var names: [String] = []
var lookup: [String: Int] = [:]

不推薦:

var names = [String]()
var lookup = [String: Int]()

語法糖

推薦使用類型聲明簡短的版本,而不是完整的泛型語法。

推薦:

var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?

不推薦:

var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?

函數 vs 方法

不在類或類型的中聲明的自由函數應該被謹慎使用。可能的話,首選方法而不是自由函數。這有助于可讀性和易領悟性。

自由函數最適用于它們與任何特定類或實例無關的情況。
推薦:

let sorted = items.mergeSorted()  // easily discoverable
rocket.launch()  // acts on the model

不推薦:

let sorted = mergeSort(items)  // hard to discover
launch(&rocket)

使用自由函數的特列:

let tuples = zip(a, b)  // feels natural as a free function (symmetry)
let value = max(x, y, z)  // another free function that feels natural

內存管理

在你的代碼中絕對不能出現循環引用的情況,使用內存分析工具并用 weakunowned 來防止強循環引用。或者,使用值類型( struct、enum )來徹底防止循環引用。

延長對象的生命周期

使用慣用語法 [weak self]guard let strongSelf = self else { return } 來延長對象的生命周期。 在 self超出閉包生命周期不明顯的地方,[weak self] 更優于[unowned self]。顯式的延長生命周期會優于使用可選解包。

推薦:

resource.request().onComplete { [weak self] response in
  guard let self = self else {
    return
  }
  let model = self.updateModel(response)
  self.updateUI(model)
}

不推薦:

// might crash if self is released before response returns
resource.request().onComplete { [unowned self] response in
  let model = self.updateModel(response)
  self.updateUI(model)
}

訪問控制

一般情況下privatefileprivate優先使用private,只有編譯器提示使用fileprivate才做修改。
將訪問控制用作前置屬性說明符。僅有 static 說明符或諸如 @IBAction 、 @IBOutlet 和 @discardableResult 的屬性可以放在訪問控制說明符前面。

推薦:

private let message = "Great Scott!"

class TimeMachine {  
  private dynamic lazy var fluxCapacitor = FluxCapacitor()
}

不推薦:

fileprivate let message = "Great Scott!"

class TimeMachine {  
  lazy dynamic private var fluxCapacitor = FluxCapacitor()
}

控制流

優先選擇 for 循環的 for-in 格式而不是 while-condition-increment 格式。

推薦:

for _ in 0..<3 {
  print("Hello three times")
}

for (index, person) in attendeeList.enumerated() {
  print("\(person) is at position #\(index)")
}

for index in stride(from: 0, to: items.count, by: 2) {
  print(index)
}

for index in (0...3).reversed() {
  print(index)
}
var i = 0
while i < 3 {
  print("Hello three times")
  i += 1
}

var i = 0
while i < attendeeList.count {
  let person = attendeeList[i]
  print("\(person) is at position #\(i)")
  i += 1
}

黃金路徑

不要嵌套多個條件判斷,可以提前將結果返回。guard語句就是因為這個存在的。

推薦:

func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {

  guard let context = context else {
    throw FFTError.noContext
  }
  guard let inputData = inputData else {
    throw FFTError.noInputData
  }

  // use context and input to compute the frequencies
  return frequencies
}

不推薦:

func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {

  if let context = context {
    if let inputData = inputData {
      // use context and input to compute the frequencies

      return frequencies
    } else {
      throw FFTError.noInputData
    }
  } else {
    throw FFTError.noContext
  }
}

當用 guard 或 if let 解包多個可選值時,在可能的情況下使用最下化復合版本嵌套。舉例:

推薦:

guard 
  let number1 = number1,
  let number2 = number2,
  let number3 = number3 
  else {
    fatalError("impossible")
}
// do something with numbers

不推薦:

if let number1 = number1 {
  if let number2 = number2 {
    if let number3 = number3 {
      // do something with numbers
    } else {
      fatalError("impossible")
    }
  } else {
    fatalError("impossible")
  }
} else {
  fatalError("impossible")
}

失敗防護

對于用某些方法退出,防護語句是必要的。一般地,它應該是一行簡潔的語句,比如: return 、 throw 、 break 、 continue 和 fatalError()。應該避免大的代碼塊。如果清理代碼被用在多個退出點,則可以考慮用 defer 塊來避免清理代碼的重復。

分號

在 Swift 中,每條代碼語句后面都不需要加分號。只有在你希望在一行中結合多條語句,才需要加分號。

不要在用分號分隔的單行中寫多條語句。

推薦:

let swift = "not a scripting language"

不推薦:

let swift = "not a scripting language";

括號

條件判斷周圍的括號是不必要的,應該省略。

推薦:

if name == "Hello" {
  print("World")
}

不推薦:

if (name == "Hello") {
  print("World")
}

在復雜的表達式中,添加括號可以讓代碼讀起來更清晰。

推薦:

let playerMark = (player == current ? "X" : "O")

多行字符串字面量

當你創建長字符串文字時,推薦使用多行字符串字面量,左右"""和字符串間隔開。

推薦:

let message = """
  You cannot charge the flux \
  capacitor with a 9V battery.
  You must use a super-charger \
  which costs 10 credits. You currently \
  have \(credits) credits available.
  """

不推薦:

let message = """You cannot charge the flux \
  capacitor with a 9V battery.
  You must use a super-charger \
  which costs 10 credits. You currently \
  have \(credits) credits available.
  """

不推薦:

let message = "You cannot charge the flux " +
  "capacitor with a 9V battery.\n" +
  "You must use a super-charger " +
  "which costs 10 credits. You currently " +
  "have \(credits) credits available."

不要使用Emoji

在你定義變量或者方法中不要使用Emoji。Emoji會增加開發人員對代碼的理解難度。

Xcode中的Organization和Bundle Identifier設置

Xcode中組織名稱應該是你單位或者你個人的名稱,如Ray Wenderlich,Bundle Identifier一般采用com/cn +公司名+項目名形式。如當你項目名稱為TutorialName時,Bundle Identifier則應該命名為 ·com.raywenderlich.TutorialName`

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容