SnapKit源碼分析

SnapKit是基于NSLayoutConstraint封裝的一個(gè)輕量級(jí)的布局框架.區(qū)別于iOS9.0中蘋(píng)果引入的系統(tǒng)框架NSLayoutAnchor。其實(shí)NSLayoutAnchor是一個(gè)工廠類,類似NSNumber這樣的設(shè)計(jì)思想.

開(kāi)始

當(dāng)我們開(kāi)始寫(xiě)約束的時(shí)候臭胜,一般都從
view.snp.makeConstraints()方法開(kāi)始,通過(guò)點(diǎn)擊snp我們進(jìn)到里面看轴术,發(fā)現(xiàn)它是下面這個(gè)樣子:

// ConstraintView 實(shí)際上就是UIView
extension ConstraintView {
    public var snp: ConstraintViewDSL {
        return ConstraintViewDSL(view: self)
    }
}

我們現(xiàn)在知道了實(shí)現(xiàn)約束的功能其實(shí)跟這個(gè)ConstraintViewDSL類有很大關(guān)系,這里也是我們理解snapKit庫(kù)如何實(shí)現(xiàn)開(kāi)始的地方钦无,下面開(kāi)始詳細(xì)介紹這個(gè)類逗栽。

ConstraintViewDSL

查看源碼我們發(fā)現(xiàn)這個(gè)類遵守了一個(gè)ConstraintAttributesDSL協(xié)議。這個(gè)協(xié)議里面沒(méi)有定義屬性和方法失暂,默認(rèn)實(shí)現(xiàn)了一些功能:

protocol ConstraintAttributesDSL: ConstraintBasicAttributesDSL { }
extension ConstraintAttributesDSL {
    public var top: ConstraintItem {
        return ConstraintItem(target: self.target, attributes: ConstraintAttributes.top)
    }
    public var bottom: ConstraintItem {
        return ConstraintItem(target: self.target, attributes: ConstraintAttributes.bottom)
    }
    ...
}

現(xiàn)在我們還不知道ConstraintItem是做什么的彼宠,不過(guò)沒(méi)關(guān)系鳄虱,下面會(huì)詳細(xì)說(shuō)到。接著回到ConstraintViewDSL類里面凭峡,可以看到有一些我們常用到的方法:

public func makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
    ConstraintMaker.makeConstraints(item: self.view, closure: closure)
}
public func updateConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
    ConstraintMaker.updateConstraints(item: self.view, closure: closure)
}

講到這里我們可以了解到拙已,項(xiàng)目中常用到的view.snp.makeConstraints() 方法和 view.snp.bottom 其實(shí)都是在ConstraintViewDSL類里面定義的。那現(xiàn)在關(guān)于ConstraintViewDSL類我們就先講到這吧摧冀,下面我們重點(diǎn)去了解ConstraintMaker和ConstraintItem是干什么的倍踪。

ConstraintMaker

寫(xiě)一段最簡(jiǎn)單的代碼,讓ConstraintMaker和我們見(jiàn)見(jiàn)面索昂。

view.snp.makeConstraints({make: ConstraintMaker in 
    make.top.equalTo(20)
})

這是我們有疑問(wèn)建车,為什么作者不直接在ConstraintMaker上面寫(xiě)約束呢,給我們ConstraintViewDSL類有什么用呢椒惨,下面我們將這種框架核心簡(jiǎn)單來(lái)實(shí)現(xiàn)下缤至,站在作者的角度去看待問(wèn)題:

  1. 首先我們自己來(lái)定義一個(gè)ConstraintMaker類,內(nèi)部實(shí)現(xiàn)如下:
class ConstraintMaker {
    
    var item: UIView
    var descriptions = [Constraint]()
    
    init(item: UIView) {
        self.item = item
    }
    
    static func prepareConstraints(item: UIView, closure: (_ make: ConstraintMaker) -> Void) -> [Constraint] {
        let maker = ConstraintMaker(item: item)
        closure(maker)
        return maker.descriptions
    }
    
    static func makeConstraints(item: UIView, closure: (_ make: ConstraintMaker) -> Void) {
        let constraints = prepareConstraints(item: item, closure: closure)
        for constraint in constraints {
            constraint.active()
        }
    }
}

extension ConstraintMaker {
    
    var bottom: ConstraintMakerExtendable {
        // 這里先簡(jiǎn)單實(shí)現(xiàn)下ConstraintAttributes
        let attr = ConstraintAttributes()
        return makeExtendableWithAttributes(attr)
    }
    
