Runtime源碼 —— property和ivar

我原本以為這兩個東西沒啥好寫的,結(jié)果是property確實(shí)沒啥好寫的侨歉,但是ivar就不少了膘壶。

本文不探討何時該選擇property掰吕,何時該選擇ivar

我會把我研究這兩東西的過程原原本本的展示出來。

試探期

Runtime源碼 —— 方法加載的過程這篇文章中呜笑,我提到過兩個結(jié)構(gòu)體:

  • class_ro_t
    記錄編譯期就已經(jīng)確定的信息
  • class_rw_t
    運(yùn)行期拷貝class_ro_t中的部分信息存入此結(jié)構(gòu)體中夫否,并存放運(yùn)行期添加的信息

細(xì)心的同學(xué)應(yīng)該發(fā)現(xiàn)在ro和rw結(jié)構(gòu)體中,method叫胁、protocol和property都是存在的凰慈,拷貝也就是拷貝的這部分信息,但是ro中還存在一個字段叫做:

const ivar_list_t * ivars;

這玩意兒就是本文研究的重點(diǎn)了驼鹅。

例子

在寫代碼之前微谓,我心里是這樣想的:
ivar都應(yīng)該存在ro的ivars字段中,property存在ro的baseProperties字段中输钩,在運(yùn)行期豺型,將property拷貝到rw中。獲取property的時候直接從rw中獲取买乃,獲取ivar則從ro中獲取姻氨。

寫代碼測試一下:

// ZNObjectFather.h
@interface ZNObjectFather : NSObject {
    NSInteger ivarInt;
    BOOL ivarBool;
}
@property (nonatomic, assign) NSInteger propertyInt;
@end

// ZNObjectFather.m
#import "ZNObjectFather.h"
@implementation ZNObjectFather
@end

還是通過lldb驗(yàn)證一下:

// 獲取ZNObjectFather class的內(nèi)存地址
2017-02-21 10:06:12.772188 TestOSX[6560:288465] 0x100002e10
(lldb) p (class_data_bits_t *)0x100002e30
(class_data_bits_t *) $0 = 0x0000000100002e30
(lldb) p $0->data()
(class_rw_t *) $1 = 0x000060800007e140
(lldb) p (*$1).ro
(const class_ro_t *) $2 = 0x0000000100002318
(lldb) p *$2
(const class_ro_t) $3 = {
  flags = 128
  instanceStart = 8
  instanceSize = 32
  reserved = 0
  ivarLayout = 0x0000000000000000 <no value available>
  name = 0x000000010000139a "ZNObjectFather"
  baseMethodList = 0x0000000100002260
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100002298
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000100002300
}
// 獲取ivars
(lldb) p $3.ivars
(const ivar_list_t *const) $4 = 0x0000000100002298
(lldb) p *$4
// $5的內(nèi)容顯示count = 3,但是實(shí)際只聲明了兩個ivar
(const ivar_list_t) $5 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 3
    first = {
      offset = 0x0000000100002d88
      name = 0x0000000100001448 "ivarInt"
      type = 0x0000000100001aee "q"
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $5.get(1)
// ivar_t的結(jié)構(gòu)體后面會分析
(ivar_t) $6 = {
  offset = 0x0000000100002d90
  name = 0x0000000100001450 "ivarBool"
  type = 0x0000000100001af0 "c"
  alignment_raw = 0
  size = 1
}
(lldb) p $5.get(2)
// 發(fā)現(xiàn)聲明的屬性自動生成了一個_propertyName的ivar
(ivar_t) $7 = {
  offset = 0x0000000100002d80
  name = 0x0000000100001459 "_propertyInt"
  type = 0x0000000100001aee "q"
  alignment_raw = 3
  size = 8
}
// 獲取property
(lldb) p $3.baseProperties
(property_list_t *const) $8 = 0x0000000100002300
(lldb) p *$8
// 結(jié)果符合預(yù)期
(property_list_t) $9 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "propertyInt", attributes = "Tq,N,V_propertyInt")
  }
}

property的測試結(jié)果很正常剪验,ivar不完全相同肴焊,如果直接聲明的2個之外,屬性也自動生成了一個ivar功戚。

另外$3的baseMethodList也不為空娶眷,存的就是property自動生成的get/set方法,感興趣自己打印一下啸臀。

