Swift Combine 入門導(dǎo)讀

在具體介紹 Combine 之前百炬,有兩個(gè)重要的概念需要簡(jiǎn)要介紹一下:

  • 觀察者模式
  • 響應(yīng)式編程

觀察者模式

觀察者模式(Observer Pattern)是一種設(shè)計(jì)模式贺归,用來描述一對(duì)多關(guān)系:一個(gè)對(duì)象發(fā)生改變時(shí)將自動(dòng)通知其他對(duì)象屋群,其他對(duì)象將相應(yīng)做出反應(yīng)盹愚。這兩類對(duì)象分別被稱為被 觀察目標(biāo)(Observable)和 觀察者(Observer)饲握,也就是說一個(gè)觀察目標(biāo)可以對(duì)應(yīng)多個(gè)觀察者煌珊,觀察者可以訂閱它們感興趣的內(nèi)容号俐,當(dāng)觀察目標(biāo)內(nèi)容改變時(shí),它會(huì)向這些觀察者廣播通知(調(diào)用 Observer 的更新方法)定庵。有一點(diǎn)需要說明的是吏饿,觀察者之間彼此時(shí)互相獨(dú)立的,也并不知道對(duì)方的存在蔬浙。

在 Swift 中猪落,一個(gè)簡(jiǎn)單的觀察者模式可以被描述為:

protocol Observable {
    associatedtype T: Observer
    mutating func attach(observer: T)
}

protocol Observer {
    associatedtype State
    func notify(_ state: State)
}

struct AnyObservable<T: Observer>: Observable{

    var state: T.State {
        didSet {
            notifyStateChange()
        }
    }

    var observers: [T] = []

    init(_ state: T.State) {
        self.state = state
    }

    mutating func attach(observer: T) {
        observers.append(observer)
        observer.notify(state)
    }

    private func notifyStateChange() {
        for observer in observers {
            observer.notify(state)
        }
    }
}

struct AnyObserver<S>: Observer {

    private let name: String

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

    func notify(_ state: S) {
        print("\(name)'s state updated to \(state)")
    }
}

var observable = AnyObservable<AnyObserver<String>>("hello")
let observer = AnyObserver<String>(name: "My Observer")
observable.attach(observer: observer)
observable.state = "world"

// "My Observer's state updated: hello"
// "My Observer's state updated: world"

響應(yīng)式編程

響應(yīng)式編程(Reactive Programming)是一種編程思想,相對(duì)應(yīng)的也有面向過程編程畴博、面向?qū)ο缶幊瘫考伞⒑瘮?shù)式編程等等。不同的是俱病,響應(yīng)式編程的核心是面向異步數(shù)據(jù)流和變化的官疲。

在現(xiàn)在的前端世界中,我們需要處理大量的事件亮隙,既有用戶的交互途凫,也有不斷的網(wǎng)絡(luò)請(qǐng)求,還有來自系統(tǒng)或者框架的各種通知溢吻,因此也無(wú)可避免產(chǎn)生紛繁復(fù)雜的狀態(tài)维费。使用響應(yīng)式編程后,所有事件將成為異步的數(shù)據(jù)流促王,更加方便的是可以對(duì)這些數(shù)據(jù)流可以進(jìn)行組合變換掩完,最終我們只需要監(jiān)聽所關(guān)心的數(shù)據(jù)流的變化并做出響應(yīng)即可。

舉個(gè)有趣的例子來解釋一下:

當(dāng)你早上起床之后硼砰,你需要一邊洗漱一邊烤個(gè)面包且蓬,最后吃早飯。

傳統(tǒng)的編程方法:

func goodMorning() {
    wake {
        let group = DispatchGroup()
        group.enter()
        wash {
            group.leave()
        }
        group.enter()
        cook {
            group.leave()
        }
        group.notify(queue: .main) {
            eat {
                print("Finished")
            }
        }
    }
}

響應(yīng)式編程:

