Swift 面向協(xié)議編程

swift

面向協(xié)議編程

你可能聽過類似的概念:面向?qū)ο缶幊?/strong>慈俯、函數(shù)式編程栏尚、泛型編程起愈,再加上蘋果新提出的面向協(xié)議編程,這些統(tǒng)統(tǒng)可以理解為是一種編程范式译仗。所謂編程范式抬虽,是隱藏在編程語言背后的思想,代表著語言的作者想要用怎樣的方式去解決怎樣的問題纵菌。

不同的編程范式反應(yīng)在現(xiàn)實世界中阐污,就是不同的編程語言適用于不同的領(lǐng)域和環(huán)境,比如在面向?qū)ο缶幊趟枷胫性墼玻_發(fā)者用對象來描述萬事萬物并試圖用對象來解決所有可能的問題笛辟。編程范式都有其各自的偏好和使用限制,所以越來越多的現(xiàn)代編程語言開始支持多范式序苏,使語言自身更強壯也更具適用性手幢。

面向協(xié)議編程是在面向?qū)ο缶幊袒A(chǔ)上演變而來,將程序設(shè)計過程中遇到的數(shù)據(jù)類型的抽瘸老辍(抽象)由使用基類進行抽取改為使用協(xié)議進行抽取弯菊。更簡單點舉個例子來說,一個貓類、一個狗類管钳,我們很容易想到抽取一個描述動物的基類钦铁,這就是面向?qū)ο缶幊獭.?dāng)然也會有人想到抽取一個動物通用的協(xié)議才漆,這就是面向協(xié)議編程了牛曹。

而在Swift語言中,協(xié)議被賦予了更多的功能和更廣闊的使用空間醇滥,為協(xié)議增加了擴展功能黎比,使其能夠勝任絕大多數(shù)情況下數(shù)據(jù)類型的抽象,所以蘋果開始聲稱Swift是一門支持面向協(xié)議編程的語言鸳玩。

協(xié)議基礎(chǔ)

官方文檔的定義:

協(xié)議為方法阅虫、屬性、以及其他特定的任務(wù)需求或功能定義藍圖不跟。協(xié)議可被類颓帝、結(jié)構(gòu)體、或枚舉類型采納以提供所需功能的具體實現(xiàn)窝革。滿足了協(xié)議中需求的任意類型都叫做遵循了該協(xié)議购城。

協(xié)議的定義

protocol Food { }

用關(guān)鍵詞 protocol ,聲明一個名為 Food 的協(xié)議虐译。

定義協(xié)議屬性

protocol Pet {
    var name: String { get set }
    var master: String { get }
    static var species: String { get }
}

協(xié)議中定義屬性表示遵循該協(xié)議的類型具備了某種屬性瘪板。

  • 只能使用 var 關(guān)鍵字聲明;

  • 需要明確規(guī)定該屬性是可讀的 {get} 漆诽、 還是可讀可寫的 {get set} 侮攀;

  • 為了保證通用,協(xié)議中必須用static定義類型方法厢拭、類型屬性魏身、類型下標(biāo),因為class只能用在類中蚪腐,不能用于結(jié)構(gòu)體等;

  • 屬性不能賦初始值税朴;

struct Dog: Pet {
    var name: String
    var master: String
    static var species: String = "哺乳動物"
    
    var color: UIColor? = nil
}

var dog = Dog(name: "旺財", master: "小明")
dog.master = "張三" // 更改了主人

