iOS底層原理19:類和分類的加載

前面已經(jīng)探究了類的加載流程洪唐,類分為懶加載類非懶加載類,他們有不同加載流程客扎,下面來探究下分類的加載饺律,以及分類和類搭配使用的情況

分類的本質(zhì)

準備工作

main.m中定義 HTPerson的分類HT, 代碼如下

image

探索分類本質(zhì)的三種方法

探索分類的本質(zhì),有以下三種方式

  • 【方式一】通過clang
  • 【方式二】通過Xcode文檔搜索Category
  • 【方式三】通過objc源碼搜索 category_t

【方式一】:通過clang

通過clang -rewrite-objc main.m -o main.cpp命令矢棚,查看編譯后的 c++文件

  • 其中分類的類型是_category_t郑什,存儲了相應(yīng)的實例方法類方法蒲肋、屬性蘑拯、協(xié)議等信息
image

搜索struct _category_t,如下所示

  • 其中有兩個_method_list_t兜粘,分別對應(yīng)對象方法類方法
image
  • 全局搜索_CATEGORY_INSTANCE_METHODS_HTPerson_申窘,找到其底層實現(xiàn)
image
  • 查看協(xié)議屬性的結(jié)構(gòu)
image

這里我們發(fā)現(xiàn)一個【問題】:分類中定義的屬性沒有相應(yīng)的set、get方法孔轴,我們可以通過關(guān)聯(lián)對象來設(shè)置(關(guān)于如何設(shè)置關(guān)聯(lián)對象剃法,我們將在下一篇中進行分析)

【方式二】:通過Xcode文檔搜索 Category

通過快捷鍵command+shift+0,搜索Category

image

【方式三】:通過objc源碼搜索 category_t

通過objc818源碼搜索category_t類型

image

分類的加載的源碼分析

分類的底層結(jié)構(gòu)是結(jié)構(gòu)體category_t路鹰,下面我們就來探究 分類是何時加載進來的贷洲,以及加載的過程

分類加載的引入

WWDC2020中關(guān)于數(shù)據(jù)結(jié)構(gòu)的變化(Class data structures changes)視頻地址收厨,蘋果為分類和動態(tài)添加專門分配的了一塊內(nèi)存rwe,因為rwe屬于dirty memory优构,所以肯定是需要動態(tài)開辟內(nèi)存诵叁。下面從class_rw_t中去查找相關(guān)rwe的源碼

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;
    
    // ...省略代碼
    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }

    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        // 判斷rwe是否存在
        if (fastpath(v.is<class_rw_ext_t *>())) {
            // 如果已經(jīng)有rwe數(shù)據(jù),直接返回地址指針
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            // 為rwe開辟內(nèi)存并且返回地址指針
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }

    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }
    // ...省略代碼
}

從代碼可以看出俩块,extAllocIfNeeded方法用來開辟rwe內(nèi)存黎休,全局搜索extAllocIfNeeded,在下列幾個地方有相關(guān)調(diào)用:

  • attachCategories方法:添加分類信息
  • demangledName
  • class_setVersion:設(shè)置類的版本
  • addMethods_finish:動態(tài)添加方法
  • class_addProtocol:動態(tài)添加協(xié)議
  • _class_addProperty:動態(tài)添加屬性
  • objc_duplicateClass

本文主要來探究分類的加載玉凯,??我們來分析attachCategories方法做了什么

attachCategories 反推法

attachCategories方法势腮,源碼如下:

