函數(shù)響應(yīng)式編程和 RxSwift+MVVM

Function Reactive Programming:函數(shù)響應(yīng)式編程是種編程范式。我們通過(guò)不同的構(gòu)建函數(shù),來(lái)創(chuàng)建所需要的數(shù)據(jù)序列。最后通過(guò)適當(dāng)?shù)姆绞絹?lái)響應(yīng)這個(gè)序列。這就是函數(shù)響應(yīng)式編程。它結(jié)合了函數(shù)式編程以及響應(yīng)式編程。

函數(shù)式編程

Masonry、SnapKit 就是我們最常見(jiàn)的函數(shù)式編程,通過(guò)對(duì)象.方法1().方法2.....

在程序開發(fā)中:
a = b + c

賦值之后 b 或者 c 的值變化后,a 的值不會(huì)跟著變化

響應(yīng)式編程

響應(yīng)式編程,目標(biāo)就是,如果 b 或者 c 的數(shù)值發(fā)生變化,a 的數(shù)值會(huì)同時(shí)發(fā)生變化;

RxSwift 核心思想就是 FRP。

一、RxSwift 是什么?

先說(shuō)下 Rx:

ReactiveX,簡(jiǎn)寫 Rx,是一個(gè)可以幫助我們簡(jiǎn)化異步編程的框架,它對(duì)觀察者模式進(jìn)行了擴(kuò)展,讓我們可以自由組合多個(gè)事件。(Rx 支持幾乎全部的流行編程語(yǔ)言,有 RxJava/RxJS/Rx.NET 等)

社區(qū)網(wǎng)站: reactivex.io

然后再說(shuō)下 RxSwift :

RxSwift 是 Rx 的 Swift 版本,它嘗試將原有的一些概念移植到 iOS/macOS 平臺(tái)。

RxSwift 將程序中的事件傳遞相應(yīng)方法進(jìn)行了統(tǒng)一,將其(delegate、notification、target-action 等)全替換成 Rx 的“信號(hào)鏈”方式

基本原理:

用戶輸入、點(diǎn)擊事件、定時(shí)器、網(wǎng)絡(luò)請(qǐng)求等都可以當(dāng)成 Observable(被觀察者),Observer(觀察者)總會(huì)在 Observable 處注冊(cè)一個(gè)訂閱,當(dāng)事件發(fā)生時(shí),Observable 找到所有的訂閱并通知觀察者。

二、為什么要使用 RxSwift

復(fù)合 - Rx 就是復(fù)合的代名詞

復(fù)用 - 因?yàn)樗讖?fù)合

清晰 - 因?yàn)槁暶鞫际遣豢勺兏?/p>

易用 - 因?yàn)樗橄蟮牧水惒骄幊蹋刮覀兘y(tǒng)一了代碼風(fēng)格

穩(wěn)定 - 因?yàn)?Rx 是完全通過(guò)單元測(cè)試的

具體來(lái)說(shuō)就是

在編寫代碼時(shí)我們經(jīng)常會(huì)需要檢測(cè)某些值的變化,然后進(jìn)行相應(yīng)的處理。比如說(shuō)按鈕的點(diǎn)擊事件、textFiled 值的變化、tableView 中 cell 的點(diǎn)擊事件等等。在日常搬磚中,我們針對(duì)不同的情況,需要采用不同的事件傳遞方法去處理,比如 delegate、Notifinotion、Target Action、KVO 等等。

三、如何使用 RxSwift

引入的兩個(gè)庫(kù)

import RxSwift
import RxCocoa
  • RxSwift:它只是基于 Swift 語(yǔ)言的 Rx 標(biāo)準(zhǔn)實(shí)現(xiàn)接口庫(kù),所以 RxSwift 里不包含任何 Cocoa 或者 UI 方面的類。
  • RxCocoa:是基于 RxSwift 針對(duì)于 iOS 開發(fā)的一個(gè)庫(kù),它通過(guò) Extension 的方法給原生的比如 UI 控件添加了 Rx 的特性,使得我們更容易訂閱和響應(yīng)這些控件的事件。

看下面幾個(gè)例子:

1. Target Action

傳統(tǒng)方式實(shí)現(xiàn)

