淺談Swift的內(nèi)存管理

Swift
  • 原文博客地址: 淺談Swift的內(nèi)存管理
  • 2020年的第一篇博客, 算是2020年開了個好頭, 希望能夠繼續(xù)堅(jiān)持下去, 繼續(xù)記錄分享更多更高質(zhì)量的文章
  • 今年期待已久的Swift5.0穩(wěn)定版就已經(jīng)發(fā)布了, 感興趣的小伙伴可看我的這篇博客:Swift 5.0新特性更新
  • 這篇博客可主要分享Swift的內(nèi)存管理的相關(guān)介紹和剖析, 測試環(huán)境: Xcode 11.2.1, Swift 5.1.2

內(nèi)存管理

  • OC一樣, 在Swift中也是采用基于引用計數(shù)的ARC內(nèi)存管理方案(針對堆空間的內(nèi)存管理)
  • SwiftARC中有三種引用
    • 強(qiáng)引用(strong reference):默認(rèn)情況下,代碼中涉及到的引用都是強(qiáng)引用
    • 弱引用(weak reference):通過weak定義弱引用
    • 無主引用(unowned reference):通過unowned定義無主引用

weak

  • 弱引用(weak reference):通過weak定義弱引用
    • 必須是可選類型的var,因?yàn)閷?shí)例銷毀后,ARC會自動將弱引用設(shè)置為nil
    • ARC自動給弱引用設(shè)置nil時,不會觸發(fā)屬性觀察
  • 在介紹weak弱引用之前, 先看一下下面一段代碼
class Animal {
    deinit {
        print("Animal deinit")
    }
}

func test() {
    let animal = Animal()
}

print("will deinit")
test()
print("did deinit")

上面這段代碼中在test函數(shù)調(diào)用結(jié)束之后, 該作用的內(nèi)存就會被回收, animal對象自然就會被銷毀, 毫無疑問上面的輸出結(jié)果應(yīng)該是

will deinit
Animal deinit
did deinit

同樣下面這段代碼, 同樣也是在a1對象被置為nil的時候內(nèi)存會被回收, 對象就會被銷毀

var a1: Animal? = Animal()
print("will deinit")
a1 = nil
print("did deinit")
  • 下面是一個被weak修飾的弱引用對象,
  • 我們都知道, 被weak修飾的弱引用對象, 在對象銷毀的時候, 會被自動置為nil
  • 所以被weak修飾的弱引用對象必須是可選類型的var, 兩個條件缺一不可
weak var a2: Animal? = Animal()

// 以下兩種方式都會報錯的
weak var a2: Animal = Animal()
weak let a2: Animal? = Animal()

unowned

  • 無主引用(unowned reference):通過unowned定義無主引用
  • 不會產(chǎn)生強(qiáng)引用,實(shí)例銷毀后仍然存儲著實(shí)例的內(nèi)存地址(類似于OC中的unsafe_unretained
  • 試圖在實(shí)例銷毀后訪問無主引用,會產(chǎn)生運(yùn)行時錯誤(如下野指針)
Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocate

另外需要注意的是, weakunowned只能用在類實(shí)例上面, 如下所示

// 該協(xié)議表示只能被類遵守, AnyObject代表所有的類實(shí)例
protocol Liveable: AnyObject {}
class Person {}

weak var p0: Person?
weak var p1: AnyObject?
// 所有能遵循Liveable協(xié)議的肯定都是類
weak var p2: Liveable?

unowned var p10: Person?
unowned var p11: AnyObject?
unowned var p12: Liveable?

循環(huán)引用

  • weakunowned都能解決循環(huán)引用的問題,unowned要比weak少一些性能消耗
  • 在生命周期中可能會變?yōu)?code>nil的使用weak
  • 初始化賦值后再也不會變?yōu)?code>nil的使用unowne
  • 說道循環(huán)引用就自然想到了閉包

閉包的循環(huán)引用

閉包表達(dá)式默認(rèn)會對用到的外層對象產(chǎn)生額外的強(qiáng)引用(對外層對象進(jìn)行了retain操作), 看一下下面的代碼中deinit會被調(diào)用嗎?

class Person {
    var fn: (() -> ())?
    func run() { print("run") }
    deinit { print("deinit") }
}

func test() {
    let p = Person()
    p.fn = { 
        p.run()
    }
}

test()
  • 上面代碼中, p對象強(qiáng)引用著fn閉包, fn閉包也強(qiáng)引用著p對象, 自然就造成了循環(huán)引用問題
  • 最后沒有任何輸出結(jié)果, 我們看一下上述代碼的匯編執(zhí)行過程