看到這里也就不難理解為什么:

property = ivar + get + set

但也并不總是這樣届宠,如果重寫了屬性的get/set方法,就不會生成_propertyName這樣的ivar了,本文不做深入席揽。

再看看$3里面的這么兩個屬性:

instanceStart = 8
instanceSize = 32
  • instanceStart之所以等于8顽馋,是因?yàn)槊總€對象的isa占用了前8個字節(jié)。
  • instanceSize = isa + 3個ivar幌羞,$6的size只有1寸谜,但是為了對齊,也占用了8属桦,對齊是怎么計(jì)算的后面再講熊痴。

到這里對ivar和property已經(jīng)有一個大概的理解了,下面繼續(xù)深入聂宾。

深入期

根據(jù)上半部分的分析果善,我們已經(jīng)知道了

  • ivars在編譯期就已經(jīng)確定了
  • 屬性會生成 _propertyName格式的ivar,也在編譯期確定
  • 對象的大小是由 isa + ivars決定的

但是這就引出了如下幾個問題:

  • 帶有繼承體系的對象是怎么表示的系谐?
  • 繼承體系中對象的 instanceStart和 instanceSize是怎么計(jì)算的巾陕?
  • ivar_t中的 alignment_raw和 offset是什么意思?
  • class_ro_t中的 ivarLayout和 weakIvarLayout是什么意思纪他?

現(xiàn)在我們都知道class_ro_t中的ivar鄙煤,property,protocol和method都是在編譯期就確定的茶袒,在運(yùn)行期時梯刚,通過realizeClass()方法將部分信息拷貝到class_rw_t中。

在realizeClass()方法中有這么一段代碼:

// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

注釋里面講了亡资,這一步會調(diào)整ivar的offset值植康,并且更新ro的信息,看起來這一步就是關(guān)鍵,看看方法是怎么實(shí)現(xiàn)的:

static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro_t*& ro) 
{
    class_rw_t *rw = cls->data();
    ...
    const class_ro_t *super_ro = supercls->data()->ro;
    ...// 省略了用于debug的相關(guān)代碼
    if (ro->instanceStart >= super_ro->instanceSize) {
        // Superclass has not overgrown its space. We're done here.
        return;
    }
    if (ro->instanceStart < super_ro->instanceSize) {
        ...
        class_ro_t *ro_w = make_ro_writeable(rw);
        ro = rw->ro;
        moveIvars(ro_w, super_ro->instanceSize);
        gdb_objc_class_changed(cls, OBJC_CLASS_IVARS_CHANGED, ro->name);
    } 
}

只保留了最關(guān)鍵的代碼,關(guān)注一下其中的if判斷,比較的是當(dāng)前類的instanceStart和父類的instanceSize宪卿,當(dāng)start < size的時候調(diào)整了一下當(dāng)前類ro的相關(guān)信息。

這給了我一個信息,也就是在這一步之前禾进,ro中的instanceStart和instanceSize其實(shí)并不是最終值宠纯。

具體調(diào)整的過程在moveIvars(ro_w, super_ro->instanceSize)這個方法中完成:

static void moveIvars(class_ro_t *ro, uint32_t superSize)
{
    ...
    uint32_t diff;
    ...
    diff = superSize - ro->instanceStart;

    if (ro->ivars) {
        uint32_t maxAlignment = 1;
        for (const auto& ivar : *ro->ivars) {
            if (!ivar.offset) continue;  // anonymous bitfield

            uint32_t alignment = ivar.alignment();
            if (alignment > maxAlignment) maxAlignment = alignment;
        }

        uint32_t alignMask = maxAlignment - 1;
        diff = (diff + alignMask) & ~alignMask;

        for (const auto& ivar : *ro->ivars) {
            if (!ivar.offset) continue;  // anonymous bitfield

            uint32_t oldOffset = (uint32_t)*ivar.offset;
            uint32_t newOffset = oldOffset + diff;
            *ivar.offset = newOffset;

            ...
        }
    }

    *(uint32_t *)&ro->instanceStart += diff;
    *(uint32_t *)&ro->instanceSize += diff;
}

這個方法做了這些事情:

  • 更新當(dāng)前類ivar中的offset字段
  • 更新當(dāng)前類ro的instanceStart和instanceSize