// 將分類的 方法列表、屬性漫仆、協(xié)議等數(shù)據(jù)加載到 類中
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }

    /*
     * Only a few classes have more than 64 categories during launch.
     * This uses a little stack, and avoids malloc.
     *
     * Categories must be added in the proper order, which is back
     * to front. To do that with the chunking, we iterate cats_list
     * from front to back, build up the local buffers backwards,
     * and call attachLists on the chunks. attachLists prepends the
     * lists, so the final result is in the expected order.
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    // 獲取rwe
    auto rwe = cls->data()->extAllocIfNeeded();
    // 遍歷所有的分類
    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[I];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                // 當(dāng)mlists的個數(shù)為 64時捎拯,對方法進行排序,然后將 mlists加載到rwe中
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            // 如果 mcount = 0盲厌,mlist存放的位置在63個位置署照,總共是0 ~ 63, mlists最多存放64個方法列表(mlist)
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        // 處理屬性數(shù)據(jù)
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        // 處理協(xié)議相關(guān)信息
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

attachCategories準備分類的數(shù)據(jù)吗浩,然后調(diào)用attachLists將數(shù)據(jù)添加到rwe中建芙,那么到底哪些地方調(diào)用attachCategories方法,

  • 全局搜索attachCategories懂扼,發(fā)現(xiàn)有兩處進行了調(diào)用禁荸,分別是 attachToClass方法和load_categories_nolock方法
image

image

attachToClass流程流程分析

全局搜索attachToClass,發(fā)現(xiàn)只有methodizeClass方法中進行了調(diào)用

image
  • methodizeClass方法阀湿,我們應(yīng)該不陌生赶熟,在上一篇類的加載中有分析,從源碼我們發(fā)現(xiàn)previously的值為nil
  • previously作為備用參數(shù)陷嘴,這種設(shè)計可能是蘋果內(nèi)部調(diào)試用的
  • attachToClass調(diào)用流程:_read_images --> realizeClassWithoutSwift --> methodizeClass --> attachToClass --> attachCategories --> attachLists

load_categories_nolock流程分析

  • 全局搜索load_categories_nolock映砖,在loadAllCategories方法中調(diào)用
image
  • 接著全局搜索loadAllCategories,在load_images方法中調(diào)用
image
  • didInitialAttachCategories默認值是false灾挨,當(dāng)執(zhí)行完loadAllCategories()后將didInitialAttachCategories的值設(shè)為true邑退,其實就是只調(diào)用一次loadAllCategories()方法
  • load_categories_nolock的調(diào)用流程:load_images --> loadAllCategories --> load_categories_nolock --> attachCategories

attachLists方法分析

attachLists方法得源碼如下:

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

    if (hasArray()) {
        // many lists -> many lists
        uint32_t oldCount = array()->count;
        uint32_t newCount = oldCount + addedCount;
        array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
        newArray->count = newCount;
        array()->count = newCount;

        for (int i = oldCount - 1; i >= 0; I--)
            newArray->lists[i + addedCount] = array()->lists[I];
        for (unsigned i = 0; i < addedCount; I++)
            newArray->lists[i] = addedLists[I];
        free(array());
        setArray(newArray);
        validate();
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
        validate();
    } 
    else {
        // 1 list -> many lists
        Ptr<List> oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        for (unsigned i = 0; i < addedCount; I++)
            array()->lists[i] = addedLists[I];
        validate();
    }
}

從源碼可以看出,attachLists方法總共有三個流程分支:
【流程1】:0 lists -> 1 list

  • addedLists[0]的指針賦值給list

【流程2】:1 list -> many lists

  • 計算舊的list的個數(shù)
  • 計算新的list個數(shù) 涨醋,新的list個數(shù) = 原有的list個數(shù) + 新增的list個數(shù)
  • 根據(jù)newCount開辟相應(yīng)的內(nèi)存瓜饥,類型是array_t類型,并設(shè)置數(shù)組標識位-setArray
  • 將原有的list放在數(shù)組的末尾浴骂,因為最多只有一個不需要遍歷存儲
  • 遍歷addedLists將遍歷的數(shù)據(jù)從數(shù)組的開始位置存儲

【流程3】:many lists -> many lists

  • 判斷hasArray()是否存在

  • 計算原有的數(shù)組中的list個數(shù),array()->lists

  • 計算新的list個數(shù) 宪潮,新的list個數(shù) = 原有的list個數(shù) + 新增的list個數(shù)

  • 根據(jù)newCount開辟相應(yīng)的內(nèi)存溯警,類型是array_t類型

  • 設(shè)置新數(shù)組的個數(shù)等于newCount

  • 設(shè)置原有數(shù)組的個數(shù)等于newCount

  • 遍歷原有數(shù)組中l(wèi)ist將其存放在newArray->lists中 且是放在數(shù)組的末尾

  • 遍歷addedLists將遍歷的數(shù)據(jù)從數(shù)組的開始位置存儲

  • 釋放原有的array()

  • 設(shè)置新的newArray

  • list_array_tt結(jié)構(gòu)和方法分析

image
  • rwe結(jié)構(gòu)中的 方法趣苏、屬性協(xié)議的類型都是繼承自list_array_tt梯轻,在底層是二維數(shù)組的形式存儲
image

實例驗證分類的加載

通過上面的兩個例子食磕,我們可以大致將分類 是否實現(xiàn)+load方法的情況分為4種

類和分類 分類實現(xiàn)+load 分類未實現(xiàn)+load
類實現(xiàn)+load 非懶加載類+非懶加載分類<span class="Apple-tab-span" style="white-space:pre"></span> 非懶加載類+懶加載分類<span class="Apple-tab-span" style="white-space:pre"></span>
類未實現(xiàn)+load 懶加載類+非懶加載分類<span class="Apple-tab-span" style="white-space:pre"></span> 懶加載類+懶加載分類

準備工作

  • 創(chuàng)建HTPerson類以及分類HTPerson (HTA)

非懶加載類和非懶加載分類的加載

主類實現(xiàn)了+load方法,分類同樣實現(xiàn)了+load方法喳挑,在前文分類的加載時機時彬伦,我們已經(jīng)分析過這種情況,所以可以直接得出結(jié)論伊诵,這種情況下

  • 程序啟動单绑,會直接加載非懶加載類,加載主類的方法
  • 分類的數(shù)據(jù)加載是通過load_images加載到類中的

運行代碼曹宴,發(fā)現(xiàn)會調(diào)用attachCategories方法搂橙,來加載分類信息,通過bt查看函數(shù)調(diào)用棧

image

在相應(yīng)函數(shù)出設(shè)置斷點笛坦,打印結(jié)果如下


image
  • 通過MachOView查看可執(zhí)行文件
image

非懶加載類與懶加載分類

主類實現(xiàn)了+load方法区转,分類未實現(xiàn)+load方法

  • 運行程序,發(fā)現(xiàn)并沒有調(diào)用attachCategories方法版扩,那么分類是如何加載的呢废离?
image
  • realizeClassWithoutSwift方法處設(shè)置斷點,我們來看一下ro是否有分類方法
image
  • 獲取ro的方法列表:p ro->baseMethods()
  • 打印第i個方法信息: p $2.get(i).big()
image

從上面的打印輸出可以看出礁芦,分類的方法和類的方法已經(jīng)合并到一起了蜻韭,方法的順序是 HTA分類-HTPerson類,此時分類已經(jīng) 加載進來了宴偿,但是還沒有排序湘捎,說明這種情況下分類數(shù)據(jù)在編譯時就與類數(shù)據(jù)合并到一起了,不需要運行時添加進去

  • 通過MachOView查看可執(zhí)行文件
image

懶加載類與懶加載分類

主類和分類均未實現(xiàn)+load方法

  • 程序啟動時窄刘,類數(shù)據(jù)不會加載窥妇,只有在首次接收消息時才加載
image

其中realizeClassMaybeSwiftMaybeRelock是消息流程中慢速查找中的函數(shù),即在第一次調(diào)用消息時才會去加載懶加載類

  • realizeClassWithoutSwift方法處設(shè)置斷點娩践,我們來看一下ro是否有分類方法
image
  • 通過MachOView查看可執(zhí)行文件
image

【結(jié)論】:

  • 懶加載類與懶加載分類的數(shù)據(jù)加載是在消息第一次調(diào)用時加載
  • 分類數(shù)據(jù)與類數(shù)據(jù)活翩,在編譯時已合并到一起,MachO文件中的分類列表__objc_catlist中無分類

懶加載類與非懶加載分類

主類未實現(xiàn)+load方法翻伺,分類實現(xiàn)了+load方法

  • 運行程序材泄,會調(diào)用realizeClassWithoutSwift方法,即程序一啟動吨岭,就會記載類數(shù)據(jù)拉宗,產(chǎn)看函數(shù)調(diào)用棧如下圖:
image
  • realizeClassWithoutSwift方法處設(shè)置斷點,我們來看一下ro是否有分類方法
image
  • 從上面的打印輸出可以看出,分類的方法和類的方法已經(jīng)合并到一起了旦事,方法的順序是 HTA分類-HTPerson類魁巩,此時分類已經(jīng) 加載進來了,但是還沒有排序姐浮,說明這種情況下分類數(shù)據(jù)在編譯時就與類數(shù)據(jù)合并到一起了谷遂,不需要運行時添加進去

  • 通過MachOView查看可執(zhí)行文件

image

結(jié)論:

  • 懶加載類變成非懶加載類,分類的數(shù)據(jù)在編譯期間合并到類數(shù)據(jù)中

多分類的情況

新增兩個分類卖鲤,HTPerson (HTB)HTPerson (HTC)

image

通過不同組合來肾扰,驗證類和分類的加載,總結(jié)如下

實現(xiàn)+load方法的分類個數(shù) 非懶加載類 懶加載類
0 編譯時類數(shù)據(jù)與分類數(shù)據(jù)已合并 </br> MachO文件中類和分類數(shù)據(jù)如下:</br> __objc_classlist:1 </br> __objc_nlclslist:1 </br> __objc_catlist:0 </br> __objc_nlcatlist:0 首次接收消息時蛋逾,才加載類數(shù)據(jù)集晚,分類數(shù)據(jù)與類數(shù)據(jù),在編譯時已合并到一起 </br> MachO文件中類和分類數(shù)據(jù)如下:</br> __objc_classlist:1 </br> __objc_nlclslist:0 </br> __objc_catlist:0 </br> __objc_nlcatlist:0
1 程序啟動加載類數(shù)據(jù)换怖,load_images時加載分類數(shù)據(jù) </br> MachO文件中類和分類數(shù)據(jù)如下:</br> __objc_classlist:1 </br> __objc_nlclslist:1 </br> __objc_catlist:3 </br> __objc_nlcatlist:1 程序啟動加載類數(shù)據(jù)(編譯器將類標記為非懶加載類)甩恼,分類數(shù)據(jù)與類數(shù)據(jù),在編譯時已合并到一起 </br> MachO文件中類和分類數(shù)據(jù)如下:</br> __objc_classlist:1 </br> __objc_nlclslist:1 </br> __objc_catlist:0 </br> __objc_nlcatlist:0
2 程序啟動加載類數(shù)據(jù)沉颂,load_images時加載分類數(shù)據(jù) </br> MachO文件中類和分類數(shù)據(jù)如下:</br> __objc_classlist:1 </br> __objc_nlclslist:1 </br> __objc_catlist:3 </br> __objc_nlcatlist:2 編譯后条摸,類仍是懶加載類,程序啟動(load_images方法中)會加載類和分類數(shù)據(jù) </br> MachO文件中類和分類數(shù)據(jù)如下:</br> __objc_classlist:1 </br> __objc_nlclslist:0 </br> __objc_catlist:3 </br> __objc_nlcatlist:2
3 程序啟動加載類數(shù)據(jù)铸屉,load_images時加載分類數(shù)據(jù) </br> MachO文件中類和分類數(shù)據(jù)如下:</br> __objc_classlist:1 </br> __objc_nlclslist:1 </br> __objc_catlist:3 </br> __objc_nlcatlist:3 編譯后钉蒲,類仍是懶加載類,程序啟動(load_images方法中)會加載類和分類數(shù)據(jù) </br> MachO文件中類和分類數(shù)據(jù)如下:</br> __objc_classlist:1 </br> __objc_nlclslist:0 </br> __objc_catlist:3 </br> __objc_nlcatlist:3
  • 1彻坛、非懶加載類 + 3個懶加載分類
image
  • 2顷啼、非懶加載類 + 2個懶加載分類 + 1個非懶加載分類
    程序啟動加載類數(shù)據(jù),load_images時按照MachO中 __objc_catlist中的順序挨個加載分類數(shù)據(jù)
image
  • 3昌屉、非懶加載類 + 1個懶加載分類 + 2個非懶加載分類

程序啟動加載類數(shù)據(jù)钙蒙,load_images時按照MachO中 __objc_catlist中的順序挨個加載分類數(shù)據(jù)

  • 4、非懶加載類 + 3個非懶加載分類

程序啟動加載類數(shù)據(jù)间驮,load_images時按照MachO中 __objc_catlist中的順序挨個加載分類數(shù)據(jù)

  • 5躬厌、懶加載類 + 3個懶加載分類

首次接收消息時,才加載類數(shù)據(jù)竞帽,分類數(shù)據(jù)與類數(shù)據(jù)扛施,在編譯時已合并到一起

image

  • 6、懶加載類 + 2個懶加載分類 + 1個非懶加載分類

程序啟動加載類數(shù)據(jù)(編譯器將類標記為非懶加載類)屹篓,分類數(shù)據(jù)與類數(shù)據(jù)疙渣,在編譯時已合并到一起

image

  • 7、懶加載類 + 1個懶加載分類 + 2個非懶加載分類

編譯后堆巧,類仍是懶加載類妄荔,程序啟動(load_images方法中)會加載類和分類數(shù)據(jù)泼菌,類和分類的加載流程:load_images --> prepare_load_methods --> realizeClassWithoutSwift --> methodizeClass --> attachToClass --> attachCategories

image
  • 8、懶加載類 + 3個非懶加載類

編譯后懦冰,類仍是懶加載類灶轰,程序啟動(load_images方法中)會加載類和分類數(shù)據(jù)谣沸,類和分類的加載流程:load_images --> prepare_load_methods --> realizeClassWithoutSwift --> methodizeClass --> attachToClass --> attachCategories

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刷钢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子乳附,更是在濱河造成了極大的恐慌内地,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赋除,死亡現(xiàn)場離奇詭異阱缓,居然都是意外死亡,警方通過查閱死者的電腦和手機举农,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門荆针,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人颁糟,你說我怎么就攤上這事航背。” “怎么了棱貌?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵玖媚,是天一觀的道長。 經(jīng)常有香客問我婚脱,道長今魔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任障贸,我火速辦了婚禮错森,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘篮洁。我一直安慰自己涩维,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布嘀粱。 她就那樣靜靜地躺著激挪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锋叨。 梳的紋絲不亂的頭發(fā)上垄分,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音娃磺,去河邊找鬼薄湿。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的豺瘤。 我是一名探鬼主播吆倦,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坐求!你這毒婦竟也來了蚕泽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤桥嗤,失蹤者是張志新(化名)和其女友劉穎须妻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泛领,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡荒吏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了渊鞋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绰更。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锡宋,靈堂內(nèi)的尸體忽然破棺而出儡湾,到底是詐尸還是另有隱情,我是刑警寧澤员辩,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布盒粮,位于F島的核電站,受9級特大地震影響奠滑,放射性物質(zhì)發(fā)生泄漏丹皱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一宋税、第九天 我趴在偏房一處隱蔽的房頂上張望摊崭。 院中可真熱鬧,春花似錦杰赛、人聲如沸呢簸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽根时。三九已至,卻和暖如春辰晕,著一層夾襖步出監(jiān)牢的瞬間蛤迎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工含友, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留替裆,地道東北人校辩。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像辆童,于是被迫代替她去往敵國和親宜咒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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