定義一個繼承協(xié)議的結(jié)構(gòu)體 Dog 回季,并新增了一個color屬性。

  • static 修飾的類屬性必須有初始值或?qū)崿F(xiàn)了 get set 方法('static var' declaration requires an initializer expression or getter/setter specifier

  • set 為什么不報錯正林?

    if dog.master == "小明" {
        dog.master = "張三"
    }
    

    master 屬性在協(xié)議中被定義為只讀屬性 get泡一,為什么上面的代碼還可以 set

    協(xié)議中的“只讀”屬性修飾的是協(xié)議這種“類型”的實例觅廓。

    let pet: Pet = dog
    pet.master = "李四"
    

    雖然我們并不能像創(chuàng)建類的實例那樣直接創(chuàng)建協(xié)議的實例鼻忠,但是我們可以通過“賦值”得到一個協(xié)議的實例。此時 就會報錯 Cannot assign to property: 'master' is a get-only property杈绸。

    Dog 中新增的 Pet 中沒有的屬性 var color: UIColor? = nil 帖蔓,將不會出現(xiàn)在 pet 中矮瘟。

定義協(xié)議方法

Swift中的協(xié)議可以定義類方法或?qū)嵗椒ǎ谧袷卦搮f(xié)議的類型中塑娇,具體的實現(xiàn)方法的細節(jié)澈侠,通過類或?qū)嵗{(diào)用。

protocol Pet {
    var name: String { get set }
    var master: String { get }
    static var species: String { get }
    
    // 新增的協(xié)議方法
    static func sleep()
    mutating func changeName()
}

struct Dog: Pet {
    var name: String
    var master: String
    static var species: String = "哺乳動物"
    var color: UIColor? = nil
    
    static func sleep() {
        print("要休息了")
    }
    mutating func changeName() {
        name = "大黃"
    }
}
  • 聲明的協(xié)議方法的參數(shù)不能有默認值

    ? func changeName(name: String = "大黃") // Swift認為默認值也是一種變相的實現(xiàn)
    
  • 結(jié)構(gòu)體中的方法修改屬性時埋酬,需要在方法前面加上關(guān)鍵字mutating 哨啃,表示該屬性屬性能被修改。這樣的方法叫 異變方法 写妥。

協(xié)議中的初始化器

每一個寵物在被領(lǐng)養(yǎng)的時候拳球,主人就已經(jīng)確定了:

// 在上面的代碼中新增
protocol Pet {    
    init(master: String)
}

struct Dog: Pet {
    init(master: String) {
        self.master = master
    }
}

此時會報錯 在不初始化所有存儲屬性的情況下從初始化器中返回所有屬性。 ( Return from initializer without initializing all stored properties )珍特。加上 self.name = "" 就可以了祝峻。

class Cat: Pet {
    required init(master: String) {
        self.master = master
        self.name = ""
    }
}

Cat類 遵守了該協(xié)議,初始化器必須用 required 關(guān)鍵字修飾初始化器的具體實現(xiàn)次坡。

繼承與遵守協(xié)議

class SomeClass: NSObject, OneProtocol, TwoProtocol { }

因為Swift中類的繼承是單一的呼猪,但是類可以遵守多個協(xié)議,因此為了突出其單一父類的特殊性砸琅,應(yīng)該 將繼承的父類放在最前面宋距,將遵守的協(xié)議依次放在后面。

多個協(xié)議方法名沖突

protocol ProtocolOne {
    func method() -> Int
}
protocol ProtocolTwo {
    func method() -> String
}

struct PersonStruct: ProtocolOne, ProtocolTwo {
    func method() -> Int {
        return 1
    }
    func method() -> String {
        return "Hello World"
    }
}

let ps = PersonStruct()
//嘗試調(diào)用返回值為Int的方法
let num = ps.method() ?
//嘗試調(diào)用返回值為String的方法
let string = ps.method() ?

let num = (ps as ProtocolOne).method() ?
let string = (ps as ProtocolTwo).method() ?

編譯器無法知道同名method() 方法到底是哪個協(xié)議中的方法症脂,因此需要指定調(diào)用特定協(xié)議的method() 方法 谚赎。

協(xié)議方法的可選實現(xiàn)

  • 方法一:通過 optional 實現(xiàn)可選

    @objc protocol OptionalProtocol {
        @objc optional func optionalMethod()
        func requiredMethod()
    }
    
  • 方法二: 通過 extension 做默認處理

    protocol OptionalProtocol {
        func optionalMethod()
        func requiredMethod()
    }
    
    extension OptionalProtocol {
        func optionalMethod() {
            
        }
    }
    

    協(xié)議的繼承、聚合

協(xié)議的繼承

協(xié)議可以繼承一個或者多個其他協(xié)議并且可以在它繼承的基礎(chǔ)之上添加更多要求诱篷。協(xié)議繼承的語法與類繼承的語法相似壶唤,選擇列出多個繼承的協(xié)議,使用逗號分隔棕所。

protocol OneProtocol { }
protocol TwoProtocol { }
protocol ThreeProtocol: OneProtocol, TwoProtocol { }

產(chǎn)生了一個 新協(xié)議 闸盔,該協(xié)議擁有 OneProtocolTwoProtocol 的方法或?qū)傩浴P枰獙崿F(xiàn) OneProtocolTwoProtocol必須的方法或?qū)傩浴?/p>

協(xié)議的聚合