button.addTarget(self, action: #selector(buttonEvent), for: .touchUpInside)

@objc func buttonEvent() {
    print("button Event")
}

用 RxSwift 實(shí)現(xiàn)

button.rx.tap
    .subscribe(onNext: {
        print("button event")
    })
    .disposed(by: disposeBag)

2. Notifinotion

傳統(tǒng)方式實(shí)現(xiàn)

NotificationCenter.default.addObserver(self, selector: #selector(notificationAction(_:)), name: NSNotification.Name("NotificationName"), object: nil)

@objc func notificationAction(_ notification: Notification) {
    // do something
}

用 RxSwift 實(shí)現(xiàn)

NotificationCenter.default.rx.notification(NSNotification.Name("NotificationName"))
    .subscribe(onNext: { (notification) in
        // do something
    })
    .disposed(by: disposeBag)

3. delegate

傳統(tǒng)方式實(shí)現(xiàn)

// 遵循代理
class ViewController: UITextFieldDelegate 
// 設(shè)置代理
textField.delegate = self
// 實(shí)現(xiàn)代理方法
func textFieldDidEndEditing(_ textField: UITextField) {
    // do something
}

用 RxSwift 實(shí)現(xiàn)

textField.rx.controlEvent([.editingDidEnd])
    .asObservable()
    .subscribe(onNext: { _ in
        // do something
    })
    .disposed(by: disposeBag)


// 字符輸入時(shí)
textField.rx.controlEvent([.editingChanged])
    .asObservable()
    .subscribe(onNext: { _ in
        print("字符輸入:\(self.textField.text ?? "")")
    })
    .disposed(by: disposeBag)
    
// 或者
textField.rx.text
    .subscribe(onNext: { text in
        print("text:\(text ?? "")")
        })
    .disposed(by: disposeBag)

RxCocoa 用 extensiton 的方式,為 UITextfield,UIlabel 等控件添加了很多可監(jiān)聽的屬性,這里的 textfield.rx.text 就是一個(gè)

看上面幾個(gè)例子,和傳統(tǒng)方式相比,Rx 的代碼更加清晰簡(jiǎn)潔,易讀、易維護(hù)。

4. BehaviorRelay

作用:存儲(chǔ)時(shí)常需要更新的數(shù)據(jù)

let number: BehaviorRelay = BehaviorRelay<Int>(value: 0)
let disposeBag = DisposeBag()

// 訂閱 number 的變化,skip(1): 跳過(guò)初始化時(shí)值的變化
number.skip(1).subscribe(onNext: { (num) in
    print("數(shù)字變化:\(num)")
}).disposed(by: disposeBag)


for i in 1...5 {
    number.accept(i)
}

輸出結(jié)果

數(shù)字變化:1
數(shù)字變化:2
數(shù)字變化:3
數(shù)字變化:4
數(shù)字變化:5

5. 綁定

let isCodeValid = textField.rx.text
    .orEmpty
    .map { $0.count == 6 }
    .share(replay: 1)

// 驗(yàn)證碼輸入是否有效 -> 驗(yàn)證碼提示語(yǔ)是否隱藏
isCodeValid
    .bind(to: errorLabel.rx.isHidden)
    .disposed(by: disposeBag)
// 驗(yàn)證碼輸入是否有效 -> 提交按鈕是否可點(diǎn)擊
isCodeValid
    .bind(to: button.rx.isEnabled)
    .disposed(by: disposeBag)

代碼的簡(jiǎn)單說(shuō)明:

  • orEmpty:可以將 String? 類型的 ControlProperty(ControlProperty 說(shuō)明是被觀察者) 轉(zhuǎn)成 String,省得我們?cè)偃ソ獍#ㄒ?jiàn)下圖)
  • share(replay: 1) :我們用 isCodeValid 來(lái)控制驗(yàn)證碼提示語(yǔ)是否隱藏以及登錄按鈕是否可用。shareReplay 就是讓他們共享這一個(gè)源,而不是為他們單獨(dú)創(chuàng)建新的源。這樣可以減少不必要的開支。
  • DisposeBag:作用是 Rx 在視圖控制器或者其持有者將要銷毀的時(shí)候,自動(dòng)釋法掉綁定在它上面的資源。它是通過(guò)類似“訂閱處置機(jī)制”方式實(shí)現(xiàn)(類似于 NotificationCenter 的 removeObserver)。

從下圖的源碼中我們可以看到 UITextField 的 rx.text 屬性類型便是 ControlProperty<String?>

textField.rx.text

6. TableView

傳統(tǒng)方式實(shí)現(xiàn)

import Foundation
 
// 聯(lián)系人列表數(shù)據(jù)源
struct ContactsViewModel {
    let data = [
        Contacts("HarrySun1", phoneNumber: "1"),
        Contacts("HarrySun2", phoneNumber: "12"),
        Contacts("HarrySun3", phoneNumber: "123"),
        Contacts("HarrySun4", phoneNumber: "1234"),
        Contacts("HarrySun5", phoneNumber: "12345")
    ]
}



/*
接著我們?cè)O(shè)置 UITableView 的委托,并讓視圖控制器實(shí)現(xiàn) UITableViewDataSource 和 UITableViewDelegate 協(xié)議及相關(guān)的協(xié)議方法。
這個(gè)大家肯定都寫過(guò)無(wú)數(shù)遍了,也沒(méi)什么好講的
*/
import UIKit
import RxSwift
 
class ViewController: UIViewController {
 
    //tableView對(duì)象
    @IBOutlet weak var tableView: UITableView!
     
    // 聯(lián)系人列表數(shù)據(jù)源
    let contactsViewModel = ContactsViewModel()
     
    override func viewDidLoad() {
        super.viewDidLoad()
         
        //設(shè)置代理
        tableView.dataSource = self
        tableView.delegate = self
    }
}
 
extension ViewController: UITableViewDataSource {
    //返回單元格數(shù)量
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return contactsViewModel.data.count
    }
     
    //返回對(duì)應(yīng)的單元格
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
        -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
        let contacts = contactsViewModel.data[indexPath.row]
        cell.textLabel?.text = "\(contacts.name) : \(contacts.phoneNumber)"
        return cell
    }
}
 