weak
  • 從上面匯編代碼可以看出, 整個過程經(jīng)歷了
  • 一次init引用計數(shù)為----1
  • 一次retain引用計數(shù)會加(1), 結(jié)果為----2
  • 一次release引用計數(shù)會減(1), 結(jié)果為----1
  • 那么最后的引用計數(shù)就是1, 所以p對象肯定沒有被釋放
  • 下面是使用解決循環(huán)引用的情況
    • 在閉包表達(dá)式的捕獲列表里, 聲明weakunowned引用,用以解決循環(huán)引用問題
// 使用weak
func test() {
    let p = Person()
    p.fn = { [weak p] in
        p?.run()
    }
}

// 使用unowned
func test() {
    let p = Person()
    p.fn = { [unowned p] in
        p.run()
    }
}
  • 上述兩種方式都可以解決循環(huán)引用的問題, 運(yùn)行后就發(fā)現(xiàn)Person對象調(diào)用了deinit
  • 這里我們再看一下匯編代碼如下, 從下面匯編代碼中可以很明顯看到, 引用計數(shù)最后為0, 對象被釋放
weak

另外下面這段代碼其實(shí)是等價的

func test() {
    let p = Person()
    p.fn = { [unowned p] in
        p.run()
    }
}

// 和上面等價代碼
func test() {
    let p = Person()
    p.fn = { [unowned ownedP = p, weak weakP = p] in
        ownedP.run()
        // weakP?.run()
    }
}

特別注意點(diǎn), 這里要區(qū)分捕獲列表和參數(shù)列表, 下面看看fn有參數(shù)的情況下

class Person {
    var fn: ((Int) -> ())?
    func run() { print("run") }
    deinit { print("deinit") }
}

func test() {
    let p = Person()
    p.fn = {
        (num) in
        print("num = \(num)")
    }
}

那么閉包的參數(shù)列表和捕獲列表同事存在的情況如下代碼所示

func test() {
    let p = Person()
    p.fn = {
        [weak p](num) in
        print("num = \(num)")
        p?.run()
    }
}

self的循環(huán)引用

  • 如果想在引用閉包的同時引用self, 這個閉包必須是lazy
  • 因?yàn)閷?shí)例在初始化完畢之后才能引用self
class Person {
    lazy var fn: (() -> ()) = {
        self.run()
    }
    func run() { print("run") }
    deinit { print("deinit") }
}

func test() {
    let p = Person()
    p.fn()
}

test()
  • 上面代碼中如果fn閉包去掉lazy, 編譯器會直接報錯
  • Swift中, 為了保證初始化的安全, 設(shè)定了兩段式初始化, 在所有的存儲屬性被初始化完成之后, 初始化器才能夠使用self
  • 而且在上述fn閉包中, 如果fn內(nèi)部用到了實(shí)例成員(屬性和方法), 則編譯器會強(qiáng)制要求明確寫出self
  • lazy既保證只有在使用的時候才會被初始化一次
  • 但是上述代碼同樣存在循環(huán)引用的問題, Person對象強(qiáng)引用著fn閉包, fn閉包也強(qiáng)引用著self
  • 同樣使用weakunowned解決循環(huán)引用的問題
// weak解決循環(huán)引用
lazy var fn: (() -> ()) = {
    [weak self] in
    self?.run()
}

// unowned解決循環(huán)引用
lazy var fn: (() -> ()) = {
    [unowned self] in
    self.run()
}

另外再看看下面這種情況, 是都存在循環(huán)引用的問題

class Student {
    var age: Int = 2
    lazy var getAge: Int = {
        self.age
    }()
    deinit { print("deinit") }
}

func test() {
    let p = Student()
    print(p.getAge)
}

test()

/* 輸出結(jié)果
2
deinit
*/

通過輸出結(jié)果看一看出調(diào)用了deinit, 說明對象最終被釋放, 并未出現(xiàn)循環(huán)引用的問題, 下面比較一下

// 存在循環(huán)引用
class Person {
    lazy var fn: (() -> ()) = {
        self.run()
    }
    func run() { print("run") }
    deinit { print("deinit") }
}