使用形如OneProtocol & TwoProtocol的形式實現(xiàn)協(xié)議聚合(組合)復(fù)合多個協(xié)議到一個要求里 琳省。

protocol OneProtocol { }
protocol TwoProtocol { }
typealias FourProtocol = OneProtocol & TwoProtocol

聚合出來的不是新的協(xié)議莫鸭,只是一個代指趟大,代指這些協(xié)議的集合蜒谤。

協(xié)議的繼承和聚合的區(qū)別

首先協(xié)議的繼承是定義了一個全新的協(xié)議赋兵,我們是希望它能夠“大展拳腳”得到普遍使用。而協(xié)議的聚合不一樣桦他,它并沒有定義新的固定協(xié)議類型蔫巩,相反,它只是定義一個臨時的擁有所有聚合中協(xié)議要求組成的局部協(xié)議,很可能是“一次性需求”圆仔,使用協(xié)議的聚合保證了代碼的簡潔性垃瞧、易讀性,同時去除了定義不必要的新類型的繁瑣荧缘,并且定義和使用的地方如此接近皆警,見明知意,也被稱為匿名協(xié)議聚合截粗。但是使用了匿名協(xié)議聚合能夠表達的信息就少了一些信姓,所以需要開發(fā)者斟酌使用。

協(xié)議的檢查

if pig is Pet {
    print("遵守了 Pet 協(xié)議")
}

檢查pig 是否是遵守了 Pet 協(xié)議類型的實例绸罗。

協(xié)議的指定

protocol ClassProtocol: class { }

struct Test: ClassProtocol { } // 報錯

使用關(guān)鍵字 class 使定義的協(xié)議只能被類遵守意推。如果有枚舉或結(jié)構(gòu)體嘗試遵守會報錯 Non-class type 'Test' cannot conform to class protocol 'ClassProtocol'

協(xié)議作為參數(shù)

func update(param: FourProtocol) { }

將協(xié)議作為參數(shù)珊蟀,表明遵守了該協(xié)議的實例可作為參數(shù)菊值。

協(xié)議的關(guān)聯(lián)類型

協(xié)議的關(guān)聯(lián)類型指的是根據(jù)使用場景的變化,如果協(xié)議中某些屬性存在 邏輯相同的而類型不同 的情況育灸,可以使用關(guān)鍵字associatedtype來為這些屬性的類型聲明“關(guān)聯(lián)類型”腻窒。

protocol LengthMeasurable {
    associatedtype LengthType
    var length: LengthType { get }
    func printMethod()
}

struct Pencil: LengthMeasurable {
    typealias LengthType = CGFloat
    var length: CGFloat
    func printMethod() {
        print("鉛筆的長度為 \(length) 厘米")
    }
}

struct Bridge: LengthMeasurable {
    typealias LengthType = Int
    var length: Int
    func printMethod() {
        print("橋梁的的長度為 \(length) 米")
    }
}

LengthMeasurable 協(xié)議中用 associatedtype 定義了一個 類型泛型 。在實現(xiàn)協(xié)議的時候磅崭,定義具體的類型儿子。這樣就可以適配各種物體長度的測量。

associatedtype & typealias的區(qū)別
  • associatedtype: 在定義協(xié)議時砸喻,可以用來聲明一個或多個類型作為協(xié)議定義的一部分柔逼,叫關(guān)聯(lián)類型。這種關(guān)聯(lián)類型為協(xié)議中的某個類型提供了自定義名字割岛,其代表的實際類型或?qū)嶋H意義在協(xié)議被實現(xiàn)時才會被指定愉适。
  • typealias: 是給 現(xiàn)有 的類型(包括系統(tǒng)和自定義的)進行重新命名,然后就可以用該別名來代替原來的類型癣漆,已達到改善程序可讀性维咸,而且可以自實際編程中根據(jù)業(yè)務(wù)來重新命名,可以表達實際意義惠爽。

協(xié)議的擴展

設(shè)想一個這樣的場景: 有一個人參加比賽癌蓖,三個評委打分。比賽結(jié)束疆股,求這個人的平均分。

protocol Score {
    var name: String { get set }
    var firstJudge: CGFloat { get set }
    var secondJudge: CGFloat { get set }
    var thirdJudge: CGFloat { get set }
    
    func averageScore() -> String
}

struct Xiaoming: Score {
    var firstJudge: CGFloat
    var secondJudge: CGFloat
    var thirdJudge: CGFloat
    
