ReactiveCocoa 4 圖解之五——信號(Signal)

一個信號狞谱,由Signal類型表現(xiàn)乃摹,是可以被持續(xù)監(jiān)視的一系列事件(events)。

信號一般用來表示“正在進(jìn)行中”的事件流跟衅,比如通知孵睬,用戶輸入等。隨著操作完成或者收到數(shù)據(jù)伶跷,事件在信號上發(fā)送掰读,信號將他們推向所有的監(jiān)聽器。所有的監(jiān)聽器會在同時看到事件撩穿。

用戶必須監(jiān)聽(observe)一個信號來訪問它的事件磷支。監(jiān)聽信號不會產(chǎn)生任何副作用(side effects)谒撼。換句話說食寡,信號完全是生成器驅(qū)動和基于推送的,而且在其生命周期內(nèi)消費(fèi)者(監(jiān)聽器)不能對它產(chǎn)生任何影響廓潜。監(jiān)聽一個信號的時候抵皱,用戶只能按照事件在信號上發(fā)送的順序處理事件。沒有方法可以隨機(jī)訪問信號的值辩蛋。

可以通過在信號上施加原函數(shù)(primitives)來操作信號呻畸。典型的操作信號的原函數(shù)有filtermap悼院,reduce伤为,以及一些同時操作多個信號的原函數(shù)(比如zip)。原函數(shù)僅在信號的Next事件上施加操作。

信號的生命周期由任意多個Next事件绞愚,和一個緊隨其后的終結(jié)事件組成叙甸,終結(jié)事件可能是FailedCompleted位衩,或者Interrupted(但不會是他們的組合)裆蒸。終結(jié)事件不屬于信號的值,他們必須被特殊處理糖驴。

—— ReactiveCocoa 框架概覽

1. 信號什么樣



看看信號的定義僚祷,可以發(fā)現(xiàn)信號異常簡單,它僅僅持有了一個對監(jiān)聽器集合的引用而已:

public final class Signal<Value, Error: ErrorType> {

    public typealias Observer = ReactiveCocoa.Observer<Value, Error>

    private let atomicObservers: Atomic<Bag<Observer>?> = Atomic(Bag())

    ......
}

Bag是一個在ReactiveCocoa定義的數(shù)據(jù)結(jié)構(gòu)贮缕,認(rèn)為它是一個數(shù)組就可以了辙谜。Atomic也是ReactiveCocoa自定的數(shù)據(jù)結(jié)構(gòu),給Atomic一個值以后跷睦,如果對這個值進(jìn)行操作筷弦,Atomic會保證線程安全。這些都是信號的內(nèi)部實現(xiàn)細(xì)節(jié)抑诸,對信號的使用者來講是透明的烂琴。

關(guān)鍵在于信號的私有成員atomicObservers,它是正在監(jiān)聽這個信號的所有監(jiān)聽器的集合蜕乡。通過typealias奸绷,信號的泛型定義決定了這些監(jiān)聽器能夠接受的事件的類型。也就是說层玲,如果一個信號發(fā)出帶有String類型數(shù)據(jù)的Next事件号醉,或者帶有SomeError類型的Failed事件,那么只有能夠處理這兩種事件的監(jiān)聽器才能監(jiān)聽它辛块。

于是信號看起來就是這樣子的:

單純的Signal是這樣子的

那么信號是怎么發(fā)送事件的畔派?ReactiveCocoa的信號僅僅是一個媒介,它不管事件如何發(fā)生润绵,只管把這些事件發(fā)送給監(jiān)聽自己的監(jiān)聽器們线椰。打個比方,信號就是公路尘盼,而事件是公路上跑的車憨愉。事件怎么產(chǎn)生,需要用戶(也就是我們)在初始化信號時來告訴信號卿捎,下面就來看看信號是如何初始化的配紫。

2. 創(chuàng)建信號