先按照源代碼分析,最后寫代碼驗(yàn)證。

part1
diff = superSize - ro->instanceStart;

獲取了當(dāng)前類的instanceStart和父類的instanceSize的偏移量珍逸,但這并不是最終的結(jié)果摹量,因?yàn)榇嬖趯R的問題祝迂。這就是后面這個if判斷內(nèi)部做的事情睦尽。

part2

先看第一個for循環(huán):

for (const auto& ivar : *ro->ivars) {
    if (!ivar.offset) continue;  // anonymous bitfield

    uint32_t alignment = ivar.alignment();
    if (alignment > maxAlignment) maxAlignment = alignment;
}

遍歷了ivars,獲取了最大得alignment型雳。這個ivar.alignment()是ivar_t結(jié)構(gòu)體中的方法:

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;
    uint32_t size;

    uint32_t alignment() const {
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        return 1 << alignment_raw;
    }
}

#   define WORD_SHIFT 3UL

備注:這里有這么一個字段:alignment_raw当凡,這個字段據(jù)我的理解,應(yīng)該是在編譯期確定的纠俭,但是是按照什么規(guī)則確定的就不清楚了沿量。根據(jù)測試的結(jié)果來看,一般都是0或者3冤荆。

通過ivar.alignment()得到的結(jié)果是1 << 3朴则,也就是8。

part3
uint32_t alignMask = maxAlignment - 1;
diff = (diff + alignMask) & ~alignMask;

這一步確定了diff的值钓简,那個&運(yùn)算的結(jié)果就是把diff按8對齊乌妒,比如本來diff = 9,這一步之后diff = 16外邓。

part4
for (const auto& ivar : *ro->ivars) {
    if (!ivar.offset) continue;  // anonymous bitfield

    uint32_t oldOffset = (uint32_t)*ivar.offset;
    uint32_t newOffset = oldOffset + diff;
    *ivar.offset = newOffset;
}

這一步調(diào)整ivar的offset字段撤蚊,調(diào)整的過程就是用原來的offset加上上一步得到的diff。說白了就是當(dāng)前類的ivar是在父類的ivar之后的损话。

part5
*(uint32_t *)&ro->instanceStart += diff;
*(uint32_t *)&ro->instanceSize += diff;

最后更新了當(dāng)前類的instanceStart和instanceSize侦啸,過程也是加上diff。其實(shí)就是把父類的instanceSize給空出來了丧枪。

到這里的時候光涂,已經(jīng)回答了這部分最開始提出的4個問題中的前3個。先來驗(yàn)證一下豪诲。

例子

為了驗(yàn)證前3個問題顶捷,需要給增加一個類:

// ZNObjectSon.h
#import "ZNObjectFather.h"
@interface ZNObjectSon : ZNObjectFather {
    NSInteger ivarIntSon;
    BOOL ivarBoolSon;
}
@property (nonatomic, assign) NSInteger propertyIntSon;

@end
// ZNObjectSon.m
#import "ZNObjectSon.h"
@implementation ZNObjectSon
@end

此類繼承于ZNObjectFather,按照老套路屎篱,還是先獲取一下類的地址:

2017-02-21 11:43:49.962750 TestOSX[6743:331148] father address: 0x100002e30
2017-02-21 11:43:49.962803 TestOSX[6743:331148] son address: 0x100002de0

接著在reconcileInstanceVariables()方法中添加一個條件斷點(diǎn)服赎,進(jìn)入斷點(diǎn)后葵蒂,通過lldb獲取一下相關(guān)值,請看圖:

ZNObjectFather相關(guān)信息

條件斷點(diǎn)設(shè)置的是ZNObjectFather的地址重虑,所以:

  • ro的信息就是ZNObjectFather的ro

ZNObjectFather繼承于NSObject践付,所以:

  • super_ro是NSObject的ro

根據(jù)控制臺打印的信息,這一步的if判斷結(jié)果為true缺厉,所以直接return了永高,調(diào)整一下條件斷點(diǎn)的內(nèi)容,把地址設(shè)置為ZNObjectSon的地址再試一下:

ZNObjectSon相關(guān)信息

可以看到father的start和size沒有發(fā)生變化提针,因?yàn)樯弦徊阶鲞^說明直接return了命爬。