func reactiveGoodMorning() {
    let routine = wake.rx.flapLatest {
        return Observable.zip(wash, cook)
    }.flapLatest {
        return eat.rx
    }
    routine.subsribe(onCompleted: {
        print("Finished")
    })
}

不同于傳統(tǒng)可以看到 wake/wash/cook/eat 這幾個(gè)事件通過一些組合轉(zhuǎn)換被串聯(lián)成一個(gè)流题翰,我們也只需要訂閱這個(gè)流就可以在應(yīng)該響應(yīng)的時(shí)候得到通知恶阴。

Marble Diagram

為了更方便理解數(shù)據(jù)流诈胜,我們通常用一種叫 Marble Diagram 的圖象形式來表示數(shù)據(jù)流基于時(shí)間的變化。



我們用從左至右的箭頭表示時(shí)間線冯事,不同的形狀代表 Publisher 發(fā)出的值(Input)焦匈,豎線代表正常終止,叉代表發(fā)生錯(cuò)誤而終止昵仅。上圖中缓熟,上面一條時(shí)間線是一個(gè)數(shù)據(jù)流,中間的方塊代表組合變換摔笤,下方的另外一條時(shí)間線代表經(jīng)過變換后的數(shù)據(jù)流够滑。

Combine

現(xiàn)在可以進(jìn)入正題了,什么是 Combine吕世?

官方文檔的介紹是:

The Combine framework provides a declarative Swift API for processing values over time. These values can represent user interface events, network responses, scheduled events, and many other kinds of asynchronous data.
By adopting Combine, you’ll make your code easier to read and maintain, by centralizing your event-processing code and eliminating troublesome techniques like nested closures and convention-based callbacks.

簡(jiǎn)而言之彰触,Combine 可以使代碼更加簡(jiǎn)潔、易于維護(hù)命辖,也免除了飽受詬病的嵌套閉包和回調(diào)地獄况毅。Combine 是 Reactive Programming 在 Swift 中的一個(gè)實(shí)現(xiàn),更確切的說是對(duì) ReactiveX (Reactive Extensions, 簡(jiǎn)稱 Rx) 的實(shí)現(xiàn)尔艇,而這個(gè)實(shí)現(xiàn)正是基于觀察者模式的尔许。

Combine 是基于泛型實(shí)現(xiàn)的,是類型安全的终娃。它可以無(wú)縫地接入已有的工程母债,用來處理現(xiàn)有的 Target/ActionNotification尝抖、KVOcallback/closure 以及各種異步網(wǎng)絡(luò)請(qǐng)求迅皇。

在 Combine 中昧辽,有幾個(gè)重要的組成部分:

  • 發(fā)布者:Publiser
  • 訂閱者:Subscriber
  • 操作符:Operator
Publisher

在 Combine 中,Publisher 是觀察者模式中的 Observable登颓,并且可以通過組合變換(利用 Operator)重新生成新的 Publisher搅荞。

public protocol Publisher {
    /// The kind of values published by this publisher.
    associatedtype Output

    /// The kind of errors this publisher might publish.
    ///
    /// Use `Never` if this `Publisher` does not publish errors.
    associatedtype Failure : Error

    /// This function is called to attach the specified `Subscriber` to 
    /// this `Publisher` by `subscribe(_:)`
    ///
    /// - SeeAlso: `subscribe(_:)`
    /// - Parameters:
    ///     - subscriber: The subscriber to attach to this `Publisher`.
    ///                   once attached it can begin to receive values.
    func receive<S>(subscriber: S) where S : Subscriber, 
        Self.Failure == S.Failure, Self.Output == S.Input
}

在 Publisher 的定義中,Output 代表數(shù)據(jù)流中輸出的值框咙,值的更新可能是同步咕痛,也可能是異步,F(xiàn)ailure 代表可能產(chǎn)生的錯(cuò)誤喇嘱,也就是說 Pubslier 最核心的是定義了值與可能的錯(cuò)誤茉贡。Publisher 通過 receive(subscriber:) 用來接受訂閱,并且要求 Subscriber 的值和錯(cuò)誤類型要一致來保證類型安全者铜。