extension ViewController: UITableViewDelegate {
    //單元格點(diǎn)擊
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let contacts = contactsViewModel.data[indexPath.row]
        print("你選中的聯(lián)系人信息【\(contacts)】")
    }
}

用 RxSwift 實(shí)現(xiàn)

// 對(duì) viewModel 修改
/* 
這里我們將 data 屬性變成一個(gè)可觀察序列對(duì)象(Observable Squence),而對(duì)象當(dāng)中的內(nèi)容和我們之前在數(shù)組當(dāng)中所包含的內(nèi)容是完全一樣的。
簡(jiǎn)單說(shuō)就是“序列”可以對(duì)這些數(shù)值進(jìn)行“訂閱(Subscribe)”,有點(diǎn)類似于“通知(NotificationCenter)”
*/ 

import RxSwift
 
// 聯(lián)系人列表數(shù)據(jù)源
struct ContactsViewModel {
    // 創(chuàng)建發(fā)送指定值的 Observerble
    let data = Observable.just([
        Contacts("HarrySun1", phoneNumber: "1"),
        Contacts("HarrySun2", phoneNumber: "12"),
        Contacts("HarrySun3", phoneNumber: "123"),
        Contacts("HarrySun4", phoneNumber: "1234"),
        Contacts("HarrySun5", phoneNumber: "12345")
        ])
}



// 對(duì)視圖控制器代碼修改
/*
這里我們不再需要實(shí)現(xiàn)數(shù)據(jù)源和委托協(xié)議了。而是寫一些響應(yīng)式代碼,讓它們將數(shù)據(jù)和 UITableView 建立綁定關(guān)系。
*/
import UIKit
import RxSwift
import RxCocoa
 
class ViewController: UIViewController {
 
    let tableView = UITableView()
    // 聯(lián)系人列表數(shù)據(jù)源
    let contactsViewModel = ContactsViewModel()
    
    let disposeBag = DisposeBag()
     
