iOS手把手帶你探索Category

在我們的實(shí)際開發(fā)中Category分類的使用必不可少岸军,那么我們通過(guò)以下幾個(gè)方面來(lái)探索一下分類

  • 1.什么是分類Category
  • 2.Category的作用
  • 3.CategoryExension的區(qū)別
  • 4.Category底層探究
  • 5.關(guān)聯(lián)對(duì)象的探索

什么是分類(Category)

CategoryOvjective-C 2.0 之后添加的語(yǔ)言特性蔽莱,Category作用是為已經(jīng)存在的類添加方法

Category的作用

  • 1.可以減少單個(gè)文件的體積
  • 2.可以把不同的功能組織到不同的Category中
  • 3.可以按需加載
  • 4.聲明私有方法
  • 5.把framework的私有方法公開

Category和Exension的區(qū)別

  • 1.Category依托當(dāng)前類存在蛔添,Exension則是類的一部分
  • 2.Category在運(yùn)行時(shí)期確定袖迎,Exension在編譯器確定
  • 3.Category不能添加變量,Exension可以添加變量(編譯時(shí)期對(duì)象的內(nèi)存布局已經(jīng)確定)

Category底層探究

4.1 Category的數(shù)據(jù)結(jié)構(gòu)

Category的數(shù)據(jù)結(jié)構(gòu)如下

struct category_t {
    //當(dāng)前類的名稱
    const char *name;
    //需要擴(kuò)展的類對(duì)象史简,在編譯期沒(méi)有值
    classref_t cls;
    //實(shí)例方法
    WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;
    //對(duì)象方法
    WrappedPtr<method_list_t, method_list_t::Ptrauth> classMethods;
    //協(xié)議
    struct protocol_list_t *protocols;
    //實(shí)例屬性
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    //類屬性
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};
  • const char *name;: 當(dāng)前類的名稱
  • classref_t cls;:需要擴(kuò)展的類對(duì)象居暖,在編譯期沒(méi)有值
  • WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;:實(shí)例方法列表
  • WrappedPtr<method_list_t, method_list_t::Ptrauth> classMethods;:對(duì)象方法列表
  • struct protocol_list_t *protocols;:協(xié)議列表
  • struct property_list_t *instanceProperties;:實(shí)例屬性
  • struct property_list_t *_classProperties;:類屬性

4.2 Category在編譯時(shí)期做了什么

#import "Person+A.h"

@implementation Person (A)

- (void)funcA {
    NSLog(@"A");
}
@end

首先我們創(chuàng)建這樣一個(gè)分類顽频,下面我們通過(guò)clang看下在編譯時(shí)期分類到底做了些什么

clang -rewrite-objc "文件名.m/h"

我們可以通過(guò)上面這個(gè)命令在終端中獲取到.cpp文件

static struct _category_t _OBJC_$_CATEGORY_Person_$_A __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_A,
    0,
    0,
    0,
};

我們編譯后的東西都存放在Mach-o的可執(zhí)行文件中,通過(guò)字段標(biāo)識(shí)當(dāng)前內(nèi)容太闺。上述內(nèi)容就是存在__DATA中的__objc_const的字段中

  • Person ->name字段 類的名稱
  • 0, // &OBJC_CLASS_$_Person, ->cls 需要擴(kuò)展的類對(duì)象糯景,在編譯期沒(méi)有值
  • (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_A, -> instanceMethods 在分類中聲明的實(shí)例方法

我們?cè)賮?lái)看下實(shí)例方法列表這個(gè)結(jié)構(gòu)體里有些啥

 _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_A __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"funcA", "v16@0:8", (void *)_I_Person_A_funcA}}
};
  • funcA方法編號(hào)
  • v16@0:8方法簽名
  • (void *)_I_Person_A_funcA 真實(shí)的函數(shù)地址
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_Person_$_A,
};

將編譯期生成的分類結(jié)構(gòu)體存放在__DATA中的__objc_catlist字段中


總結(jié) Category在編譯期做了什么

  • 1.把分類編譯成結(jié)構(gòu)體
  • 2.在對(duì)應(yīng)的字段中填充相應(yīng)的數(shù)據(jù)
  • 3.把所有的分類存放在__DATA數(shù)據(jù)段中的__objc_catlist字段中

4.3 Category在運(yùn)行時(shí)期做了什么