    var top: ConstraintMakerExtendable {
        // 這里先簡(jiǎn)單實(shí)現(xiàn)下ConstraintAttributes
        let attr = ConstraintAttributes()
        return makeExtendableWithAttributes(attr)
    }
    
    private func makeExtendableWithAttributes(_ attributes: ConstraintAttributes) -> ConstraintMakerExtendable {
        // 這里存在著將ConstraintAttributes對(duì)象轉(zhuǎn)化成了Constraint對(duì)象
        let constraint = Constraint()
        descriptions.append(constraint)
        return ConstraintMakerExtendable()
    }
}

這些就是snapKit實(shí)現(xiàn)約束的核心方法了康谆,為了不報(bào)錯(cuò)领斥,這里把剩下的幾個(gè)輔助的類也定義了,它們內(nèi)部只實(shí)現(xiàn)了一些簡(jiǎn)單的方法:

class Constraint {
    func active() { print("開(kāi)始布局啦") }
}

class ConstraintMakerExtendable {
    func calc() { print("計(jì)算約束") }
}

class ConstraintAttributes {
    static var top = ConstraintAttributes()
    static var bottom = ConstraintAttributes()
}

接著我們還需要定義一個(gè)ConstraintViewDSL類沃暗,用來(lái)承載具體的約束:

class ConstraintViewDSL {
    
    internal let view: UIView
    
    internal init(view: UIView) {
        self.view = view
    }
    
    public func makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
        ConstraintMaker.makeConstraints(item: self.view, closure: closure)
    }
}

extension UIView {
    var snp: ConstraintViewDSL {
        return ConstraintViewDSL(view: self)
    }
}

大功告成月洛,現(xiàn)在我們也可以自己寫(xiě)一個(gè)約束庫(kù)了,在外面調(diào)用:

let view1 = UIView()
view1.snp.makeConstraints { (make) in
    let bottom: ConstraintMakerExtendable = make.bottom
    bottom.calc()
    
    let top: ConstraintMakerExtendable = make.top
    top.calc()
}

打印如下:

計(jì)算約束
計(jì)算約束
開(kāi)始布局啦
開(kāi)始布局啦

回到ConstraintMaker中繼續(xù)講

SnapKit的作者寫(xiě)了很多的類孽锥,當(dāng)我們第一眼看到這么多類嚼黔,會(huì)覺(jué)得無(wú)從下手,不知道從哪開(kāi)始閱讀忱叭,下面我大概整理了一下隔崎,約束的過(guò)程今艺。

// 一條完整的約束一般是這樣的:
make.top.left.equalTo().offset().priority()

//鏈?zhǔn)秸{(diào)用的關(guān)系如下:
make: ConstraintMaker
    .top: ConstraintMakerExtendable
        .left: ConstraintMakerExtendable(繼承自下面的ConstraintMakerRelatable)
             .equalTo: ConstraintMakerRelatable
                      .offset: ConstraintMakerEditable(繼承自下面的ConstraintMakerPriortizable)
                              .priority: ConstraintMakerPriortizable
// .offset韵丑、.priority類繼承關(guān)系
ConstraintMakerEditable -> ConstraintMakerPriortizable -> ConstraintMakerFinalizable
// .top、.left虚缎、.equalTo類繼承關(guān)系
ConstraintMakerExtendable -> ConstraintMakerRelatable

通過(guò)閱讀源碼可以看到撵彻,調(diào)用view.snp.makeConstraints()方法,實(shí)際上內(nèi)部是先調(diào)用prepareConstraints方法將約束準(zhǔn)備好实牡,在調(diào)用activate()將約束添加到視圖上:

internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
    let constraints = prepareConstraints(item: item, closure: closure)
    for constraint in constraints {
        constraint.activateIfNeeded(updatingExisting: false)
    }
}

activeate方法作為添加約束內(nèi)部實(shí)際調(diào)用的是:

NSLayoutConstraint.activate(layoutConstraints)

這個(gè)方法我們比較熟悉陌僵,是系統(tǒng)添加約束的方法,只是activate方法做了一層封裝创坞,當(dāng)然這個(gè)方法除了單純添加約束也可作為更新約束使用碗短,下面會(huì)詳細(xì)講解到。

image