例如來自官方的一個(gè)例子:

extension NotificationCenter {
        struct Publisher: Combine.Publisher {
                typealias Output = Notification
                typealias Failure = Never
                init(center: NotificationCenter, name: Notification.Name, object: Any? = nil)
        }
}

這個(gè) Publisher 提供的值就是 Notification 類型腔丧,而且永遠(yuǎn)不會(huì)產(chǎn)生錯(cuò)誤(Never)放椰。這個(gè)擴(kuò)展非常有用,可以很方便地將任何 Notification 轉(zhuǎn)換成 Publisher愉粤,便于我們將應(yīng)用改造成 Reactive砾医。


讓我們?cè)倏匆粋€(gè)的例子:

let justPubliser = Publishers.Just("Hello")

justPubliser 會(huì)給每個(gè)訂閱者發(fā)送一個(gè) "Hello" 消息,然后立即結(jié)束(這個(gè)數(shù)據(jù)流只包含一個(gè)值)衣厘。


類似的如蚜,Combine 為我們提供了一些便捷的 Publisher 的實(shí)現(xiàn),除了上面的 Just影暴,這里也列出一些很有用的 Publisher错邦。

Empty

Empty 不提供任何值的更新,并且可以選擇立即正常結(jié)束坤检。


Once

Once 可以提供以下兩種數(shù)據(jù)流之一:

發(fā)送一次值更新兴猩,然后立即正常結(jié)束(和 Just 一樣)
立即因錯(cuò)誤而終止
Fail

Fail 和 Once 很像,也是提供兩種情況之一:

發(fā)送一次值更新早歇,然后立即因錯(cuò)誤而終止
立即因錯(cuò)誤而終止


Sequence

Sequence 將給定的一個(gè)序列按序通知到訂閱者倾芝。


Future

Future 初始化需要提供執(zhí)行具體操作的 closure,這個(gè)操作可以是異步的箭跳,并且最終返回一個(gè) Result晨另,所以 Future 要么發(fā)送一個(gè)值,然后正常結(jié)束谱姓,要么因錯(cuò)誤而終止借尿。在將一些異步操作轉(zhuǎn)換為 Publisher 時(shí)非常有用,尤其是網(wǎng)絡(luò)請(qǐng)求屉来。例如:

let apiRequest = Publishers.Future { promise in
    URLSession.shared.dataTask(with: url) { data, _, _ in
        promise(.success(data))
    }.resume()
}


Deferred

Deferred 初始化需要提供一個(gè)生成 Publisher 的 closure路翻,只有在有 Subscriber 訂閱的時(shí)候才會(huì)生成指定的 Publisher,并且每個(gè) Subscriber 獲得的 Publisher 都是全新的茄靠。

那么茂契,在之前的 Just Publisher 例子中,假設(shè)我們要實(shí)現(xiàn)延遲兩秒后再發(fā)送消息呢慨绳?也很簡(jiǎn)單掉冶。我們前面說到 Publisher 都是可以組合變換的,這些組合變換可以通過操作符來實(shí)現(xiàn)的脐雪。

上面的例子利用 delay 操作符就可以改寫為:

let delayedJustPublisher = justPubliser.delay(for: 2, scheduler: DispatchQueue.main)

在文章的后半部分厌小,我們將了解一些常用操作符以及它們的使用場(chǎng)景。

Subscriber

和 Publisher 相對(duì)應(yīng)的战秋,Subscriber 就是觀察者模式中 Observer璧亚。

public protocol Subscriber : CustomCombineIdentifierConvertible {
    /// The kind of values this subscriber receives.
    associatedtype Input

    /// The kind of errors this subscriber might receive.
    ///
    /// Use `Never` if this `Subscriber` cannot receive errors.
    associatedtype Failure : Error

    /// Tells the subscriber that it has successfully subscribed to 
    /// the publisher and may request items.
    ///
    /// Use the received `Subscription` to request items from the publisher.
    /// - Parameter subscription: A subscription that represents the connection 
    /// between publisher and subscriber.
    func receive(subscription: Subscription)