// 不存在循環(huán)引用
class Student {
    var age: Int = 2
    lazy var getAge: Int = {
        self.age
    }()
    deinit { print("deinit") }
}
  • 上述兩種寫法的區(qū)別, 本質(zhì)上說
  • Person對象中的fn閉包屬于閉包賦值
  • Student對象那個中的getAge屬于閉包調(diào)用(類似函數(shù)調(diào)用)
  • 相當(dāng)于在在Student對象調(diào)用getAge結(jié)束之后, 作用域內(nèi)的變量就會被釋放
// getAge也可以寫成如下形式
lazy var getAge: Int = {
    return self.age
}()

// 也可以理解為
lazy var getAge: Int = self.age

內(nèi)存訪問沖突

Swift中的內(nèi)存訪問沖突主要在兩個訪問滿足下列條件時發(fā)生

  • 至少一個是寫入操作
  • 它們訪問的是同一塊內(nèi)存
  • 它們的訪問時間重疊(比如在同一個函數(shù)內(nèi))
  • 對比看看以下兩個函數(shù)操作
// 不存在內(nèi)存訪問沖突
var number = 1
func plus(_ num: inout Int) -> Int {
    return num + 1
}
number = plus(&number)

// 存在內(nèi)存訪問沖突
var step = 1
func increment(_ num: inout Int) {
    num += step
}
increment(&step)

上面第二部分代碼就是同時對step變量執(zhí)行讀寫操作, 運(yùn)行時會報出如下錯誤

Simultaneous accesses to 0x100002028, but modification requires exclusive access.

再看下面對于結(jié)構(gòu)體和元組的使用, 這里先定義一個全局函數(shù)和一個結(jié)構(gòu)體

// 改變兩個傳入?yún)?shù)的值, 讀取并修改傳入?yún)?shù)的值
func balance(_ x: inout Int, _ y: inout Int) {
    let sum = x + y
    x = sum / 2
    y = sum - x
}

// 定義Player結(jié)構(gòu)體
struct Player {
    var name: String
    var health: Int
    var energy: Int
    mutating func shareHealth(with teammate: inout Player) {
        balance(&teammate.health, &health)
    }
}

再看下面的使用示例, 兩者都會有一個內(nèi)存訪問沖突的錯誤

// 這里讀寫的是同一個maria
var maria = Player(name: "Maria", health: 50, energy: 10)
balance(&maria.health, &maria.energy)

// 這里讀寫的是同一個tuple
var tuple = (health: 10, energy: 20)
balance(&tuple.health, &tuple.energy)

但是有時候的確會有上面這種訪問同一塊內(nèi)存的需求, 如果下面的條件滿足, 就說明重疊訪問結(jié)構(gòu)體的屬性是安全的

  • 訪問的是實(shí)例存儲屬性, 不是計算屬性或者類屬性
  • 結(jié)構(gòu)體是局部變量而非全局變量
  • 結(jié)構(gòu)體要么沒有被閉包捕獲要么只被非逃逸閉包捕獲
// 這里可以在局部作用域內(nèi)定義成局部變量, 就不會有問題了
func test() {
    var maria = Player(name: "Maria", health: 50, energy: 10)
    var tuple = (health: 10, energy: 20)
    balance(&tuple.health, &tuple.energy)
    balance(&maria.health, &maria.energy)
}

指針

class Person {}
var person = Person()
  • Swiftclass聲明的類(Person)是引用類型, 初始化的person對象其本質(zhì)上就是一個指針變量
  • person里面存儲的就是這個指針變量的地址值, 也就可以根據(jù)這個地址值去訪問被分配的內(nèi)存空間
  • 指針在某種意義上被定性為不安全的, 舉個例子:
    • 當(dāng)前指針變量的地址值對應(yīng)的空間只有32個字節(jié), 但有可能訪問的是超過32個字節(jié)的空間, 這樣就可能會出問題的

指針分類

Swift中也有專門的指針類型,這些都被定性為Unsafe(不安全的),常見的有以下4種類型

  • UnsafePointer<Pointee>, 類似于C語言中的const Pointee *, 只能訪問內(nèi)存不能修改內(nèi)存, 這里的Pointee是指泛型
  • UnsafeMutablePointer<Pointee>類似于C語言中的Pointee *, 可以訪問和修改內(nèi)存, 這里的Pointee是指泛型
  • UnsafeRawPointer類似于const void *, 不支持泛型
  • UnsafeMutableRawPointer類似于void, 不支持泛型

下面看一下具體的使用示例