上面這種圖很形象的表示了約束執(zhí)行的過(guò)程题涨,在makeExtendableWithAttributes方法中偎谁,maker對(duì)象調(diào)用它的.bottom方法(類型為ConstraintMakerExtendable)將約束添加到descriptions數(shù)組中总滩,返回ConstraintMakerExtendable類型進(jìn)行下一次的鏈?zhǔn)秸{(diào)用,然后獲取準(zhǔn)備生產(chǎn)的Array<Constraint>,最后進(jìn)行加工:

// maker對(duì)象調(diào)用makeConstraints方法巡雨,開(kāi)始加工
func makeConstraints() {
    // 獲得半成品
    let constraints = prepareConstraints(item: item, closure: closure)
    // 開(kāi)始加工
    for constraint in constraints {
        constraint.activateIfNeeded(updatingExisting: false)
    }
}

我們繼續(xù)看ConstraintMaker中的加工的機(jī)器:


func makeExtendableWithAttributes(_ attributes: ConstraintAttributes) -> ConstraintMakerExtendable {
    // 一條完整的約束描述類 類似 make.top.equal(10)
    let description = ConstraintDescription(item: self.item, attributes: attributes)
    //將單條約束添加到數(shù)組中
    self.descriptions.append(description)
    // 它可以實(shí)現(xiàn)多個(gè)屬性鏈?zhǔn)讲僮?有了它 就可以實(shí)現(xiàn) make.width.height 這種特性 
    // 其中width是ConstraintMaker的屬性 height是ConstraintMakerExtendable的屬性
    // 它們都是ConstraintMakerExtendable類型
    return ConstraintMakerExtendable(description)
}

有了這個(gè)ConstraintMakerExtendable類就可以通過(guò)鏈?zhǔn)秸{(diào)用比如.width方法闰渔,.height方法添加一些約束,來(lái)一步步完善ConstraintDescription類

ConstraintDescription可以看成是Constraint腳手架铐望,在一步步添加約束時(shí)操作的都是ConstraintDescription類冈涧,等將所有約束添加到數(shù)組中,準(zhǔn)備下一步生產(chǎn)時(shí)正蛙,會(huì)拿到它內(nèi)部的constraint屬性(Constraint類型)進(jìn)行操作督弓。

題外話,既然makeConstraints()方法內(nèi)部執(zhí)行了兩步操作跟畅,那我們就可以利用這個(gè)特性咽筋,在視圖有多種布局的時(shí)候,可以用到prepareConstraints方法徊件,將布局提前裝載好奸攻,然后根據(jù)狀態(tài)執(zhí)行不同顯示效果,代碼如下:

let v1 = View()
let v2 = View()
self.container.addSubview(v1)
self.container.addSubview(v2)

let constraints = v1.snp.prepareConstraints { (make) -> Void in
    make.edges.equalTo(v2)
    return
}

//打印 self.container.snp_constraints.count == 0,

for constraint in constraints {
    constraint.activate()
}

//打印 self.container.snp_constraints.count == 4,

for constraint in constraints {
    constraint.deactivate()
}

//打印 self.container.snp_constraints.count == 0,

再來(lái)講講 .equalTo()

其實(shí)和它類似的方法有很多包括:.equalToSuperview(), .lessThanOrEqualTo(), .lessThanOrEqualToSuperview(), .greaterThanOrEqualTo(), .greaterThanOrEqualToSuperview()實(shí)現(xiàn)的功能類似虱痕。相同點(diǎn)在是它內(nèi)部調(diào)用的是同一個(gè)方法:

func relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable {

這個(gè)方法返回一個(gè)ConstraintMakerEditable類型睹耐,用于對(duì)約束添加附加的操作(offset偏移量,priority優(yōu)先級(jí))部翘。進(jìn)入到這個(gè)方法內(nèi)部硝训,我們將核心代碼提取出來(lái):

func relatedTo(_ other: ConstraintRelatableTarget) -> ConstraintMakerEditable {
    if let other = other as? ConstraintItem {
        //這里處理參數(shù)類似于equalTo(view.snp.bottom)
    } else if let other = other as? ConstraintView {
        //這里處理參數(shù)類似于equalTo(view)
    } else if let other = other as? ConstraintConstantTarget {
        //這里處理參數(shù)類似于equalTo(50)
    } else if let other = other as? ConstraintLayoutGuide {
        //這里處理參數(shù)類似于equalTo(layoutGuide)
    }
}
let v1 = View()
let g1 = UILayoutGuide()

self.container.addSubview(v1)
self.container.addLayoutGuide(g1)
            
v1.snp.makeConstraints { (make) -> Void in
    make.top.equalTo(g1).offset(50)
    make.left.equalTo(g1.snp.top).offset(50)
}

如何實(shí)現(xiàn)make.top.equalTo(view)和make.top.equalTo(view.snp.top)效果一樣?

通過(guò)查看ConstraintMakerRelatable類下面的relatedTo()方法,我們可以看到在傳入不同類型的參數(shù)時(shí)(view和view.snp.top分別為UIView類型和ConstraintItem類型)新思,方法內(nèi)部經(jīng)過(guò)處理窖梁,全部轉(zhuǎn)化成了ConstraintItem處理,這時(shí)夹囚,我們猜想當(dāng)參數(shù)類型是UIView時(shí)纵刘,是否自動(dòng)轉(zhuǎn)為了ConstraintItem類型,帶著這個(gè)疑問(wèn)我們接著看:

在relatedTo方法中關(guān)于視圖的判斷邏輯是這樣子的:

if let other = other as? ConstraintView {
    related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
}

如果傳入的是視圖對(duì)象荸哟,則ConstraintItem對(duì)象的attributes就置為了.none,應(yīng)該和這個(gè)有關(guān)假哎,我們?cè)趤?lái)了解一下ConstraintAttributes是做什么的?