    /// Tells the subscriber that the publisher has produced an element.
    ///
    /// - Parameter input: The published element.
    /// - Returns: A `Demand` instance indicating how many more elements 
    /// the subcriber expects to receive.
    func receive(_ input: Self.Input) -> Subscribers.Demand

    /// Tells the subscriber that the publisher has completed publishing, 
    /// either normally or with an error.
    ///
    /// - Parameter completion: A `Completion` case indicating 
    /// whether publishing completed normally or with an error.
    func receive(completion: Subscribers.Completion<Self.Failure>)
}

可以看出,從概念上和我們之前定義的簡(jiǎn)單觀察者模式相差無(wú)幾脂信。Publisher 在自身狀態(tài)改變時(shí)涨岁,調(diào)用 Subscriber 的三個(gè)不同方法(receive(subscription), receive(_:Input), receive(completion:))來通知 Subscriber拐袜。


這里也可以看出,Publisher 發(fā)出的通知有三種類型:

  • Subscription:Subscriber 成功訂閱的消息梢薪,只會(huì)發(fā)送一次蹬铺,取消訂閱會(huì)調(diào)用它的 Cancel 方法來釋放資源
  • Value(Subscriber 的 Input,Publisher 中的 Output):真正的數(shù)據(jù)秉撇,可能發(fā)送 0 次或多次
  • Completion:數(shù)據(jù)流終止的消息甜攀,包含兩種類型:.finished 和 .failure(Error),最多發(fā)送一次琐馆,一旦發(fā)送了終止消息规阀,這個(gè)數(shù)據(jù)流就斷開了,當(dāng)然有的數(shù)據(jù)流可能永遠(yuǎn)沒有終止

大部分場(chǎng)景下我們主要關(guān)心的是后兩種消息瘦麸,即數(shù)據(jù)流的更新和終止谁撼。

Combine 內(nèi)置的 Subscriber 有三種:

  • Sink
  • Assign
  • Subject

Sink 是非常通用的 Subscriber,我們可以自由的處理數(shù)據(jù)流的狀態(tài)滋饲。

例如:

let once: Publishers.Once<Int, Never> = Publishers.Once(100)
let observer: Subscribers.Sink<Publishers.Once<Int, Never>> = Subscribers.Sink(receiveCompletion: {
    print("completed: \($0)")
}, receiveValue: {
    print("received value: \($0)")
})
once.subscribe(observer)

// received value: 100
// completed: finished

Publisher 甚至提供了 sink(receiveCompletion:, receiveValue:) 方法來直接訂閱厉碟。

Assign 可以很方便地將接收到的值通過 KeyPath 設(shè)置到指定的 Class 上(不支持 Struct),很適合將已有的程序改造成 Reactive屠缭。

例如:

class Student {
    let name: String
    var score: Int

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

let student = Student(name: "Jack", score: 90)
print(student.score)
let observer = Subscribers.Assign(object: student, keyPath: \Student.score)
let publisher = PassthroughSubject<Int, Never>()
publisher.subscribe(observer)
publisher.send(91)
print(student.score)
publisher.send(100)
print(student.score)

// 90
// 91
// 100

在例子中箍鼓,一旦 publisher 的值發(fā)生改變,相應(yīng)的呵曹,student 的 score 也會(huì)被更新款咖。

PassthroughSubject 這里是 Combine 內(nèi)置的一個(gè) Publisher,可能你會(huì)好奇:前面不是說 Subject 是一種 Observer 嗎奄喂,這里怎么又是 Pulisher 呢铐殃?

Subject

有些時(shí)候我們想隨時(shí)在 Publisher 插入值來通知訂閱者,在 Rx 中也提供了一個(gè) Subject 類型來實(shí)現(xiàn)跨新。Subject 通常是一個(gè)中間代理富腊,即可以作為 Publisher,也可以作為 Observer玻蝌。Subject 的定義如下:

public protocol Subject : AnyObject, Publisher {