var age = 10
func sum1(_ ptr: UnsafeMutablePointer<Int>) {
    // 通過訪問pointee屬性, 獲取ptr指針的內(nèi)存地址所存儲的值
    // UnsafeMutablePointer的pointee屬性是可讀可寫的
    ptr.pointee += 10
}
func sum2(_ ptr: UnsafePointer<Int>) {
    // UnsafePointer的pointee屬性是只讀的
    // ptr.pointee += 10
    print(ptr.pointee)
}
func sum3(_ num: inout Int) {
    // 
    num += 10
}

// 和inout輸入輸出參數(shù)一樣接受變量的地址值
sum1(&age)
sum2(&age)
sum3(&age)
print(age)


func sum4(_ ptr: UnsafeMutableRawPointer) {
    // 可讀可寫, 取值
    print("age = ", ptr.load(as: Int.self))
    // 可讀可寫, 賦值
    ptr.storeBytes(of: 50, as: Int.self)
}
func sum5(_ ptr: UnsafeRawPointer) {
    // 只讀, 取值
    print("age = ", ptr.load(as: Int.self))
}

sum4(&age)
sum5(&age)

獲得變量的指針

Swift中有可以直接獲取變量的指針的方法

// 獲取可變的變量指針, value參數(shù)接受變量地址
@inlinable public func withUnsafeMutablePointer<T, Result>(to value: inout T, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result
// 獲取不可變的變量指針, value參數(shù)接受變量
@inlinable public func withUnsafePointer<T, Result>(to value: T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
// 獲取不可變的變量指針, value參數(shù)接受變量地址
@inlinable public func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result

上述方法中返回值默認(rèn)是變量的指針地址, 也可以是其他的數(shù)據(jù)類型, 主要取決于body閉包的返回值, 返回值類型由閉包中的Result泛型決定

var age = 10
var ptr1 = withUnsafeMutablePointer(to: &age) { $0 }   // UnsafeMutablePointer<Int>
var ptr2 = withUnsafePointer(to: &age) { $0 }          // UnsafePointer<Int>
ptr1.pointee = 22
print(ptr2.pointee) // 22
print(ptr2)         // 0x0000000100008310

var ptr3 = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0) }   // UnsafeMutableRawPointer
var ptr4 = withUnsafePointer(to: &age) { UnsafeRawPointer($0) }                 // UnsafeRawPointer
// as參數(shù)是需要存儲什么類型的數(shù)據(jù)
ptr3.storeBytes(of: 33, as: Int.self)
print(ptr4.load(as: Int.self)) // 33
print(ptr4)         // 0x0000000100008310

創(chuàng)建指針

  • 之前獲取到的指針都是根據(jù)已經(jīng)存在的內(nèi)存獲取的
  • 這里就看看重新分配一塊內(nèi)存指向堆空間

malloc

Swift提供了malloc直接分配內(nèi)存創(chuàng)建指針的方式

// 根據(jù)需要分配的內(nèi)存大小創(chuàng)建一個指針
public func malloc(_ __size: Int) -> UnsafeMutableRawPointer!
// 釋放內(nèi)存
public func free(_: UnsafeMutableRawPointer!)


// 下面這兩個函數(shù), 是賦值和取值的函數(shù), 之前簡單介紹過
// 參數(shù)一: 需要存儲的值
// 參數(shù)二: 偏移量, 從第幾個字節(jié)開始存儲, 默認(rèn)從第一個
// 參數(shù)三: 需要存儲的值的類型
@inlinable public func storeBytes<T>(of value: T, toByteOffset offset: Int = 0, as: T.Type)

// 參數(shù)一: 偏移量, 從第幾個字節(jié)開始存儲, 默認(rèn)從第一個
// 參數(shù)二: 需要存儲的值的類型
@inlinable public func load<T>(fromByteOffset offset: Int = 0, as type: T.Type) -> T

代碼示例如下

// 創(chuàng)建指針
var ptr = malloc(16)
// 存儲值
ptr?.storeBytes(of: 10, as: Int.self)
// 這里toByteOffset參數(shù)如果傳0, 就會覆蓋前8個字節(jié)的數(shù)據(jù)
ptr?.storeBytes(of: 12, toByteOffset: 8, as: Int.self)
// 取值
print(ptr?.load(as: Int.self) ?? 0)
print(ptr?.load(fromByteOffset: 8, as: Int.self) ?? 0)
// 銷毀, 釋放內(nèi)存
free(ptr)

allocate

使用allocate方式創(chuàng)建指針, 代碼示例如下

// byteCount: 需要申請的字節(jié)數(shù), alignment: 對其字節(jié)數(shù)
var ptr2 = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
// 存儲
ptr2.storeBytes(of: 9, as: Int.self)