    override func viewDidLoad() {
        super.viewDidLoad()
         
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        
        //將數(shù)據(jù)源數(shù)據(jù)綁定到tableView上
        contactsViewModel.data
            .bind(to: tableView.rx.items(cellIdentifier:"Cell")) { _, contacts, cell in
                cell.textLabel?.text = "\(contacts.name) : \(contacts.phoneNumber)"
            }.disposed(by: disposeBag)
        
        //tableView點(diǎn)擊響應(yīng)
        tableView.rx.modelSelected(Contacts.self).subscribe(onNext: { contacts in
            print("你選中的聯(lián)系人信息【\(contacts)】")
        }).disposed(by: disposeBag)
    }
}

代碼的簡(jiǎn)單說(shuō)明:

  • rx.items(cellIdentifier:):這是 Rx 基于 cellForRowAt 數(shù)據(jù)源方法的一個(gè)封裝。傳統(tǒng)方式中我們還要有個(gè) numberOfRowsInSection 方法,使用 Rx 后就不再需要了(Rx 已經(jīng)幫我們完成了相關(guān)工作)。
  • rx.modelSelected: 這是 Rx 基于 UITableView 委托回調(diào)方法 didSelectRowAt 的一個(gè)封裝。

更多例子參見(jiàn) RxSwift 中文文檔:為什么要使用 RxSwift ?

上面看了幾個(gè) RxSwift 的例子,也感受到了函數(shù)響應(yīng)式編程 清晰簡(jiǎn)潔、易讀、易維護(hù)的代碼,下面我們看下 RxSwift 的最佳搭檔:MVVM

MVVM

原先常用的架構(gòu):MVC
蘋果默認(rèn)推薦的設(shè)計(jì)模式 MVC
  • Model:數(shù)據(jù)層。負(fù)責(zé)讀寫數(shù)據(jù),保存 App 狀態(tài)等
  • Controller:業(yè)務(wù)邏輯層。負(fù)責(zé)業(yè)務(wù)邏輯、事件響應(yīng)、數(shù)據(jù)加工等工作

缺點(diǎn):

  • ViewController 既扮演了 View 的角色,又扮演了 ViewController 的角色
  • 而 Model 在 VIewController 中又可以直接與 View 進(jìn)行交互
  • 當(dāng) App 交互復(fù)雜的時(shí)候,就會(huì)發(fā)現(xiàn) ViewController 將變得十分臃腫,大量代碼被添加到控制器中,使得控制器負(fù)擔(dān)過(guò)重。
MVVM
微軟 MVVM

優(yōu)點(diǎn):

  • 可以對(duì) ViewController 進(jìn)行瘦身
  • 實(shí)現(xiàn)邏輯視圖的復(fù)用。比如一個(gè) ViewModel 可以綁定到不同的 View 上,讓多個(gè) View 重用相同的視圖邏輯。
  • 而且使用 MVVM 可以大大降低代碼的耦合性,方便進(jìn)行單元測(cè)試以及維護(hù),也方便多人協(xié)作開發(fā)(比如一個(gè)人負(fù)責(zé)邏輯實(shí)現(xiàn),一個(gè)人負(fù)責(zé) UI 實(shí)現(xiàn))。

缺點(diǎn):

  • 相較于 MVC,使用 MVVM 會(huì)輕微的增加代碼量,但是總體上減少了代碼的復(fù)雜性。
  • 還有就是有一定的學(xué)習(xí)成本(如何數(shù)據(jù)綁定等)。

RxSwift 和 MVVM 結(jié)合使用

例1. 上面 tableView 的實(shí)現(xiàn)
例2. 兩個(gè)網(wǎng)絡(luò)請(qǐng)求返回?cái)?shù)據(jù)后跳轉(zhuǎn)


// endAction 和 payAction 是兩個(gè)網(wǎng)絡(luò)請(qǐng)求,把兩個(gè)接口請(qǐng)求是否完成壓縮為一個(gè)信號(hào)再做操作
Observable.merge(viewModel.endAction.elements, viewModel.payAction.elements)
    .observeOn(MainScheduler.instance) // 保證在主線程
    .subscribe(onNext: { [weak self] model in
        // doSomething
    })
    .disposed(by: disposeBag)

代碼簡(jiǎn)單說(shuō)明