    /// Sends a value to the subscriber.
    ///
    /// - Parameter value: The value to send.
    func send(_ value: Self.Output)

    /// Sends a completion signal to the subscriber.
    ///
    /// - Parameter completion: A `Completion` instance which indicates 
    /// whether publishing has finished normally or failed with an error.
    func send(completion: Subscribers.Completion<Self.Failure>)
}

作為 Observer 的時(shí)候,可以通過 Publisher 的 subscribe(_:Subject) 方法訂閱某個(gè) Publisher词疼。

作為 Publisher 的時(shí)候俯树,可以主動(dòng)通過 Subject 的兩個(gè) send 方法,我們可以在數(shù)據(jù)流中隨時(shí)插入數(shù)據(jù)贰盗。目前在 Combine 中许饿,有三個(gè)已經(jīng)實(shí)現(xiàn)對(duì) Subject: AnySubject,CurrentValueSubject 和 PassthroughSubject 舵盈。

利用 Subject 可以很輕松的將現(xiàn)在已有代碼的一部分轉(zhuǎn)為 Reactive陋率,一個(gè)比較典型的使用場(chǎng)景是:

// Before
class ContentManager {

    var content: [String] {
        didSet {
            delegate?.contentDidChange(content)
        }
    }

    func getContent() {
        content = ["hello", "world"]
    }
}

// After
class RxContentController {

    var content = CurrentValueSubject<[String], NSError>([])

    func getContent() {
        content.value = ["hello", "world"]
    }
}

CurrentValueSubject 的功能很簡(jiǎn)單球化,就是包含一個(gè)初始值,并且會(huì)在每次值變化的時(shí)候發(fā)送一個(gè)消息瓦糟,這個(gè)值會(huì)被保存筒愚,可以很方便的用來替代 Property Observer。在上例中菩浙,以前需要實(shí)現(xiàn) delegate 來獲取變化巢掺,現(xiàn)在只需要訂閱 content 的變化即可,并且它作為一個(gè) Publisher劲蜻,可以很方便的利用操作符進(jìn)行組合變換陆淀。

PassthroughSubject 和 CurrentValueSubject 幾乎一樣,只是沒有初始值先嬉,也不會(huì)保存任何值轧苫。

AnyPublisher、AnySubscriber疫蔓、AnySubject

前面說到 Publisher含懊、Subscriber、Subject 是類型安全的鳄袍,那么在使用中不同類型的 Publisher绢要、Subscriber、Subject 勢(shì)必會(huì)造成一些麻煩拗小。

考慮下面這種情況:

class StudentManager {

    let namesPublisher: ??? // what's the type?

    func updateStudentsFromLocal() {
        let student1 = Student(name: "Jack", score: 75)
        let student2 = Student(name: "David", score: 80)
        let student3 = Student(name: "Alice", score: 96)
        let namesPublisher: Publishers.Sequence<[String], Never> = Publishers.Sequence<[Student], Never>(sequence: [student1, student2, student3]).map { $0.name }
        self.namesPublisher = namesPublisher
    }

    func updateStudentsFromNetwork() {
        let namesPublisher: Publishers.Future<[String], Never> = Publishers.Future { promise in
            getStudentsFromNetwork {
                let names: [String] = ....
                promise(.success([names]))
            }
        }
        self.namesPublisher = namesPublisher
    }
}

這里 namesPublisher 究竟該是什么類型呢重罪?第一個(gè)方法返回的是 Publishers.Sequence<[String], Never>,第二個(gè)方法是 Publishers.Future<[String], Never>哀九,而我們是無(wú)法定義成 Publisher<[String], Never> 的剿配。

AnyPublisher、AnySubscriber阅束、AnySubject 正是為這樣的場(chǎng)景設(shè)計(jì)的呼胚,它們是通用類型,任意的 Publisher息裸、Subscriber蝇更、Subject 都可以通過 eraseToAnyPublisher()、eraseToAnySubscriber()呼盆、eraceToAnySubject() 轉(zhuǎn)化為對(duì)應(yīng)的通用類型年扩。

class StudentManager {