要想探索Category在運(yùn)行時(shí)期做了什么,首先我們來(lái)倒app的入口函數(shù)_objc_init

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    //環(huán)境變量初始化
    environ_init();
    //線程寄存器初始化
    tls_init();
    //靜態(tài)初始化
    static_init();
    //runtime初始化
    runtime_init();
    //異常初始化
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

首先進(jìn)行了一些初始化的操作省骂,environ_init (),tls_init (),static_init (),runtime_init (),exception_init.

然后調(diào)用_dyld_objc_notify_register(&map_images, load_images, unmap_image);函數(shù)蟀淮,通過(guò)dyld注冊(cè)三個(gè)函數(shù):map_images(map映射,當(dāng)前dyld把images(鏡像)加載進(jìn)內(nèi)存時(shí)钞澳,會(huì)出發(fā)map_images的回調(diào)), load_images(當(dāng)dyld初始化images(鏡像)模塊時(shí)調(diào)用怠惶,+load方法也會(huì)在這里被調(diào)用), unmap_image(dyld把image移除內(nèi)存時(shí)調(diào)用)

這里我們重點(diǎn)關(guān)注map_images

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    //頭文件信息
    header_info *hList[mhCount];
    //頭文件數(shù)
    uint32_t hCount;
    size_t selrefCount = 0; 
    // Find all images with Objective-C metadata.
    hCount = 0;
    // Count classes. Size various table based on the total.
    //類的數(shù)量
    int totalClasses = 0;
    if (hCount > 0) { //讀取鏡像文件,讀取OC相關(guān)的Section
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    } 
}

由于map_images_nolock函數(shù)中代碼太多轧粟,截取了部分關(guān)鍵代碼

_read_images函數(shù)中做了什么

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    header_info *hi;
    uint32_t hIndex;
    
#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++

    if (didInitialAttachCategories) {
        //遍歷整個(gè)頭文件列表
        for (EACH_HEADER) { 
            //加載分類
            load_categories_nolock(hi);
        }
    }
}

由于_read_images函數(shù)中代碼太多策治,截取了部分關(guān)鍵代碼

  • EACH_HEADER是一個(gè)宏定義,用來(lái)遍歷整個(gè)頭文件列表
  • load_categories_nolock加載分類

接著往下看 load_categories_nolock

static void load_categories_nolock(header_info *hi) {
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    size_t count;
    auto processCatlist = [&](category_t * const *catlist) {
        for (unsigned i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
            locstamped_category_t lc{cat, hi};

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Ignore the category.
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class",
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category.
            if (cls->isStubClass()) {
                // Stub classes are never realized. Stub classes
                // don't know their metaclass until they're
                // initialized, so we have to add categories with
                // class methods or properties to the stub itself.
                // methodizeClass() will find them and add them to
                // the metaclass as appropriate.
                if (cat->instanceMethods ||
                    cat->protocols ||
                    cat->instanceProperties ||
                    cat->classMethods ||
                    cat->protocols ||
                    (hasClassProperties && cat->_classProperties))
                {
                    objc::unattachedCategories.addForClass(lc, cls);
                }
            } else {
                // First, register the category with its target class.
                // Then, rebuild the class's method lists (etc) if
                // the class is realized.
                if (cat->instanceMethods ||  cat->protocols
                    ||  cat->instanceProperties)
                {
                    if (cls->isRealized()) {
                        attachCategories(cls, &lc, 1, ATTACH_EXISTING);
                    } else {
                        objc::unattachedCategories.addForClass(lc, cls);
                    }
                }

                if (cat->classMethods  ||  cat->protocols
                    ||  (hasClassProperties && cat->_classProperties))
                {
                    if (cls->ISA()->isRealized()) {
                        //把分類中聲明的屬性兰吟,方法添加到累的屬性通惫,方法列表中
                        attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                    } else {
                        //把當(dāng)前分類加載到對(duì)應(yīng)的類中
                        objc::unattachedCategories.addForClass(lc, cls->ISA());
                        //addForClass底層數(shù)據(jù)結(jié)構(gòu)的映射
                    }
                }
            }
        }
    };
    //讀取分類列表
    processCatlist(hi->catlist(&count));
    processCatlist(hi->catlist2(&count));
}

在這個(gè)函數(shù)中做了三件事

  • 1.processCatlist(hi->catlist(&count));,processCatlist(hi->catlist2(&count));讀取分類列表
    1. objc::unattachedCategories.addForClass(lc, cls->ISA());當(dāng)前分類加載到對(duì)應(yīng)的類中
    1. attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS); 把分類中聲明的屬性,方法揽祥,協(xié)議添加到類的屬性讽膏,方法,協(xié)議列表中