再來看看son的start值,說實(shí)話看到這個24我是無法理解的辐脖。在這之前我預(yù)期start = 8饲宛,這多出來的16是怎么回事?

我做了一個猜測:instanceStart的值在編譯期已經(jīng)計(jì)算了父類直接聲明的ivar嗜价,由property生成的沒有計(jì)算艇抠。

我做了一些驗(yàn)證,先把father類中的屬性注釋掉了:

// ZNObjectFather.h
@interface ZNObjectFather : NSObject {
    NSInteger ivarInt;
    BOOL ivarBool;
}
//@property (nonatomic, assign) NSInteger propertyInt;
@end

這時候打印出來的start和size如下:

// ZNObjectFather
instanceStart = 8
instanceSize = 24

// ZNObjectSon
instanceStart = 24
instanceSize = 48

沒有問題久锥,father的size少了8家淤,son沒有變化,這個時候son的start >= father的size瑟由,所以直接return絮重。

如果把father中的一個ivar注釋掉:

// ZNObjectFather.h
@interface ZNObjectFather : NSObject {
    NSInteger ivarInt;
//    BOOL ivarBool;
}
@property (nonatomic, assign) NSInteger propertyInt;
@end

這時候打印出來的start和size如下:

// ZNObjectFather
instanceStart = 8
instanceSize = 24

// ZNObjectSon
instanceStart = 16
instanceSize = 40

跟預(yù)期的一樣,因?yàn)橹挥幸粋€ivar错妖,所以son的start只多了8绿鸣,那是不是可以證明上面的猜測是對的呢?

回到上面的截圖暂氯,這個時候那一步if判斷是沒法通過的,因?yàn)?4 < 32亮蛔,這個時候就進(jìn)到了moveIvars()方法了痴施,再進(jìn)這個方法之前,先把son的ivars全打印出來究流,看看offset的原始值:

(lldb) p $2.ivars
(const ivar_list_t *const) $4 = 0x0000000100002170
(lldb) p *$4
(const ivar_list_t) $5 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 3
    first = {
      offset = 0x0000000100002d90
      name = 0x00000001000013e5 "ivarIntSon"
      type = 0x0000000100001ace "q"
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $5.get(0)
(ivar_t) $6 = {
  offset = 0x0000000100002d90
  name = 0x00000001000013e5 "ivarIntSon"
  type = 0x0000000100001ace "q"
  alignment_raw = 3
  size = 8
}
(lldb) p $5.get(1)
(ivar_t) $7 = {
  offset = 0x0000000100002d98
  name = 0x00000001000013f0 "ivarBoolSon"
  type = 0x0000000100001ad0 "c"
  alignment_raw = 0
  size = 1
}
(lldb) p $5.get(2)
(ivar_t) $8 = {
  offset = 0x0000000100002d88
  name = 0x00000001000013fc "_propertyIntSon"
  type = 0x0000000100001ace "q"
  alignment_raw = 3
  size = 8
}
(lldb) p $6.offset
(int32_t *) $9 = 0x0000000100002d90
(lldb) p *$9
(int32_t) $10 = 24
(lldb) p $7.offset
(int32_t *) $11 = 0x0000000100002d98
(lldb) p *$11
(int32_t) $12 = 32
(lldb) p $8.offset
(int32_t *) $13 = 0x0000000100002d88
(lldb) p *$13
(int32_t) $14 = 40

$6和$7是直接聲明的ivar辣吃,排在前2位,屬性生成的$8排在后面芬探,打印出各自的offset神得,第一個ivar的offset即$10就是instanceStart,最后一個offset即$14加上ivar的size就是instanceSize偷仿,結(jié)果很清晰哩簿。

moveIvars()方法前面已經(jīng)分析過源碼了宵蕉,這里不再贅述,直接看看方法結(jié)束之后的結(jié)果节榜,在moveIvars()方法之后加一個斷點(diǎn):

moveIvars()方法調(diào)用結(jié)果.png

這個時候ro的start和size已經(jīng)是這樣的了:

// ZNObjectSon
instanceStart = 32
instanceSize = 56

調(diào)整結(jié)果符合預(yù)期羡玛,繼續(xù)打印出ivar的offset也是沒問題的,這里就不截圖了宗苍。

到這里稼稿,前3個問題基本驗(yàn)證完畢了,還剩最后一個問題:

class_ro_t中的 ivarLayout和 weakIvarLayout是什么意思讳窟?

這個問題之所以單獨(dú)講让歼,是因?yàn)樵趯ふ掖鸢傅倪^程中,出現(xiàn)了一些有趣的結(jié)果丽啡,怎么個有趣法是越,一起來看看。