ConstraintAttributes

這是一個(gè)結(jié)構(gòu)體類型鞍历,它遵守兩個(gè)協(xié)議舵抹,通過(guò)這個(gè)字面量協(xié)議和多選協(xié)議,完成以組合的形式加入約束劣砍,最后通過(guò)layoutAttributes數(shù)組惧蛹,對(duì)接到NSLayoutConstraint.Attribute中,下面我們來(lái)實(shí)現(xiàn)一個(gè):

enum LayoutAttribute: CustomDebugStringConvertible {
    case left
    case right
    case top
    case width
    
    var debugDescription: String {
        switch self {
        case .left: return "左邊"
        case .right: return "右邊"
        case .top: return "上邊"
        case .width: return "寬度"
        }
    }
}

struct ConstraintAttributes: OptionSet, ExpressibleByIntegerLiteral {

    typealias IntegerLiteralType = UInt

    var rawValue: UInt

    init(rawValue: UInt) {
        self.rawValue = rawValue
    }

    init(_ rawValue: UInt) {
        self.init(rawValue: rawValue)
    }

    init(integerLiteral value: IntegerLiteralType) {
        self.init(value)
    }

    static var none: ConstraintAttributes { return 0 }
    static var left: ConstraintAttributes { return 1 }
    static var right: ConstraintAttributes { return 2 }
}

extension ConstraintAttributes {
    var layoutAttributes:[LayoutAttribute] {
        var attrs = [LayoutAttribute]()
        if contains(ConstraintAttributes.left) { attrs.append(.left) }
        if contains(ConstraintAttributes.right) { attrs.append(.right) }
        if contains(ConstraintAttributes.none) { /*什么都不做*/ }
        return attrs
    }
}

在外面調(diào)用:

let attributes: ConstraintAttributes = [.left, .right]
let description = attributes.layoutAttributes.map{ $0.debugDescription }
print(description)
//["左邊", "右邊"]

另外我們還有一種簡(jiǎn)便的方式來(lái)實(shí)現(xiàn)ConstraintAttributes支持多選的方式:

struct ConstraintAttributes: OptionSet {
    
    var rawValue: Int
    
    init(rawValue: Int) {
        self.rawValue = rawValue
    }
    
    static var none = ConstraintAttributes(rawValue: 1 << 0)
    static var left = ConstraintAttributes(rawValue: 1 << 1)
    static var right = ConstraintAttributes(rawValue: 1 << 2)
}

我們?cè)趯?shí)現(xiàn)過(guò)程中沒(méi)有使用字面量協(xié)議ExpressibleByIntegerLiteral,直接用的位于運(yùn)算香嗓,和SnapKit作者實(shí)現(xiàn)的效果相同爵政。

回到剛才的問(wèn)題,如果在ConstraintItem構(gòu)造方法(target: AnyObject?, attributes: ConstraintAttributes)中attributes傳入.none,它表示對(duì)layoutAttributes數(shù)組不添加NSLayoutConstraint.Attribute元素陶缺。

因?yàn)镾napKit是對(duì)NSLayoutConstraint的封裝钾挟,我們有必要說(shuō)一說(shuō)NSLayoutConstraint這個(gè)類了,它的構(gòu)造方法如下:

/*
 item: 指定需要添加約束的視圖一
 attribute: 指定視圖一需要約束的屬性
 relatedBy: 指定視圖一和視圖二添加約束的關(guān)系
 toItem: 指定視圖一依賴關(guān)系的視圖二饱岸;可為nil
 attribute: 指定視圖一所依賴的視圖二的屬性掺出,若view2=nil,該屬性設(shè)置 NSLayoutAttributeNotAnAttribute
 multiplier: 系數(shù)
    情況一:設(shè)置A視圖的高度 = A視圖高度 * multiplier + constant苫费;此時(shí)才會(huì)起作用汤锨;
    情況二:設(shè)置A視圖和其他視圖的關(guān)系或 toItem=nil,multiplier設(shè)置不等于0即可百框,若等于0會(huì)crash闲礼;
 constant: 常量
 
 layoutConstraint: 返回生成的約束對(duì)象
*/
NSLayoutConstraint(item view1: Any,
    attribute attr1: NSLayoutConstraint.Attribute,
    relatedBy relation: NSLayoutConstraint.Relation,
    toItem view2: Any?, 
    attribute attr2: NSLayoutConstraint.Attribute,
    multiplier: CGFloat, 
    constant c: CGFloat)