    let namePublisher: AnyPublisher<[String, Never]>

    func updateStudentsFromLocal() {
        let namePublisher: AnyPublisher<[String, Never]> = Publishers.Sequence<[Student], Never>(sequence: students).map { $0.name }.eraseToAnyPublisher()
        self.namePublisher = namePublisher
    }

    func updateStudentsFromNetwork() {
        let namePublisher: AnyPublisher<[String, Never]> = Publishers.Future { promise in
            promise(.success([names]))
        }.eraseToAnyPublisher()
        self.namePublisher = namePublisher
    }
}

AnyPublisher、AnySubscriber访圃、AnySubject 的另外一個(gè)實(shí)用場(chǎng)景是創(chuàng)建自定義的 Publisher/Subscriber/Subject厨幻,因?yàn)樵诳蚣苤兴鼈円呀?jīng)是實(shí)現(xiàn)好相應(yīng)的協(xié)議了。

let justPubliser = AnyPublisher<String, NSError> { subscribe in
    _ = subscribe.receive("hello")  // ignore demand
    subscribe.receive(completion: .finished)
}
let subscriber = AnySubscriber<String, NSError>(receiveValue: { input in
    print("Received input: \(input)")
    return .unlimited
}, receiveCompletion: { completion in
    print("Completed with \(completion)")
})
justPubliser.subscribe(subscriber)

// Received input: hello
// Completed with finished

Cancellable

讓我們回到上文提到的 Future 的一個(gè)例子:

let apiRequest = Publishers.Future { promise in
    URLSession.shared.dataTask(with: url) { data, _, _ in
        promise(.success(data))
    }.resume()
}

它似乎能夠很好的工作,但是一個(gè)比較經(jīng)典的場(chǎng)景是:用戶上傳某個(gè)文件况脆,上傳到一半發(fā)現(xiàn)選錯(cuò)了就點(diǎn)擊取消饭宾,我們也應(yīng)該取消這個(gè)上傳。類似的還有一些臨時(shí)生成的資源需要在取消訂閱時(shí)釋放格了。這在 Combine 中該怎么實(shí)現(xiàn)呢看铆?

Combine 中提供了 Cancellable 這個(gè)協(xié)議。定義很簡(jiǎn)單:

protocol Cancellable {
    /// Cancel the activity.
    func cancel()
}

前面我們提到 Publisher 在被訂閱時(shí)會(huì)給 Subscriber 發(fā)送一個(gè) Subscription 消息笆搓,這個(gè) Subscription 恰好也實(shí)現(xiàn)了 Cancellable 協(xié)議性湿,在取消訂閱時(shí),會(huì)調(diào)用它的 cancel 方法满败。

這樣肤频,我們就可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的例子:

struct AnySubscription: Subscription {
    private let cancellable: Cancellable

    init(_ cancel: @escaping () -> Void) {
        cancellable = AnyCancellable(cancel)
    }

    func cancel() {
        cancellable.cancel()
    }
}

let downloadPublisher = AnyPublisher<Data?, Never> { subscribe in
    let task = URLSession.shared.uploadTask(with: request, fromFile: file) { (data, _, _) in
        _ = subscribe.receive(data) // ignore demand
        subscribe.receive(completion: .finished)
    }
    let subscription = AnySubscription {
        task.cancel()
    }
    subscribe.receive(subscription: subscription)
    task.resume()
}
let cancellable = downloadPublisher.sink { data in
    print("Received data: \(data)")
}

// Cancel the task before it finishes
cancellable.cancel()
操作符

操作符是 Combine 中非常重要的一部分,通過各式各樣的操作符算墨,可以將原來各自不相關(guān)的邏輯變成一致的(unified)宵荒、聲明式的(declarative)的數(shù)據(jù)流。

轉(zhuǎn)換操作符:

  • map/mapError
  • flatMap
  • replaceNil
  • scan
  • setFailureType