首先依然是一個猜測碌上,weakIvarLayout名字中有個weak倚评,是不是統(tǒng)計(jì)weak類型的ivar用的。又因?yàn)閕var默認(rèn)類型是strong馏予,所以ivarLayout是不是用于統(tǒng)計(jì)strong類型的ivar呢天梧?

當(dāng)然這里默認(rèn)strong是不針對基本類型的

這時候又要修改一下測試的代碼了,son類已經(jīng)不需要了霞丧,只用一個father類就可以了:

// ZNObjectFather.h
@interface ZNObjectFather : NSObject {
    NSInteger ivarInt;
    BOOL ivarBool;
    __strong NSArray *ivarArray;
}
@end
// .m文件就不寫了呢岗,因?yàn)槭裁匆矝]有

runtime也提供了方法用于獲取 ivarLayout和 weakIvarLayout

const uint8_t *
class_getIvarLayout(Class cls)
{
    if (cls) return cls->data()->ro->ivarLayout;
    else return nil;
}

const uint8_t *
class_getWeakIvarLayout(Class cls)
{
    if (cls) return cls->data()->ro->weakIvarLayout;
    else return nil;
}

其實(shí)就是返回ro的那兩個值,直接用這兩個方法就不需要用lldb慢慢打印了蛹尝,測試的代碼是這樣的:

const uint8_t *ivarLayout = class_getIvarLayout([ZNObjectFather class]);
const uint8_t *weakIvarLayout = class_getWeakIvarLayout([ZNObjectFather class]);

使用上面修改之后的father代碼測試一下后豫,有趣的事情就發(fā)生了:

ivarLayout = "!"
weakIvarLayout = NULL

說實(shí)話,看到這個結(jié)果的時候突那,我的第一反應(yīng)是: 臥槽挫酿,這個!是什么鬼

第二行為空我裝作可以理解,因?yàn)闆]有weak類型的ivar愕难。

我在想早龟,是不是因?yàn)樵趕trong之前有兩個基本類型,去掉那兩個基本類型再試試:

// ZNObjectFather.h
@interface ZNObjectFather : NSObject {
    __strong NSArray *ivarArray;
}
@end

結(jié)果:
ivarLayout = "\x01"
weakIvarLayout = NULL

這個結(jié)果看起來還像點(diǎn)樣子猫缭,那個01中的1應(yīng)該就表示有一個strong類型的ivar吧葱弟,接著做測試:

// ZNObjectFather.h
@interface ZNObjectFather : NSObject {
    __strong NSArray *ivarArray;
}
@property (nonatomic, weak) NSArray *propertyArrayWeak;
@end

結(jié)果:
ivarLayout = "\x01"
weakIvarLayout = "\x11"

看到這里我又不能理解了,這個"\x11"怎么解釋呢猜丹?

沒辦法芝加,只能搜索一下,發(fā)現(xiàn)了Objective-C Class Ivar Layout 探索

這篇文章里面的結(jié)果輸出并不完全正確射窒,可能作者并沒有真正寫代碼測試吧藏杖,但是關(guān)于layout編碼的規(guī)則猜測看起來是沒問題的:

layout 就是一系列的字符往湿,每兩個一組狭魂,比如 \xmn,每一組 ivarLayout 中第一位表示有 m 個非強(qiáng)屬性,第二位表示接下來有 n 個強(qiáng)屬性荡短。

再回過去看之前的結(jié)果:

  • ivarLayout = "\x01"嚷硫,表示在先有0個弱屬性类垫,接著有1個連續(xù)的強(qiáng)屬性培廓。若之后沒有強(qiáng)屬性了,則忽略后面的弱屬性误褪,對weakIvarLayout也是同理责鳍。
  • weakIvarLayout = "\x11",表示先有1個強(qiáng)屬性兽间,然后才有1個連續(xù)的弱屬性历葛。