    func averageScore() -> String {
        let average = (firstJudge + secondJudge + thirdJudge) / 3
        return "\(name)的得分為\(average)"
    }
}
let xiaoming = Xiaoming(name: "小明", firstJudge: 80, secondJudge: 90, thirdJudge: 100)
let average = xiaoming.averageScore()

這場比賽倒槐,如果有10個人參加旬痹,計算平均值的方法,就需要寫10次。代碼重復(fù)嚴(yán)重两残∮酪悖可以通過 協(xié)議的擴展解決問題

extension Score {
    func averageScore() -> String {
        let average = (firstJudge + secondJudge + thirdJudge) / 3
        return "\(name)的得分為\(average)"
    }
}

averageScore 默認進行了實現(xiàn)人弓,不需要遵守者必須實現(xiàn)了沼死。

這個時候,比賽承辦方想統(tǒng)計每個人的最高分崔赌,應(yīng)該怎么辦呢意蛀?

extension Score {
    func maxScore() -> CGFloat {
        return max(firstJudge, secondJudge, thirdJudge)
    }
}
let maxScore = xiaoming.maxScore()

比賽承辦方對比賽結(jié)果的輸出不太滿意。想把 小明的得分為90.0 前面統(tǒng)一加上前綴健芭。

// 對系統(tǒng)協(xié)議進行擴展
extension CustomStringConvertible {
    var customDescription: String {
        return "新希望學(xué)校春季運動會運動會得分為::" + description
    }
}
print(xiaoming.averageScore().customDescription)

總結(jié):

  • 通過協(xié)議的擴展提供協(xié)議中某些屬性和方法的默認實現(xiàn)县钥。
  • 將公共的代碼和屬性統(tǒng)一起來極大的增加了代碼的復(fù)用。
  • 為系統(tǒng)/自定義的協(xié)議提供的擴展慈迈。

Swift的55標(biāo)準(zhǔn)庫協(xié)議

Swift的55標(biāo)準(zhǔn)庫協(xié)議可以分為三類

類型 描述 標(biāo)志
”Can do“協(xié)議 (表示能力)描述的事情是類型可以做或已經(jīng)做過的若贮。 -able 結(jié)尾
"Is a"協(xié)議 (表示身份)描述類型是什么樣的,與"Can do"的協(xié)議相比痒留,這些更基于身份谴麦,表示身份的類型。 -type 結(jié)尾
"Can be"協(xié)議 (表示轉(zhuǎn)換)這個類型可以被轉(zhuǎn)換到或者轉(zhuǎn)換成別的東西伸头。 -Convertible 結(jié)尾

如何更好的命名協(xié)議匾效?

在自定義協(xié)議時應(yīng)該盡可能遵守蘋果的命名規(guī)則,便于開發(fā)人員之間的高效合作熊锭。

55個標(biāo)準(zhǔn)的Swift協(xié)議

55個標(biāo)準(zhǔn)Swift協(xié)議地址(待完成)

協(xié)議編程的優(yōu)勢

面向?qū)ο螅ɡ^承)

有這樣一個需求弧轧,在某個頁面中,顯示的Logo圖片需要切圓角處理碗殷,讓它更美觀一些精绎。

logoImageView.layer.cornerRadius = 5
logoImageView.layer.masksToBounds = true

如果要求,整個APP中所有的Logo都要切圓角處理锌妻。最容易的解決辦法是定義一個名為LogoImageView的類代乃,使用該類初始化Logo對象。

class LogoImageView: UIImageView {
    init(radius: CGFloat = 5) {
        super.init(frame: CGRect.zero)
        layer.cornerRadius = radius
        layer.masksToBounds = true
    }
 
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

如果要求所有的Logo還要支持點擊抖動效果仿粹。

class LogoImageView: UIImageView {
    