attachCategories函數(shù)的實(shí)現(xiàn)

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    //聲明 方法列表拄丰,屬性列表府树,協(xié)議列表所用的空間
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    if (mcount > 0) {
        //準(zhǔn)備當(dāng)前的方法列表
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        //把分類方法追加進(jìn)類的方法列表中
        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ù)中代碼太多,截取了部分關(guān)鍵代碼

  • 1.prepareMethodLists準(zhǔn)備當(dāng)前的方法列表料按,核心:把方法名稱放到方法列表中
  • 2.attachLists分類和本類相應(yīng)的對(duì)象方法奄侠,屬性,和協(xié)議進(jìn)行了合并载矿。

attachLists內(nèi)部實(shí)現(xiàn)

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

        if (hasArray()) {
            // many lists -> many lists
            //多對(duì)多
            uint32_t oldCount = array()->count; //獲取原來(lái)方法列表中的方法個(gè)數(shù)
            uint32_t newCount = oldCount + addedCount; //得到添加后的方法個(gè)數(shù)
            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]; //把原來(lái)的方法放在后面
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i]; //把新添加的方法放在數(shù)組的前面
            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();
        }
    }

這里會(huì)分三種情況:多對(duì)多垄潮,0對(duì)1烹卒,1對(duì)多,這里就以多對(duì)多為例

  • uint32_t oldCount = array()->count;獲取原來(lái)方法列表中的方法個(gè)數(shù)
  • uint32_t newCount = oldCount + addedCount得到添加后的方法個(gè)數(shù)
  • array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));開辟新的空間
  • newArray->lists[i + addedCount] = array()->lists[i];把原來(lái)的方法放在后面
  • newArray->lists[i] = addedLists[i] 把新添加的方法放在數(shù)組的前面

總結(jié)Category在運(yùn)行時(shí)做了什么
1.通過(guò)catlist,catlist2 (_getObjc2CategoryList)讀取分類列表
2.通過(guò)addForClass當(dāng)前分類加載到對(duì)應(yīng)的類中
3.attachCategories把分類中聲明的屬性弯洗,方法旅急,協(xié)議添加到類的屬性,方法牡整,協(xié)議列表中
4.prepareMethodLists準(zhǔn)備當(dāng)前的方法列表藐吮,核心:把方法名稱放到方法列表中
5.attachLists分類和本類相應(yīng)的對(duì)象方法,屬性逃贝,和協(xié)議進(jìn)行了合并
6.合并的順序谣辞,先為分類中的方法,再是原類中的方法(這就是同名方法分類會(huì)優(yōu)先于本類調(diào)用的原因)

多個(gè)分類/類也有同名方法沐扳,方法查找泥从,本質(zhì)上調(diào)用Runtime遍歷方法列表,通過(guò)二分查找算法沪摄,不會(huì)立馬返回imp指針躯嫉,往前移動(dòng),找是否有同名方法杨拐。

關(guān)聯(lián)對(duì)象的探索

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end

在類中聲明一個(gè)屬性和敬,系統(tǒng)會(huì)自動(dòng)幫我們生成getter setter ivar.

當(dāng)我們使用分類想為原類添加屬性時(shí),我們則需要使用關(guān)聯(lián)對(duì)象的方式戏阅,那么關(guān)聯(lián)對(duì)象和直接聲明數(shù)據(jù)原理是否一樣呢,我們接下來(lái)分析下

分類中我們使用objc_setAssociatedObject,objc_getAssociatedObject這兩函數(shù)添加屬性

首先我們來(lái)看下關(guān)聯(lián)對(duì)象怎么存的objc_setAssociatedObject

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if (!object && !value) return;

    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));

    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    ObjcAssociation association{policy, value}; //把外部傳入的value和策略存起來(lái)

    // retain the new value (if any) outside the lock.
    //根據(jù)上面存的policy啤它,設(shè)置內(nèi)存管理語(yǔ)義
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        AssociationsManager manager; //聲明一個(gè)管理者奕筐,用來(lái)管理下面的AssociationsHashMap
        AssociationsHashMap &associations(manager.get());

        if (value) {
            //<first, second> -> <disguised, ObjectAssociationMap{}>
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                isFirstAssociation = true;
            }

            /* establish or replace the association */
            auto &refs = refs_result.first->second;
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else {
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }

    // Call setHasAssociatedObjects outside the lock, since this
    // will call the object's _noteAssociatedObjects method if it
    // has one, and this may trigger +initialize which might do
    // arbitrary stuff, including setting more associated objects.
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // release the old value (outside of the lock).
    association.releaseHeldValue();
}
  • DisguisedPtr<objc_object> disguised{(objc_object *)object}按位取反
  • ObjcAssociation association{policy, value};把外部傳入的value和策略存起來(lái)
  • association.acquireValue();根據(jù)上面存的policy,設(shè)置內(nèi)存管理語(yǔ)義,copy, retain
  • AssociationsManager manager;聲明一個(gè)管理者变骡,用來(lái)管理下面的AssociationsHashMap
  • AssociationsHashMap &associations(manager.get());初始化HashMap
  • object->setHasAssociatedObjects();與關(guān)聯(lián)對(duì)象做綁定离赫,方便釋放