創(chuàng)建一個信號,可以通過信號的初始化方法init(_ generator: Observer -> Disposable?)午阵。這個方法的外部參數(shù)名被取消了躺孝,不過從內(nèi)部參數(shù)名可以看出它的作用——事件源(generator)。剛才提到過,信號?除了誰在監(jiān)聽自己以外一無所知植袍,其實事件源也不知道信號的存在伪很,那么就必須有一個中間人將事件從事件源移動到信號上。這個中間人奋单,就是事件源接受的參數(shù)——一個Observer锉试。他們之間的聯(lián)系如何建立,就要看看信號初始化方法的執(zhí)行過程:

Signal實例化后出現(xiàn)的對象览濒,圖中的箭頭表示它們的引用關(guān)系
  1. 先創(chuàng)建一個空的串行存根(SerialDisposable
  2. 再創(chuàng)建一個監(jiān)聽器(Observer)呆盖,這個監(jiān)聽器持有上一步中創(chuàng)建的存根的引用,和對信號(Signal)本身的引用
  3. 將上一步創(chuàng)建的監(jiān)聽器交給事件源贷笛,讓事件源開始工作
  4. 將上一步返回的存根?(Disposable)交給第一步的串行存根

上面第二步中的監(jiān)聽器可能是ReactiveCocoa中最重要的對象了(其實应又,?把它叫做監(jiān)聽器讓人困惑,我覺得叫做事件分發(fā)器更合適乏苦,只不過使用Observer類來實現(xiàn)了而已)株扛,這個對象把ReactiveCocoa和ReactiveCocoa之外的世界(我們想做的App)聯(lián)系了起來。所以必須說明一下它的作用(就是上面圖中黃色圓角的action在做什么):

  1. 如果它從事件源收到任何事件汇荐,就在信號的監(jiān)聽器集合中循環(huán)迭代洞就,將此事件原封不動地?分發(fā)給每一個監(jiān)聽器。
  2. 如果它從事件源收到的事件是一個終結(jié)事件掀淘,除了分發(fā)這個事件外旬蟋,它還會廢棄自己持有的存根對象。
發(fā)送事件

事件源一開始發(fā)生事件革娄,分發(fā)器就把這個事件分發(fā)給信號的所有監(jiān)聽器倾贰。一旦事件源發(fā)出了終結(jié)事件,分發(fā)器就廢棄自己持有的串行存根拦惋,這會進(jìn)而廢棄事件源返回的存根匆浙,釋放事件源占用的系統(tǒng)資源,事件源不再工作厕妖,信號就終結(jié)了首尼。

值得注意的是:

  1. 其實在我們使用者看來,事件源叹放,分發(fā)器饰恕,信號三者并沒有區(qū)分看待的必要挠羔,將他們整體看做信號就可以了井仰。
  2. 信號一經(jīng)初始化,事件源就立即開始工作破加,發(fā)生事件(也就是所謂的“熱”信號)俱恶。

該看看我們的職責(zé)了——提供事件源。事件源是一個回調(diào)函數(shù),接受一個Observer參數(shù)(就是那個很重要的分發(fā)器)合是,可以選擇性地返回一個Disposable了罪。我們可以做任何想做的事,只要把想告知信號另一端的監(jiān)聽器的值用sendNext(value:)方法交給分發(fā)器就可以了聪全;如果想要告訴對方我們做的事情失敗了泊藕,就用調(diào)用分發(fā)器的sendFailed(error:)方法;如果我們的操作正常結(jié)束就調(diào)用sendCompleted()难礼;如果我們被打斷了娃圆,就調(diào)用sendInterrupted()

另外蛾茉,如果我們的事件源要做一些很重的操作讼呢,需要占用系統(tǒng)資源要到操作完成才能釋放的話,我們可以把釋放資源的工作包裝到一個Disposable對象中谦炬,把它作為返回值傳回去悦屏。分發(fā)器會在收到我們的終結(jié)事件時幫我們調(diào)用這些清理和釋放的工作。當(dāng)然键思,要是沒有這個必要的話返回nil就可以了础爬。

code somple

信號我們有了,那么如何監(jiān)聽信號呢吼鳞?

3. 監(jiān)聽信號



相信你已經(jīng)有了答案幕帆,要監(jiān)聽一個信號,只要將一個類型正確的監(jiān)聽器加入到信號的監(jiān)聽器集合里就行了赖条。為此失乾,ReavtiveCocoa框架在Signal類中定義了observe(observer: Observer) -> Disposable?實例方法,把我們的監(jiān)聽器作為參數(shù)傳入就可以了纬乍。

signal.observe(Signal.Observer { event in
    switch event {
    case let .Next(next):
        print("Next: \(next)")
    case let .Failed(error):
        print("Failed: \(error)")
    case .Completed:
        print("Completed")
    case .Interrupted:
        print("Interrupted")
    }
})

值得一提的是這個方法的返回值碱茁,一個存根會交到我們手中,我們可以廢棄這個存根仿贬,這樣做僅僅會使我們的監(jiān)聽器被從信號的監(jiān)聽器集合中移除纽竣,從而停止接收信號發(fā)出的事件,但是對信號本身而言沒有任何影響茧泪。

監(jiān)聽信號

在swift 2中蜓氨,協(xié)議的定義中可以提供方法的默認(rèn)實現(xiàn)。所有聲明要實現(xiàn)?該協(xié)議的對象队伟,如果沒有提供自己的對于這些方法的實現(xiàn)穴吹,都可以使用這些默認(rèn)實現(xiàn)。ReactiveCocoa里定義了一個SignalType協(xié)議嗜侮,規(guī)定了一個對象能夠被稱為信號所需要滿足的接口港令。同時啥容,它還定義了一些便利的幫助方法:

extension SignalType {

    public func observe(action: Signal<Value, Error>.Observer.Action) -> Disposable? {
        return observe(Observer(action))
    }

    public func observeNext(next: Value -> ()) -> Disposable? {
        return observe(Observer(next: next))
    }

    public func observeCompleted(completed: () -> ()) -> Disposable? {
        return observe(Observer(completed: completed))
    }
    
    public func observeFailed(error: Error -> ()) -> Disposable? {
        return observe(Observer(failed: error))
    }
    
    public func observeInterrupted(interrupted: () -> ()) -> Disposable? {
        return observe(Observer(interrupted: interrupted))
    }
  
    ......
}

Signal類實現(xiàn)了SignalType協(xié)議,繼承了這些默認(rèn)方法顷霹,所以就不必顯示調(diào)用監(jiān)聽器的初始化函數(shù)了咪惠,只要針對我們感興趣的事件提供處理方法,作為參數(shù)傳入就可以了:

signal.observeNext { next in 
  print("Next: \(next)") 
}

signal.observeFailed { error in
  print("Failed: \(error)")
}

signal.observeCompleted { 
  print("Completed") 
}

signal.observeInterrupted { 
  print("Interrupted")
}

4. 管道(Pipes)


一個管道淋淀,由Signal.pipe()方法創(chuàng)建遥昧,是一個可以手動控制的信號(signal)。

這個方法返回一個信號(signal)和一個監(jiān)聽器(observer)朵纷∏耄可以通過向監(jiān)聽器發(fā)送事件來控制信號。這在將非RAC的代碼橋接到信號的世界時非常有用柴罐。

比如徽缚,不在回調(diào)中處理應(yīng)用程序邏輯,?而是在這個回調(diào)中簡單的向監(jiān)聽器發(fā)送事件革屠。同時凿试,信號可以被返回,隱藏了回調(diào)的實現(xiàn)細(xì)節(jié)似芝。

—— ReactiveCocoa 框架概覽

pipe是定義在Signal類上的一個類方法那婉,是另一種創(chuàng)建信號的方法。和信號的初始化方法不同党瓮,它不需要我們提供事件源详炬,而是在返回值的元組中把事件分發(fā)器的引用交給我們,如何發(fā)送事件和何時發(fā)送時間完全由我們的后續(xù)處理而定:

/// Creates a Signal that will be controlled by sending events to the given
/// observer.
///
/// The Signal will remain alive until a terminating event is sent to the
/// observer.
public static func pipe() -> (Signal, Observer) {
  var observer: Observer!
  let signal = self.init { innerObserver in
    observer = innerObserver
    return nil
  }

  return (signal, observer)
}

pipe方法調(diào)用了信號的初始化方法寞奸,作為參數(shù)的事件源中沒有任何產(chǎn)生事件的處理呛谜,而是將事件分發(fā)器(上面代碼中的innerObserver)直接賦值到閉包外面的變量中,最后用元組的形式將創(chuàng)建好的信號和事件分發(fā)器返回枪萄。我們可以操作并監(jiān)聽返回的信號隐岛,或者在分發(fā)器上手動發(fā)送事件:

let (signal, observer) = Signal<String, NoError>.pipe()

signal
    .map { string in string.uppercaseString }
    .observeNext { next in print(next) }

observer.sendNext("a")     // Prints A
observer.sendNext("b")     // Prints B
observer.sendNext("c")     // Prints C

事件可以產(chǎn)生了,信號把它們傳遞到了我們的監(jiān)聽器里瓷翻,我們的監(jiān)聽器把事件中關(guān)聯(lián)的值拿來做了我們要做的事【郯迹現(xiàn)在輪到ReactiveCocoa中最強(qiáng)大的部分登場了。

5. 信號的變形



假定有一只正在發(fā)出白光的手電筒齐帚,我們從它那里得到了白色的光妒牙。如果把它放到一塊藍(lán)色的玻璃后面,我們得到的光就變成了藍(lán)色——信號發(fā)生了變形对妄。

?用ReactiveCocoa的概念做個類比湘今,信號就是手電筒,事件就是發(fā)出的光饥伊,監(jiān)聽器就是我們的眼睛象浑。如果需要在事件發(fā)送到我們的監(jiān)聽器之前發(fā)生對它們做一些改變,就必須要有一個辦法把我們的藍(lán)色玻璃插入到信號和監(jiān)聽器之間琅豆,而且還應(yīng)該可以插入任意多個任意顏色的玻璃愉豺。上面提到的SignalType協(xié)議就提供這些辦法。

SignalType協(xié)議里有三個信號變形方法的默認(rèn)實現(xiàn)茫因,這三個方法(尤其是map)是其他信號變形的基礎(chǔ):

  1. map<U>(transform: Value -> U) -> Signal<U, Error>
  2. mapError<F>(transform: Error -> F) -> Signal<Value, F>
  3. filter(predicate: Value -> Bool) -> Signal<Value, Error>

正如這三個方法一樣蚪拦,所有關(guān)于信號變形操作的返回值依然是一個信號,也就是說可以進(jìn)一步對這個新信號再次施加變形操作冻押,從而形成一個變形操作的鏈條驰贷。除了mapmapError洛巢,filter以外括袒,ReactiveCocoa提供了許多其他的變形操作(后述),將這些操作排列組合稿茉,可以讓信號發(fā)生無窮無盡的變化锹锰。如果ReactiveCocoa提供的變形操作不夠用,我們可以擴(kuò)展SignalType協(xié)議(使用extension)加入自定義的變形方法漓库。

code sample

下面我們來分別看看它們在做什么:

1. 映射(map和mapError)



顧名思義恃慧,映射就是事件一對一的變形,我們來決定變形的具體過程渺蒿,將這個過程作為參數(shù)傳遞給map方法即可痢士。

extension SignalType {

    ......

    /// Maps each value in the signal to a new value.
    @warn_unused_result(message="Did you forget to call `observe` on the signal?")
    public func map<U>(transform: Value -> U) -> Signal<U, Error> {
        return Signal { observer in
            return self.observe { event in
                observer.action(event.map(transform))
            }
        }
    }

    /// Maps errors in the signal to a new error.
    @warn_unused_result(message="Did you forget to call `observe` on the signal?")
    public func mapError<F>(transform: Error -> F) -> Signal<Value, F> {
        return Signal { observer in
            return self.observe { event in
                observer.action(event.mapError(transform))
            }
        }
    }

    ......
}

map方法寥寥數(shù)語,但是所做事情比較復(fù)雜茂装,有必要慢慢分解一下的話:

  1. 首先創(chuàng)建一個新的信號怠蹂,這個過程和前面提到的信號初始化相同,一個事件分發(fā)器被傳遞到事件源中少态。
  2. 新信號的事件源使用得到的分發(fā)器創(chuàng)建一個監(jiān)聽器褥蚯,這個監(jiān)聽對我們作為參數(shù)傳入的變形方法有一個引用,它對每一個收到的事件實施這個變形方法况增,然后交給新信號的分發(fā)器赞庶。
  3. 新信號的?事件源不發(fā)生任何事件,僅僅把?第二步創(chuàng)建的監(jiān)聽器用observe方法加入到當(dāng)前信號的監(jiān)聽器集合中澳骤。
  4. 因為用了observe方法歧强,一個ActionDisposable類型的存根會返回,交給新信號的串行存根为肮。
  5. 將新的信號返回摊册。
map變形操作后出現(xiàn)的對象,圖中藍(lán)色的箭頭表示它們的引用關(guān)系

簡而言之颊艳,映射操作就是使用當(dāng)前的信號作為事件源制造了一個新的信號茅特。沿用我們的類比忘分,就是把手電筒和藍(lán)色的玻璃綁在一起,當(dāng)成一個新的手電筒白修。上面的過程中第二步中創(chuàng)建的監(jiān)聽器十分關(guān)鍵妒峦,它起到了連接新舊兩個信號的作用,我們定義的變形方法(也就是我們制造的一個有顏色的玻璃)?被包裝在這個監(jiān)聽器中兵睛。第三步肯骇,這個監(jiān)聽器加入到了當(dāng)前信號的監(jiān)聽器集合中(跟手電筒綁在一起),一旦當(dāng)前的信號有事件發(fā)生祖很,這個監(jiān)聽器就會收到并立即調(diào)用變形方法笛丙,然后將新的事件交給新信號的分發(fā)器,于是新的信號的監(jiān)聽器們(我們的眼睛)就收到了變形后的事件(藍(lán)色的光)假颇。就像這樣:

map后的事件發(fā)送.gif

上面第四步返回的存根胚鸯,和之前提到的監(jiān)聽信號時得到的存根一樣唠摹,可以用來將負(fù)責(zé)事件變形的監(jiān)聽器從當(dāng)前的信號上移除测蹲,而信號本身不會受任何影響(相當(dāng)于把藍(lán)色的玻璃拿掉喜喂,而手電筒不會有什么變化勃刨。)饲帅。

2. 過濾(filter)



有了上面?zhèn)€關(guān)于映射的討論侵蒙,再來看過濾的話就不困難了也榄。過濾不會改變信號上事件流的值或類型惦费,而是把不滿足一定條件的事件攔截掉趟脂。攔截的方法泰讽,就是在連接新舊信號的監(jiān)聽器中規(guī)定,如果事件不滿足條件昔期,就不要把該事件傳遞給新信號的分發(fā)器已卸。