    init(radius: CGFloat = 5) {
        super.init(frame: CGRect.zero)
        layer.cornerRadius = radius
        layer.masksToBounds = true
        
        isUserInteractionEnabled = true
        let tap = UITapGestureRecognizer.init(target: self, action: #selector(shakeEvent))
        addGestureRecognizer(tap)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension LogoImageView {
    @objc func shakeEvent() {
        let animation = CAKeyframeAnimation()
        animation.keyPath = "transform"
        animation.duration = 0.25
        
        let origin = CATransform3DIdentity
        let minimum = CATransform3DMakeScale(0.8, 0.8, 1)
        
        let originValue = NSValue(caTransform3D: origin)
        let minimumValue = NSValue(caTransform3D:minimum)
        
        animation.values = [originValue, minimumValue, origin, minimumValue, origin]
        layer.add(animation, forKey: "bounce")
        layer.transform = origin
    }
}

這個時候搁吓,如果其他的功能也要使用抖動動畫,就不得不接受切圓角功能吭历。即使把切圓角功能從初始化方法中剝離成一個可選方法堕仔,但是也不得不接受這份耦合代碼。

有的項目里定義了繼承 UIViewController 的父類晌区,實現(xiàn)了很多功能摩骨,項目里頁面都要繼承它通贞。而且往這個自定義UIViewController里塞代碼實在太方便了,這個類很容易隨著功能迭代逐漸膨脹恼五,變的僵化昌罩,越來越難以維護。下面的子類代碼全都依賴這個父類灾馒,想抽出來復(fù)用非常難茎用。

項目里混合使用了原生功能和H5功能。定義的H5容器的父類WebViewController睬罗,需要滿足以下業(yè)務(wù)要求:

  • 有的H5頁面比較簡單轨功,只需要正常展示網(wǎng)頁即可。
  • 有的需要JS代碼注入傅物。
  • 有的需要提供保存圖片到相冊給H5使用夯辖。
  • 有的需要提供存跳轉(zhuǎn)原始頁面給H5使用。

這個父類中處理了WKWebView實現(xiàn)董饰、 JS注入蒿褂、橋的定義以及橋功能的實現(xiàn)等眾多能力。導(dǎo)致WebViewController代碼量多達幾千行卒暂。很難維護擴展啄栓。

采用 繼承 方式解決復(fù)用的問題,很容易帶來代碼的耦合也祠。

假如 UILabel 也需要抖動的動畫昙楚,采用繼承無法實現(xiàn)。UIImageView 和 UILabel 已經(jīng)是 UIView的子類诈嘿,除非改動UIView堪旧,否則無法通過繼承的方式實現(xiàn)。

面向?qū)ο螅〝U展)

通過擴展奖亚,可以不修改類的實現(xiàn)文件的情況下淳梦,給類增加新的方法∥糇郑可以通過給 UIView 添加擴展來解決 UIImageView 和 UILabel 同時增加抖動功能的需求爆袍。缺點是給一個類加上這個東西就污染了該類所有的對象。(UIButton說: 我不需要為什么塞給我作郭?)

面向?qū)ο螅üぞ哳悾?/h3>

當(dāng)然陨囊,我們可以直接寫一個工具類來實現(xiàn)這個抖動的效果,然后把必要的參數(shù)(layer)傳遞過來夹攒。缺點就是蜘醋,使用起來相對麻煩,眾多的工具類難易管理咏尝。(你有因為不知道該使用哪個工具類頭疼過么压语? 有因為要把方法放哪個工具類頭疼過么闲先?有因為工具類代碼量過多頭疼過么?)

面向協(xié)議

通過協(xié)議重新實現(xiàn) 切圓角功能和抖動動畫功能无蜂。

/// 聲明一個圓角的能力協(xié)議
protocol RoundCornerable {
    func roundCorner(radius: CGFloat)
}

/// 通過擴展給這個協(xié)議方法添加默認實現(xiàn),必須滿足遵守這個協(xié)議的類是繼承UIView的蒙谓。
extension RoundCornerable where Self: UIView {
    func roundCorner(radius: CGFloat) {
        layer.cornerRadius = radius
        layer.masksToBounds = true
    }
}

/// 聲明抖動動畫的協(xié)議
protocol Shakeable {
    func startShake()
}
/// 實現(xiàn)協(xié)議方法內(nèi)容斥季,并指定只有LogoImageView才可以使用。
extension Shakeable where Self: LogoImageView {
    func startShake() {
        let animation = CAKeyframeAnimation()
        animation.keyPath = "transform"
        animation.duration = 0.25
        
        let origin = CATransform3DIdentity
        let minimum = CATransform3DMakeScale(0.8, 0.8, 1)
        
        let originValue = NSValue(caTransform3D: origin)
        let minimumValue = NSValue(caTransform3D:minimum)
        
        animation.values = [originValue, minimumValue, origin, minimumValue, origin]
        layer.add(animation, forKey: "bounce")
        layer.transform = origin
    }
}

/// 遵守了RoundCornerable協(xié)議累驮,才擁有切圓角的功能酣倾。遵守了Shakeable協(xié)議,才擁有抖動動畫效果谤专。
class LogoImageView: UIImageView, RoundCornerable, Shakeable {
    init(radius: CGFloat = 5) {
        super.init(frame: CGRect.zero)
        
        roundCorner(radius: radius)
        
        isUserInteractionEnabled = true
        let tap = UITapGestureRecognizer.init(target: self, action: #selector(shakeEvent))
        addGestureRecognizer(tap)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    @objc func shakeEvent() {
        startShake()
    }
}

泛型及泛型約束

有時候會有一些場景聲明的協(xié)議只給部分對象使用躁锡。

/// 只給遵守Shakeable協(xié)議的LogoImageView添加了拓展
extension Shakeable where Self: LogoImageView { } 

協(xié)議解決面向?qū)ο笾屑值某悊栴}

該模塊引用于:Swift標(biāo)準(zhǔn)庫中常見的協(xié)議。 由于示例過于優(yōu)秀置侍,故直接引用映之。

類結(jié)構(gòu)圖

麻雀作為一種鳥類,應(yīng)該繼承蜡坊,但是如果繼承了杠输,就相當(dāng)于默認了麻雀是一種寵物,這顯然是不和邏輯的秕衙。麻雀在圖中的位置就顯得比較尷尬蠢甲。解決此問題的一般方法如下
類結(jié)構(gòu)圖

乍一看好像解決了這樣的問題,但是仔細想由于Swift只支持單繼承据忘,麻雀沒有繼承類就無法體現(xiàn)麻雀作為一種鳥擁有的特性(比如飛翔)鹦牛。如果此時出現(xiàn)一個新的飛機類,雖然飛機寵物之間沒有任何聯(lián)系勇吊,但是飛機是由很多共同特性的(比如飛翔)曼追,這樣的特性該如何體現(xiàn)呢?答案還是新建一個類成為動物飛機的父類萧福。

面向?qū)ο缶褪沁@樣一層一層的向上新建父類最終得到一個“超級父類”NSObject拉鹃。盡管問題得到了解決,但是麻雀鲫忍、飛機之間的共性并沒有得到很好的體現(xiàn)膏燕。而協(xié)議的出現(xiàn)正是為了解決這類問題。