根據(jù)上面的代碼我們就明白了關(guān)聯(lián)對(duì)象是怎么存對(duì)象的了
AssociationsManager -> <disguised , ObjectAssociationMap >
ObjectAssociationMap -> <key(外部傳入的), ObjcAssociation>
ObjcAssociation -> 有倆屬性 外部傳入的uintptr_t _policy,id _value

objc_getAssociatedObject獲取關(guān)聯(lián)對(duì)象實(shí)現(xiàn)

id
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            ObjectAssociationMap &refs = i->second;
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                association = j->second;
                association.retainReturnedValue();
            }
        }
    }

    return association.autoreleaseReturnedValue();
}

我們探索了objc_setAssociatedObject的實(shí)現(xiàn),objc_getAssociatedObject的實(shí)現(xiàn)就一幕了然了
通過(guò)AssociationsManager獲取到AssociationsHashMap,再通過(guò)獲取的AssociationsHashMap得到association塌碌。

下面我們?cè)賮?lái)看下關(guān)聯(lián)對(duì)像的釋放

- (void)dealloc {
    _objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);

    obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer                     &&
                 !isa.weakly_referenced             &&
                 !isa.has_assoc                     &&
#if ISA_HAS_CXX_DTOR_BIT
                 !isa.has_cxx_dtor                  &&
#else
                 !isa.getClass(false)->hasCxxDtor() &&
#endif
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
        obj->clearDeallocating();
    }

    return obj;
}


/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

delloac -> _objc_rootDealloc -> rootDealloc (首先判斷是否有關(guān)聯(lián)對(duì)象等渊胸,如果沒(méi)有則直接釋放)如果沒(méi)有 -> object_dispose -> objc_destructInstance -> _object_remove_assocations(釋放關(guān)聯(lián)對(duì)象)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市台妆,隨后出現(xiàn)的幾起案子翎猛,更是在濱河造成了極大的恐慌,老刑警劉巖接剩,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件切厘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡懊缺,警方通過(guò)查閱死者的電腦和手機(jī)疫稿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人遗座,你說(shuō)我怎么就攤上這事舀凛。” “怎么了途蒋?”我有些...
    開封第一講書人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵猛遍,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我碎绎,道長(zhǎng)螃壤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任筋帖,我火速辦了婚禮奸晴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘日麸。我一直安慰自己寄啼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開白布代箭。 她就那樣靜靜地躺著墩划,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嗡综。 梳的紋絲不亂的頭發(fā)上乙帮,一...
    開封第一講書人閱讀 49,850評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音极景,去河邊找鬼察净。 笑死,一個(gè)胖子當(dāng)著我的面吹牛盼樟,可吹牛的內(nèi)容都是我干的氢卡。 我是一名探鬼主播,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼晨缴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼译秦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起击碗,我...
    開封第一講書人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤筑悴,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后稍途,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雷猪,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年晰房,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了求摇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片射沟。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖与境,靈堂內(nèi)的尸體忽然破棺而出验夯,到底是詐尸還是另有隱情,我是刑警寧澤摔刁,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布挥转,位于F島的核電站,受9級(jí)特大地震影響共屈,放射性物質(zhì)發(fā)生泄漏绑谣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一拗引、第九天 我趴在偏房一處隱蔽的房頂上張望借宵。 院中可真熱鬧,春花似錦矾削、人聲如沸壤玫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)欲间。三九已至,卻和暖如春断部,著一層夾襖步出監(jiān)牢的瞬間猎贴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工蝴光, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘱能,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓虱疏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親苏携。 傳聞我的和親對(duì)象是個(gè)殘疾皇子做瞪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349

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