過濾操作符:

  • filter
  • compactMap
  • removeDuplicates
  • replaceEmpty/replaceError

reduce 操作符:

  • collect
  • ignoreOutput
  • reduce

運(yùn)算操作符:

  • count
  • min/max

匹配操作符:

  • contains
  • allSatisfy

序列操作符:

  • drop/dropFirst
  • append/prepend
  • prefix/first/last/output

組合操作符:

  • combineLatest
  • merge
  • zip

錯(cuò)誤處理操作符:

  • assertNoFailure
  • catch
  • retry

時(shí)間控制操作符:

measureTimeInterval
debounce
delay
throttle
timeout

其他操作符:

  • encode/decode
  • switchToLatest
  • share
  • breakpoint/breakpointOnError
  • handleEvents

以下內(nèi)容僅供參考净嘀,目前并未完成所有的操作符的詳細(xì)介紹
map/mapError

map 將收到的值按照給定的 closure 轉(zhuǎn)換為其他值报咳,mapError 則將錯(cuò)誤轉(zhuǎn)換為另外一種錯(cuò)誤類型。

func map<T>(_ transform: (Output) -> T) -> Publishers.Just<T>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挖藏,一起剝皮案震驚了整個(gè)濱河市暑刃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌膜眠,老刑警劉巖岩臣,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異宵膨,居然都是意外死亡架谎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門辟躏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谷扣,“玉大人,你說我怎么就攤上這事捎琐』嵯眩” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵瑞凑,是天一觀的道長(zhǎng)末秃。 經(jīng)常有香客問我,道長(zhǎng)拨黔,這世上最難降的妖魔是什么蛔溃? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮篱蝇,結(jié)果婚禮上贺待,老公的妹妹穿的比我還像新娘。我一直安慰自己零截,他們只是感情好麸塞,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涧衙,像睡著了一般哪工。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弧哎,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天雁比,我揣著相機(jī)與錄音,去河邊找鬼撤嫩。 笑死偎捎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的序攘。 我是一名探鬼主播茴她,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼程奠!你這毒婦竟也來了丈牢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瞄沙,失蹤者是張志新(化名)和其女友劉穎己沛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帕识,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泛粹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肮疗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晶姊。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖伪货,靈堂內(nèi)的尸體忽然破棺而出们衙,到底是詐尸還是另有隱情,我是刑警寧澤碱呼,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布蒙挑,位于F島的核電站,受9級(jí)特大地震影響愚臀,放射性物質(zhì)發(fā)生泄漏忆蚀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望馋袜。 院中可真熱鬧男旗,春花似錦、人聲如沸欣鳖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)泽台。三九已至什荣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間怀酷,已是汗流浹背稻爬。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜕依,地道東北人因篇。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像笔横,于是被迫代替她去往敵國(guó)和親竞滓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

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

  • 簡(jiǎn)介 Combine是Apple在2019年WWDC上推出的一個(gè)新框架吹缔。該框架提供了一個(gè)聲明性的Swift API...
    云天涯丶閱讀 24,429評(píng)論 5 22
  • 作者寄語(yǔ) 很久之前就想寫一個(gè)專題商佑,專寫Android開發(fā)框架,專題的名字叫 XXX 從入門到放棄 厢塘,沉淀了這么久茶没,...
    戴定康閱讀 7,619評(píng)論 13 85
  • 一、Retrofit詳解 ·Retrofit的官網(wǎng)地址為 : http://square.github.io/re...
    余生_d630閱讀 1,832評(píng)論 0 5
  • RxJava詳解(一) 年初的時(shí)候就想學(xué)習(xí)下RxJava然后寫一些RxJava的教程晚碾,無(wú)奈發(fā)現(xiàn)已經(jīng)年底了抓半,然而我還...
    CharonChui閱讀 2,412評(píng)論 0 0
  • http://blog.csdn.net/yyh352091626/article/details/5330472...
    奈何心善閱讀 3,550評(píng)論 0 0