extension SignalType {

    ......

    /// Preserves only the values of the signal that pass the given predicate.
    @warn_unused_result(message="Did you forget to call `observe` on the signal?")
    public func filter(predicate: Value -> Bool) -> Signal<Value, Error> {
        return Signal { observer in
            return self.observe { (event: Event<Value, Error>) -> () in
                if case let .Next(value) = event {
                    if predicate(value) {
                        observer.sendNext(value)
                    }
                } else {
                    observer.action(event)
                }
            }
        }
    }
}
filter.gif

3. 聚合(reduce和collect)

6. 信號的組合

1. 組合(combine)

2. 打包(zip)

7. 信號的扁平化(Flatten)

1. 混合

2. 連接

3. 最新

8. 其他種類的變形

1. ignoreNil

2. take

take.gif

3. collect

collect.gif

4. observeOn

observerOn.gif

5. combineLatestWith

combineLatestWith.gif

6. delay

delay.gif

7. skip

skip.gif

8. materialize

materialize.gif

9. dematerialize

dematerialize.gif

10. sampleOn

sampleOn.gif

11. takeUntil

takeUtil.gif

12. skipUntil

skipUntil.gif

13. combinePrevious

combinePrevious.gif

14. reduce

reduce.gif

15. scan

scan(initial:, combine:)將信號包裝為一個新信號,每當(dāng)源信號發(fā)出事件時硼一,事件的值都會被累積累澡,然后再轉(zhuǎn)發(fā)給新信號。具體的累積方法般贼,由scan方法的第二個參數(shù)規(guī)定愧哟,累積的結(jié)果的類型可以和源信號的值得類型不同。scan的第一個參數(shù)是累積用的初始值哼蛆,它的類型必須和累積的結(jié)果類型一致蕊梧。