merge

將多個(gè) Observables 合并成一個(gè)

merge
observeOn

指定 Observable 在那個(gè) Scheduler 發(fā)出通知

Action

Action 被定義為一個(gè)類 Action<Input, Element>,Input 是輸入的元素,Element 是 Action 處理完之后返回的元素。

一般綁定到 button 上:button.rx.action = action

action.execute():執(zhí)行 Action

action.elements:請(qǐng)求成功,可以拿到 Action 返回的 Element

action.executing:請(qǐng)求是否在執(zhí)行,可以用來(lái)展示或者隱藏 HUD

詳細(xì)的可以看附件代碼 - 4:使用 Action 寫一個(gè)網(wǎng)絡(luò)請(qǐng)求

Demo

demo 可以在 GitHub 倉(cāng)庫(kù) 找到

附件代碼

1. ViewController.swift

//
//  ViewController.swift
//  RxSwiftTest
//
//  Created by 孫浩 on 2019/7/26.
//  Copyright ? 2019 HarrySun. All rights reserved.
//

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {
    
    let errorLabel = UILabel()
    let button = UIButton()
    let textField = UITextField()
    let number: BehaviorRelay = BehaviorRelay<Int>(value: 0)
    
    let tableView = UITableView()
    
    // 聯(lián)系人列表數(shù)據(jù)源
    let contactsViewModel = ContactsViewModel()
    
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
//        rxButton()
//        rxNotification()
        rxDelegate()
//        rcTableView()
//        rxBinding()
    }
    
    // 測(cè)試 button
    func rxButton() {
        view.addSubview(button)
        button.frame = CGRect(x: 100, y: 100, width: 100, height: 50)
        button.backgroundColor = .red
        
        button.rx.tap
            .subscribe(onNext: {
                print("button event")
                NotificationCenter.default.post(name: NSNotification.Name("NotificationName"), object: nil)
            })
            .disposed(by: disposeBag)
    }
    
    // 測(cè)試 Notification
    func rxNotification() {
        NotificationCenter.default.rx.notification(NSNotification.Name("NotificationName"))
            .subscribe(onNext: { (notification) in
                print("notification")
            })
            .disposed(by: disposeBag)
    }
    
    // 測(cè)試 delegate
    func rxDelegate() {
        view.addSubview(textField)
        textField.layer.borderWidth = 1
        textField.frame = CGRect(x: 10, y: 100, width: 300, height: 50)
        
        textField.rx.controlEvent([.editingChanged])
            .asObservable()
            .subscribe(onNext: { _ in
                print("字符輸入:\(self.textField.text ?? "")")
            })
            .disposed(by: disposeBag)
        // 或者
        textField.rx.text
            .subscribe(onNext: { text in
                print("text:\(text ?? "")")
            })
            .disposed(by: disposeBag)
        
        textField.rx.controlEvent([.editingDidEnd])
            .asObservable()
            .subscribe(onNext: { _ in
                // do something
            })
            .disposed(by: disposeBag)
    }
    
    // 測(cè)試 BehaviorRelay
    func rcBehaviorRelay() {
        number.skip(1).subscribe(onNext: { (num) in
            print("數(shù)字變化:\(num)")
        }).disposed(by: disposeBag)
        
        for i in 1...5 {
            number.accept(i)
        }
    }
    
    // 測(cè)試數(shù)據(jù)綁定
    func rxBinding() {
        let isCodeValid = textField.rx.text
            .orEmpty
            .map { $0.count == 6 }
            .share(replay: 1)
        // share(replay: 1) 是用來(lái)做什么的?
        // 我們用 isCodeValid 來(lái)控制驗(yàn)證碼提示語(yǔ)是否隱藏以及登錄按鈕是否可用。shareReplay 就是讓他們共享這一個(gè)源,而不是為他們單獨(dú)創(chuàng)建新的源。這樣可以減少不必要的開支。
        // 驗(yàn)證碼輸入是否有效 -> 提交按鈕是否可點(diǎn)擊
        isCodeValid
            .bind(to: errorLabel.rx.isHidden)
            .disposed(by: disposeBag)
        isCodeValid
            .bind(to: button.rx.isEnabled)
            .disposed(by: disposeBag)
    }
    
    // 測(cè)試 tableView
    func rcTableView() {
        view.addSubview(tableView)
        tableView.frame = view.frame
        
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        
        //將數(shù)據(jù)源數(shù)據(jù)綁定到tableView上
        contactsViewModel.data
            .bind(to: tableView.rx.items(cellIdentifier:"Cell")) { _, contacts, cell in
                cell.textLabel?.text = "\(contacts.name) : \(contacts.phoneNumber)"
            }.disposed(by: disposeBag)
        
        //tableView點(diǎn)擊響應(yīng)
        tableView.rx.modelSelected(Contacts.self).subscribe(onNext: { contacts in
            print("你選中的聯(lián)系人信息【\(contacts)】")
        }).disposed(by: disposeBag)
    }
    
}