但是文章中并沒有出現(xiàn)過那個神奇的"!",我繼續(xù)做測試嘀略。

中間過程比較艱辛恤溶,省略無數(shù)次結(jié)果

直到發(fā)現(xiàn)下面這兩次結(jié)果:

// ZNObjectFather.h
@interface ZNObjectFather : NSObject {
    __weak NSArray *ivarArrayWeak;
    __weak NSArray *ivarArrayWeak2;
    __strong NSArray *ivarArray;
}

結(jié)果:
ivarLayout = "!"
weakIvarLayout = "\x02"

這個感嘆號又來了,這個時候根據(jù)上面的規(guī)則帜羊,ivarLayout = "\x21" 才對咒程。

// ZNObjectFather.h
@interface ZNObjectFather : NSObject {
    __weak NSArray *ivarArrayWeak;
    __weak NSArray *ivarArrayWeak2;
    __strong NSArray *ivarArray;
    __strong NSArray *ivarArray2;
}

結(jié)果:
ivarLayout = "\""
weakIvarLayout = "\x02"

居然輸出了一個引號("),結(jié)果難道不應(yīng)該是:ivarLayout = "\x22" 嗎讼育?

這個時候我靈光一閃帐姻!

當(dāng)然在閃之前已經(jīng)搜索了好久,但沒有找到答案奶段,不過這個時候真的是一閃饥瓷!

我去搜索了ASCII碼表,結(jié)果真讓我猜中了:

ASCII碼表.png

所以結(jié)果其實(shí)是正確的痹籍,只是被轉(zhuǎn)成了ASCII碼呢铆,至于xcode為什么要這么做,我就不得而知了...

總結(jié)

原本以為很簡單的property和ivar词裤,其實(shí)一點(diǎn)也不簡單刺洒,特別是ivar,真的是花了很多時間吼砂。順便把class_ro_t中幾個之前沒有分析的屬性也一并理解了一下,還是很不錯的鼎文。

  • property在編譯期會生成_propertyName的ivar渔肩,和相應(yīng)的get/set屬性
  • ivars在編譯期確定,但不完全確定拇惋,offset屬性在運(yùn)行時會修改
  • 對象的大小是由ivars決定的周偎,當(dāng)有繼承體系時抹剩,父類的ivars永遠(yuǎn)放在子類之前
  • class_ro_t的instanceStart和instanceSize會在運(yùn)行時調(diào)整
  • class_ro_t的ivarLayout和weakIvarLayout存放的是強(qiáng)ivar和弱ivar的存儲規(guī)則
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蓉坎,隨后出現(xiàn)的幾起案子澳眷,更是在濱河造成了極大的恐慌,老刑警劉巖蛉艾,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钳踊,死亡現(xiàn)場離奇詭異,居然都是意外死亡勿侯,警方通過查閱死者的電腦和手機(jī)拓瞪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來助琐,“玉大人祭埂,你說我怎么就攤上這事”ィ” “怎么了蛆橡?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長掘譬。 經(jīng)常有香客問我泰演,道長,這世上最難降的妖魔是什么屁药? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任粥血,我火速辦了婚禮,結(jié)果婚禮上酿箭,老公的妹妹穿的比我還像新娘复亏。我一直安慰自己,他們只是感情好缭嫡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布缔御。 她就那樣靜靜地躺著,像睡著了一般妇蛀。 火紅的嫁衣襯著肌膚如雪耕突。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天评架,我揣著相機(jī)與錄音眷茁,去河邊找鬼。 笑死纵诞,一個胖子當(dāng)著我的面吹牛上祈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼登刺,長吁一口氣:“原來是場噩夢啊……” “哼籽腕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纸俭,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤皇耗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后揍很,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體郎楼,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年女轿,在試婚紗的時候發(fā)現(xiàn)自己被綠了箭启。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛉迹,死狀恐怖傅寡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情北救,我是刑警寧澤荐操,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站珍策,受9級特大地震影響托启,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜攘宙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一屯耸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蹭劈,春花似錦疗绣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哈打,卻和暖如春塔逃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背料仗。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工湾盗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人立轧。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓淹仑,卻偏偏與公主長得像丙挽,于是被迫代替她去往敵國和親肺孵。 傳聞我的和親對象是個殘疾皇子匀借,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

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