scan方法在原信號的監(jiān)聽器集合中加入一個監(jiān)聽器,當(dāng)信號發(fā)出第一個事件后腮介,事件的值會和initial的值累積后轉(zhuǎn)發(fā)給新信號肥矢,累積的結(jié)果會保存在新信號的一個變量中。之后源信號發(fā)出的每一個事件的值都會和前一次累積的結(jié)果再次累積叠洗,然后轉(zhuǎn)發(fā)給新信號甘改。

scan.gif

16. skipRepeats

skipRepeats.gif

17. skipWhile

skipWhile.gif

18. takeUntilReplacement

takeUntilReplacement.gif

19. takeLast

takeLast(count:)操作將信號包裝為一個新信號旅东,在源信號發(fā)出完成事件時,將源信號的最后count個事件發(fā)送出來十艾,之后緊隨一個完成事件抵代。在源信號發(fā)出完成事件之前,新信號不發(fā)出任何事件疟羹。

takeLast方法在源信號的監(jiān)聽器集合中加入一個帶有緩沖的監(jiān)聽器主守,這個緩沖是一個原信號值類型的數(shù)組禀倔,數(shù)組長度由參count數(shù)而定榄融。當(dāng)源信號發(fā)出Next事件時,這個監(jiān)聽器并不將事件轉(zhuǎn)發(fā)給新信號的事件分發(fā)器救湖,而是將事件存儲在緩沖的數(shù)組中愧杯。如果事件的數(shù)量超過了緩沖的容量,就將最早的事件從緩沖中移除以騰出空間鞋既。當(dāng)源信號發(fā)出Complete事件時力九,這個監(jiān)聽器就循環(huán)迭代緩沖數(shù)組,將其中所有的事件發(fā)送出去邑闺,之后再發(fā)出一個Complete事件跌前。