通過(guò)文檔介紹說(shuō)該方法實(shí)際上就是滿足一個(gè)數(shù)學(xué)關(guān)系view1.attr1 = view2.attr2 * multiplier + constant

image

回到剛才的ConstraintItem類,對(duì)比NSLayoutConstraint的構(gòu)造方法铐维,仔細(xì)觀察它就能發(fā)現(xiàn)柬泽,我們加的約束無(wú)非就是這樣的關(guān)系:

owningView.ConstraintItem = view1 + attr1 
toView.ConstraintItem = view2 + attr2 

通過(guò)創(chuàng)建兩個(gè)ConstraintItem就能完成基本的約束。了解這點(diǎn)就知道ConstraintItem的作用了嫁蛇。

通過(guò)查看源碼發(fā)現(xiàn)在Constraint類的便利構(gòu)造方法中锨并,對(duì)NSLayoutConstraint進(jìn)行了一層封裝。包括equalTo(view.snp.top) 和 equalTo(view)實(shí)現(xiàn)一樣的效果睬棚,都在這里做了邏輯處理第煮。

最后再來(lái)介紹Constraint類

先來(lái)看這句代碼

let constraint: Constraint = make.top.equal(20).constraint

我們經(jīng)常在外面這樣使用Constraint這個(gè)類,其中ConstraintDescription的作用是用于生產(chǎn)Constraint類抑党,ConstraintDescription的創(chuàng)建在ConstraintMaker類的makeExtendableWithAttributes方法中包警。

實(shí)現(xiàn)一個(gè)動(dòng)畫(huà)效果

var constraint: Constraint?

view.snp.makeConstraints { (make) in
    constraint = make.top.equalToSuperview().offset(10).constraint
}

UIView.animateWithDuration(0.3, {
    constraint?.update(inset: 20)
    self.container.layoutIfNeeded
})

通常我們查看一個(gè)視圖下面是否包含約束時(shí),一般會(huì)直接調(diào)用view.constraints.isEmpty 來(lái)判斷底靠,嚴(yán)謹(jǐn)來(lái)講某個(gè)視圖下可能會(huì)包含一些約束害晦,但這些約束是"不活躍的", 對(duì)視圖顯示不造成任何影響,所以判斷條件需要改一下:

extension UIView {
    var isConstraintEmpty: Bool {
        return self.constraints.filter { $0.isActive }.isEmpty
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末苛骨,一起剝皮案震驚了整個(gè)濱河市篱瞎,隨后出現(xiàn)的幾起案子苟呐,更是在濱河造成了極大的恐慌痒芝,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牵素,死亡現(xiàn)場(chǎng)離奇詭異严衬,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)笆呆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)请琳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)粱挡,“玉大人,你說(shuō)我怎么就攤上這事俄精⊙ぃ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵竖慧,是天一觀的道長(zhǎng)嫌套。 經(jīng)常有香客問(wèn)我,道長(zhǎng)圾旨,這世上最難降的妖魔是什么踱讨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮砍的,結(jié)果婚禮上痹筛,老公的妹妹穿的比我還像新娘。我一直安慰自己廓鞠,他們只是感情好帚稠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著床佳,像睡著了一般翁锡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上夕土,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天馆衔,我揣著相機(jī)與錄音,去河邊找鬼怨绣。 笑死角溃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的篮撑。 我是一名探鬼主播减细,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼赢笨!你這毒婦竟也來(lái)了未蝌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤茧妒,失蹤者是張志新(化名)和其女友劉穎萧吠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體桐筏,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纸型,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狰腌。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡除破,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出琼腔,到底是詐尸還是另有隱情瑰枫,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布丹莲,位于F島的核電站躁垛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏圾笨。R本人自食惡果不足惜教馆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望擂达。 院中可真熱鬧土铺,春花似錦、人聲如沸板鬓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)俭令。三九已至后德,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抄腔,已是汗流浹背瓢湃。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赫蛇,地道東北人绵患。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像悟耘,于是被迫代替她去往敵國(guó)和親落蝙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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