// 根據(jù)字節(jié)偏移存儲
// 這里的ptr3是ptr2偏移8個字節(jié)的新的指針地址
var ptr3 = ptr2.advanced(by: 8)  // UnsafeMutableRawPointer
ptr3.storeBytes(of: 12, as: Int.self)

// 上面這種方式等價于
ptr2.storeBytes(of: 12, toByteOffset: 8, as: Int.self)

// 取值同樣
print(ptr2.load(as: Int.self))
// 下面這兩種取值方式也是一樣的
print(ptr2.advanced(by: 8).load(as: Int.self))
print(ptr2.load(fromByteOffset: 8, as: Int.self))

// 釋放內(nèi)存
ptr2.deallocate()

這里需要注意的是只有UnsafeMutableRawPointer才有allocate分配方法, UnsafeRawPointer是沒有這個方法的, 下面說到的UnsafeMutablePointer<T>類型也是, UnsafePointer<T>沒有allocate分配方法

// capacity: 容量, 即可以存儲3個Int類型的數(shù)據(jù), 也就是24個字節(jié)
var ptr = UnsafeMutablePointer<Int>.allocate(capacity: 3)
// 初始化內(nèi)存, 用10初始化錢8個字節(jié)
ptr.initialize(to: 10)
// 用10初始化前兩個容量的內(nèi)存, 即16個字節(jié)
ptr.initialize(repeating: 10, count: 2)
// 使用successor獲取下一個存儲位, 也就是下一個Int的位置
var ptr1 = ptr.successor()  //  UnsafeMutablePointer<Int>
ptr1.initialize(to: 20)
// 存儲第三個Int值
ptr.successor().successor().initialize(to: 30)

// 取值的兩種方式
print(ptr.pointee)        // 第一個值
print((ptr + 1).pointee)  // 第二個值
print((ptr + 2).pointee)  // 第三個值

// 下面這種方式和上面等價
print(ptr[0])
print(ptr[1])
print(ptr[2])

// 前面如果使用了initialize, 則必須調(diào)用反初始化
// 而且count要和上面allocate(capacity: 3)的capacity一致, 否則會造成內(nèi)存泄露的問題
ptr.deinitialize(count: 3)
ptr.deallocate()

指針之間的轉(zhuǎn)換

前面提到過Swift中的指針類型有四種

  • UnsafePointer<Pointee>類似于const Pointee *
  • UnsafeMutablePointer<Pointee>類似于Pointee *
  • UnsafeRawPointer類似于const void *
  • UnsafeMutableRawPointer類似于void *
  • 那么上面的類型, 能否通過其中的一種創(chuàng)建另外一種指針呢, 下面我們來看一下

init

UnsafeMutableRawPointer中有一個初始化方法可以根據(jù)UnsafeMutablePointer創(chuàng)建自身

public init<T>(_ other: UnsafeMutablePointer<T>)

var ptr = UnsafeMutablePointer<Int>.allocate(capacity: 3)
var ptr1 = UnsafeMutableRawPointer(ptr)

assumingMemoryBound

反過來, UnsafeMutableRawPointer也提供了一個方法用于創(chuàng)建UnsafePointer

public func assumingMemoryBound<T>(to: T.Type) -> UnsafePointer<T>

var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
var ptr1 = ptr.assumingMemoryBound(to: Int.self)
// 初始化前8個字節(jié)
ptr1.pointee = 11
// 初始化后8個字節(jié)
// 特別注意, 這里的(ptr + 8)是指ptr向后偏移8個字節(jié), 要和之前的區(qū)分開
(ptr + 8).assumingMemoryBound(to: Int.self).pointee = 12

ptr.deallocate()

unsafeBitCast

unsafeBitCast是忽略數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)換,不會因?yàn)閿?shù)據(jù)類型的變化而改變原來的內(nèi)存數(shù)

// 把第一個參數(shù)類型轉(zhuǎn)成第二個參數(shù)類型
@inlinable public func unsafeBitCast<T, U>(_ x: T, to type: U.Type) -> U


var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
unsafeBitCast(ptr, to: UnsafeMutablePointer<Int>.self).pointee = 13
// 注意, 這里的(ptr + 8)是指ptr向后偏移8個字節(jié), 要和之前的區(qū)分開
unsafeBitCast(ptr + 8, to: UnsafeMutablePointer<Double>.self).pointee = 14.23

ptr.deallocate()

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

推薦閱讀更多精彩內(nèi)容