如果源信號發(fā)出了FailedInterrupted事件,緩沖機(jī)制不會執(zhí)行陡舅,而是直接轉(zhuǎn)發(fā)給新信號抵乓。

takeLast.gif

20. takeWhile

takeWhile.gif

22. zipWith

zipWith.gif

23. attempt

24. attemptMap

25. throttle

26. zip

27. timeoutWithError

28. promoteErrors

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市靶衍,隨后出現(xiàn)的幾起案子灾炭,更是在濱河造成了極大的恐慌,老刑警劉巖颅眶,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜈出,死亡現(xiàn)場離奇詭異,居然都是意外死亡涛酗,警方通過查閱死者的電腦和手機(jī)铡原,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來商叹,“玉大人眷蜈,你說我怎么就攤上這事∩蜃裕” “怎么了酌儒?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長枯途。 經(jīng)常有香客問我忌怎,道長籍滴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任榴啸,我火速辦了婚禮孽惰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鸥印。我一直安慰自己勋功,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布库说。 她就那樣靜靜地躺著狂鞋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪潜的。 梳的紋絲不亂的頭發(fā)上骚揍,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機(jī)與錄音啰挪,去河邊找鬼信不。 笑死,一個胖子當(dāng)著我的面吹牛亡呵,可吹牛的內(nèi)容都是我干的抽活。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锰什,長吁一口氣:“原來是場噩夢啊……” “哼下硕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起歇由,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤卵牍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后沦泌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體糊昙,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年谢谦,在試婚紗的時候發(fā)現(xiàn)自己被綠了释牺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡回挽,死狀恐怖没咙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情千劈,我是刑警寧澤祭刚,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響涡驮,放射性物質(zhì)發(fā)生泄漏暗甥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一捉捅、第九天 我趴在偏房一處隱蔽的房頂上張望撤防。 院中可真熱鬧,春花似錦棒口、人聲如沸寄月。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽漾肮。三九已至,卻和暖如春合敦,著一層夾襖步出監(jiān)牢的瞬間初橘,已是汗流浹背验游。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工充岛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人耕蝉。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓崔梗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親垒在。 傳聞我的和親對象是個殘疾皇子蒜魄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

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