2. ContactsViewModel.swift

//
//  ContactsViewModel.swift
//  RxSwiftTest
//
//  Created by 孫浩 on 2019/7/26.
//  Copyright ? 2019 HarrySun. All rights reserved.
//

import RxSwift

struct ContactsViewModel {
    // 創(chuàng)建發(fā)送指定值的 Observerble
    let data = Observable.just([
        Contacts("HarrySun1", phoneNumber: "1"),
        Contacts("HarrySun2", phoneNumber: "12"),
        Contacts("HarrySun3", phoneNumber: "123"),
        Contacts("HarrySun4", phoneNumber: "1234"),
        Contacts("HarrySun5", phoneNumber: "12345")
        ])
}

3. Contacts.swift

//
//  Contacts.swift
//  RxSwiftTest
//
//  Created by 孫浩 on 2019/7/26.
//  Copyright ? 2019 HarrySun. All rights reserved.
//

import UIKit

class Contacts: NSObject {
    var name = ""
    var phoneNumber = ""
    
    init(_ name: String, phoneNumber: String) {
        self.name = name
        self.phoneNumber = phoneNumber
    }
}

4. 使用 Action 寫一個(gè)網(wǎng)絡(luò)請(qǐng)求

1.在 viewModel 中寫一個(gè)請(qǐng)求數(shù)據(jù)的 Action

lazy var shareAction = Action<Void, (URL?, String, String)> {        //  Action<input,output> {  }
    guard let orderno = self.endModel?.orderno, let location = LocationService.shared.lastLocation else {
        return Observable.empty()
    }
        
    // 網(wǎng)絡(luò)請(qǐng)求
    return ShareRequest(orderid: orderno, location: location)
        .rx
        .start(usingCache:  true)
        .mapModel(ShareModel.self)
        .catchErrorJustReturn(self.defaultShareModel)        //  請(qǐng)求接口失敗后取默認(rèn)的model
        .map {          //  map:遍歷并做相應(yīng)處理
            (URL(string: $0.shareUrl), $0.comment, "  " + $0.hashtag + "  ")
    }
}
  1. 在 viewController 中調(diào)用一次 action.execute()
viewModel.shareAction.execute(())
  1. action中的請(qǐng)求成功,返回?cái)?shù)據(jù)之后的操作
viewModel.shareAction.elements
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { [weak self] tuple in
        guard let `self` = self else { return }
                
        // 處理請(qǐng)求完成后的操作
    })
    .disposed(by: disposeBag)
  1. 請(qǐng)求中的操作
    請(qǐng)求開始時(shí)顯示 HUD,請(qǐng)求結(jié)束后隱藏 HUD
    distinctUntilChanged:值修改的時(shí)候才會(huì)走此方法
viewModel.shareAction.executing
    .distinctUntilChanged()          // 數(shù)據(jù)發(fā)生改變才會(huì)走
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { [weak self] executing in
        if executing {
            ProgressHUD.showLoadingHUD(in: self?.view)
        } else {
            ProgressHUD.hideLoadingHUD(in: self?.view)
        }
    })
    .disposed(by: disposeBag)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評(píng)論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評(píng)論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評(píng)論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評(píng)論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評(píng)論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評(píng)論 0 291
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評(píng)論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評(píng)論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評(píng)論 1 296
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,492評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評(píng)論 2 380