類結(jié)構(gòu)圖

實際上圖中包括動物悟民、坝辫、飛機等類之間的關(guān)系就應(yīng)該是如上圖所示的繼承關(guān)系。使用協(xié)議將“寵物”射亏、“飛翔”等看作是一種特性近忙,或者是從另一個維度描述這種類別竭业,更重要的是使用協(xié)議并不會打破原有類別之間繼承的父子關(guān)系。

和飛翔相關(guān)的代碼統(tǒng)一放在Flyable中及舍,需要“飛翔”這種能力就遵守該協(xié)議未辆;和寵物相關(guān)的代碼統(tǒng)一放在PetType中,需要成為寵物就遵守該協(xié)議锯玛。這些

協(xié)議靈活多變咐柜,結(jié)合原有的面向?qū)ο箢愔g固有的繼承關(guān)系,完美的描述了這個世界攘残。

文獻

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拙友,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子歼郭,更是在濱河造成了極大的恐慌遗契,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件病曾,死亡現(xiàn)場離奇詭異牍蜂,居然都是意外死亡,警方通過查閱死者的電腦和手機泰涂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門捷兰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人负敏,你說我怎么就攤上這事贡茅。” “怎么了其做?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵顶考,是天一觀的道長。 經(jīng)常有香客問我妖泄,道長驹沿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任蹈胡,我火速辦了婚禮渊季,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罚渐。我一直安慰自己却汉,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布荷并。 她就那樣靜靜地躺著合砂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪源织。 梳的紋絲不亂的頭發(fā)上翩伪,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天微猖,我揣著相機與錄音,去河邊找鬼缘屹。 笑死凛剥,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的轻姿。 我是一名探鬼主播当悔,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼踢代!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嗅骄,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤胳挎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后溺森,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慕爬,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年屏积,在試婚紗的時候發(fā)現(xiàn)自己被綠了医窿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡炊林,死狀恐怖姥卢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情渣聚,我是刑警寧澤独榴,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站奕枝,受9級特大地震影響棺榔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜隘道,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一症歇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谭梗,春花似錦忘晤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缩幸,卻和暖如春壹置,著一層夾襖步出監(jiān)牢的瞬間竞思,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工钞护, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盖喷,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓难咕,卻偏偏與公主長得像课梳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子余佃,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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