Objective-C 小記(10)__weak

本文使用的 runtime 版本為 objc4-706侣诺。

__weak 修飾的指針最重要的特性是其指向的對象銷毀后祠饺,會自動置為 nil诅岩,這個特性的實現(xiàn)完全是依靠運行時的声离。實現(xiàn)思路是非常簡單的章办,對于下面的語句來說:

id __weak weakObj = strongObj;

便是用 strongObj 當作 key锉走,weakObj 當作 value 存入一個表里。當 strongObj 銷毀時藕届,從表里找到所有的 __weak 引用挪蹭,將其置為 nil

當然休偶,實際的實現(xiàn)肯定是要比這要充斥著更多的細節(jié)梁厉。

變量的創(chuàng)建和銷毀

還是上面那個例子,實際上編譯器會進行一些變動:

{
    id __weak weakObj = strongObj;
}
// 會變成
{
    id __weak weakObj;
    objc_initWeak(&weakObj, strongObj);
    
    // 離開變量的范圍踏兜,進行銷毀
    objc_destroyWeak(&weakObj);
}

objc_initWeakobjc_destroyWeak 都可以在 NSObject.mm 文件中找到:

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}

void
objc_destroyWeak(id *location)
{
    (void)storeWeak<true/*old*/, false/*new*/, false/*crash*/>
        (location, nil);
}

可以看到都是對 storeWeak 函數(shù)模板的調用(為什么要使用模板呢词顾?會更快嗎?C++ 小白內心的問題…… )碱妆。

賦值

當已有的 __weak 變量被重新賦值時會怎么樣呢计技?

weakObj = anotherStrongObj;

// 會變成下面這樣
objc_storeWeak(&weakObj, anotherStrongObj);

它的實現(xiàn)如下:

id
objc_storeWeak(id *location, id newObj)
{
    return storeWeak<true/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object *)newObj);
}

但實際上也還是對 storeWeak 函數(shù)模板的封裝。

storeWeak

storeWeak 的實現(xiàn)還是有點長的山橄,一點一點來分析:

// Update a weak variable.
// If HaveOld is true, the variable has an existing value 
//   that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be 
//   assigned into the variable. This value might be nil.
// If CrashIfDeallocating is true, the process is halted if newObj is 
//   deallocating or newObj's class does not support weak references. 
//   If CrashIfDeallocating is false, nil is stored instead.
template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
    assert(HaveOld  ||  HaveNew);
    if (!HaveNew) assert(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

函數(shù)前的注釋表明了三個模板參數(shù)的作用垮媒,當然在后面的代碼里也能直觀的看到。函數(shù)一開始進行了變量的聲明航棱,可以注意到 SideTable 這個類型睡雇,SideTable 是現(xiàn)在的運行時中用來存放引用計數(shù)和弱引用的結構體,它的結構是這樣的(省略了結構體函數(shù)):

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
}

其中 slock 是一個自旋鎖饮醇,用來對 SideTable 實例進行操作時的加鎖它抱。refcnts 則是存放引用計數(shù)的地方。weak_table 則是存放弱引用的地方(后面將詳細分析 weak_table_t)朴艰。

回到 storeWeak 函數(shù):

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (HaveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (HaveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);

    if (HaveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
        goto retry;
    }

這一段即獲取 oldObj观蓄、oldTablenewTable,并將獲取的兩個表上鎖祠墅。注意到獲取 oldTablenewTable 時侮穿,其實是用對象的地址當作 key 從 SideTables 獲取的,SideTables 返回的就是一個哈希表毁嗦,存儲著若干個 SideTable亲茅,一般是 64 個。

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (HaveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

上面這一段代碼也有著很好的注釋,就是要確保對象的類已經走過 +initialize 流程了克锣。

    // Clean up old value, if any.
    if (HaveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (HaveNew) {
        newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, 
                                                      (id)newObj, location, 
                                                      CrashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);

    return (id)newObj;
}

最后一段的邏輯也是很清晰的茵肃。首先,如果有舊的值(HaveOld)袭祟,則使用 weak_unregister_no_lock 函數(shù)將其從 oldTableweak_table 中移除验残。其次,如果有新的值(HaveNew)巾乳,則使用 weak_register_no_lock 函數(shù)將其注冊到 newTableweak_table 中胚膊,并使用 setWeaklyReferenced_nolock 函數(shù)將對象標記為被弱引用過。

storeWeak 的實現(xiàn)就告一段落了想鹰,其重點就在 weak_register_no_lockweak_unregister_no_lock 函數(shù)上紊婉。

weak_table_t

在分析這兩個函數(shù)之前,先看看 weak_table_t 是一個怎么樣的結構:

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};
  • weak_entries 便是存放弱引用的數(shù)組辑舷;
  • num_entries 是存放的 weak_entry_t 條目的數(shù)量喻犁;
  • mask 則是動態(tài)申請的弱引用數(shù)組 weak_entries 長度減 1 的值,用來對哈希后的值取余和記錄數(shù)組大泻位骸肢础;
  • max_hash_displacement 則是哈希碰撞后最大的位移值。

其實 weak_table_t 就是一個動態(tài)增長的哈希表碌廓。

繼續(xù)看看其相關的操作传轰,首先是對整個表的擴大:

#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)

// Grow the given zone's table of weak references if it is full.
static void weak_grow_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // Grow if at least 3/4 full.
    if (weak_table->num_entries >= old_size * 3 / 4) {
        weak_resize(weak_table, old_size ? old_size*2 : 64);
    }
}

可以看到,當 weak_table 里的弱引用條目達到它容量的四分之三時谷婆,便會將容量拓展為兩倍慨蛙。值得注意的是第一次拓展也就是是 mask 為 0 的情況,初始值是 64纪挎。實際對弱引用表大小的操作則交給了 weak_resize 函數(shù)期贫。

除了擴大,當然也還有縮幸彀馈:

// Shrink the table if it is mostly empty.
static void weak_compact_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // Shrink if larger than 1024 buckets and at most 1/16 full.
    if (old_size >= 1024  && old_size / 16 >= weak_table->num_entries) {
        weak_resize(weak_table, old_size / 8);
        // leaves new table no more than 1/2 full
    }
}

縮小的話則是需要表本身大于等于 1024 并且存放了不足十六分之一的條目時通砍,直接縮小 8 倍。實際工作也是交給了 weak_resize 函數(shù):

static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
    size_t old_size = TABLE_SIZE(weak_table);

    weak_entry_t *old_entries = weak_table->weak_entries;
    weak_entry_t *new_entries = (weak_entry_t *)
        calloc(new_size, sizeof(weak_entry_t));

    weak_table->mask = new_size - 1;
    weak_table->weak_entries = new_entries;
    weak_table->max_hash_displacement = 0;
    weak_table->num_entries = 0;  // restored by weak_entry_insert below
    
    if (old_entries) {
        weak_entry_t *entry;
        weak_entry_t *end = old_entries + old_size;
        for (entry = old_entries; entry < end; entry++) {
            if (entry->referent) {
                weak_entry_insert(weak_table, entry);
            }
        }
        free(old_entries);
    }
}

weak_resize 函數(shù)的過程就是新建一個數(shù)組烤蜕,將老數(shù)組里的值使用 weak_entry_insert 函數(shù)添加進去封孙,注意到代碼中間 mask 在這里被賦值為新數(shù)組的大小減去 1,max_hash_displacementnum_entries 也都清零了讽营,因為 weak_entry_insert 函數(shù)會對這兩個值進行操作虎忌。接著對 weak_entry_insert 函數(shù)進行分析:

/** 
 * Add new_entry to the object's table of weak references.
 * Does not check whether the referent is already in the table.
 */
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
    weak_entry_t *weak_entries = weak_table->weak_entries;
    assert(weak_entries != nil);

    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_entries[index].referent != nil) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_entries);
        hash_displacement++;
    }

    weak_entries[index] = *new_entry;
    weak_table->num_entries++;

    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}

這個函數(shù)就是個很正常的哈希表插入的過程,hash_pointer 函數(shù)是對指針地址進行哈希斑匪,哈希后的值之所以要和 mask 進行 & 操作呐籽,是因為弱引用表的大小永遠是 2 的冪(一開始是 64,之后不斷乘以 2)蚀瘸,mask 則是大小減去 1 即為一個 0b111...11 這么一個數(shù)狡蝶,和它進行 & 運算相當于取余。hash_displacement 則是記錄了哈希相撞后偏移的大小贮勃。

既然有插入贪惹,也就有刪除:

/**
 * Remove entry from the zone's table of weak references.
 */
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // remove entry
    if (entry->out_of_line()) free(entry->referrers);
    bzero(entry, sizeof(*entry));

    weak_table->num_entries--;

    weak_compact_maybe(weak_table);
}

很直接的清零 entry,并給 weak_tablenum_entries 減 1寂嘉,最后檢查看是否需要縮小奏瞬。

最后還有一個根據(jù)指定對象查找存在條目的函數(shù):

/** 
 * Return the weak reference table entry for the given referent. 
 * If there is no entry for referent, return NULL. 
 * Performs a lookup.
 *
 * @param weak_table 
 * @param referent The object. Must not be nil.
 * 
 * @return The table of weak referrers to this object. 
 */
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}

也是很正常的哈希表套路。

weak_entry_t

那弱引用是怎么存儲的呢泉孩,繼續(xù)分析 weak_entry_t

#define WEAK_INLINE_COUNT 4

#define REFERRERS_OUT_OF_LINE 2

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

首先 DisguisedPtr<T> 類型和 T* 的行為是一模一樣的硼端,這個類型存在的目的是為了躲過內存泄漏工具的檢查(注釋原文:「DisguisedPtr<T> acts like pointer type T*, except the stored value is disguised to hide it from tools like leaks.」)。所以 DisguisedPtr<objc_object> referent 可以看作是 objc_object *referent寓搬。

referent 這個指針記錄的便是被弱引用的對象珍昨。接下來的聯(lián)合里有兩種結構體,先分析第一種:

  • referrersreferrers 是一個 weak_referrer_t 類型的數(shù)組句喷,用來存放弱引用變量的地址镣典,weak_referrer_t 的定義是這樣的:typedef DisguisedPtr<objc_object *> weak_referrer_t;
  • out_of_line_ness:2 bit 標記位唾琼,用來確定聯(lián)合里的內存是第一個結構體還是第二個結構體兄春;
  • num_refsPTR_MINUS_2 便是字長減去 2 位,和 out_of_line_ness 一起組成一個字長锡溯,用來存儲 referrers 的大懈嫌摺;
  • maskmax_hash_displacement:和前面分析的一樣祭饭,做哈希表用到的東西涌乳。

可以發(fā)現(xiàn)第一種結構體也是一個哈希表,第二種結構體則是一個和第一種結構體一樣大的數(shù)組甜癞,所謂的 inline 存儲夕晓。存放思路則是首先 inline 存儲,當超過 WEAK_INLINE_COUNT 也就是 4 時悠咱,再變成第一種的動態(tài)哈希表存儲蒸辆。代碼下方的構造函數(shù)便體現(xiàn)了這個思路。

可以注意到 weak_entry_t 重載了賦值操作符析既,將賦值變成了一個拷貝內存的操作躬贡。

相關操作也是和上面 weak_table_t 的類似,只不過加上了 inline 存儲情況的變化眼坏,就不詳細分析了拂玻。

weak_register_no_lock

開始分析 weak_register_no_lock 函數(shù):

/** 
 * Registers a new (object, weak pointer) pair. Creates a new weak
 * object entry if it does not exist.
 * 
 * @param weak_table The global weak table.
 * @param referent The object pointed to by the weak reference.
 * @param referrer The weak pointer address.
 */
id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

第一段,約等于什么都沒干。referent 是被弱引用的對象檐蚜,referrer 則是弱引用變量的地址魄懂。

    // ensure that the referenced object is viable
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }

這一段很有意思,如果對象沒有自定義的內存管理方法(hasCustomRR)闯第,則將 deallocating 變量賦值為 rootIsDeallocating 也就是是否正在銷毀市栗。但是如果有自定義的內存管理方法的話,發(fā)送的是
allowsWeakReference 這個消息咳短,即是否允許弱引用填帽。不管怎么樣,我們得到了一個 deallocating 變量咙好。

    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }

從上面一段可以知道篡腌,deallocatingtrue 的話肯定是有問題的,所以這一段處理一下勾效。

    // now remember it and where it is being stored
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

最后一段終于做了正事了哀蘑!首先先用 weak_entry_for_referent 函數(shù)搜索對象是否已經有了 weak_entry_t 類型的條目,有的話則使用 append_referrer 添加一個變量位置進去葵第,沒有的話則新建一個 weak_entry_t 條目绘迁,使用 weak_grow_maybe 函數(shù)擴大(如果需要的話)弱引用表的大小,并使用 weak_entry_insert 將弱引用插入表中卒密。

weak_unregister_no_lock

接下來是 weak_unregister_no_lock 函數(shù):

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return;

    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        remove_referrer(entry, referrer);
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }

        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }

    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}

主要功能實現(xiàn)思路很簡單缀台,使用 weak_entry_for_referent 函數(shù)找到對應的弱引用條目,并用 remove_referrer 將對應的弱引用變量位置從中移除哮奇。最后判斷條目是否為空膛腐,為空則使用 weak_entry_remove 將其從弱引用表中移除。

自動置為 nil

對象銷毀后鼎俘,弱引用變量被置為 nil 是因為在對象 dealloc 的過程中調用了 weak_clear_no_lock 函數(shù):

/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated. 
 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

首先初始化一下哲身,獲取到弱引用條目,順便處理沒有弱引用的情況贸伐。

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }

獲取弱引用變量位置數(shù)組和個數(shù)勘天。

    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry);
}

循環(huán)將它們置為 nil,最后移除整個弱引用條目捉邢。

訪問弱引用

在訪問一個弱引用時脯丝,ARC 會對其進行一些操作:

obj = weakObj;

// 會變成
objc_loadWeakRetained(&weakObj);
obj = weakObj;
objc_release(weakObj);

objc_loadWeakRetained 函數(shù)的主要作用就是調用了 rootTryRetain 函數(shù):

ALWAYS_INLINE bool 
objc_object::rootTryRetain()
{
    return rootRetain(true, false) ? true : false;
}

實際上就是嘗試對引用計數(shù)加 1,讓弱引用對象在使用時不會被釋放掉伏伐。

有關 rootRetain 的實現(xiàn):《Objective-C 小記(7)retain & release》

總結

存放一個弱引用還真是哈希了很多次:

  1. SideTable 哈希一次宠进,這里分開來應該是為了性能原因;
  2. weak_table_t 哈希一次藐翎;
  3. weak_entry_t 哈希一次材蹬。

對于開銷实幕,直觀感受上也并沒有什么很大開銷,想用就用唄……

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末堤器,一起剝皮案震驚了整個濱河市昆庇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吼旧,老刑警劉巖凰锡,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件未舟,死亡現(xiàn)場離奇詭異圈暗,居然都是意外死亡,警方通過查閱死者的電腦和手機裕膀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門员串,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人昼扛,你說我怎么就攤上這事寸齐。” “怎么了抄谐?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵渺鹦,是天一觀的道長。 經常有香客問我蛹含,道長毅厚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任浦箱,我火速辦了婚禮吸耿,結果婚禮上,老公的妹妹穿的比我還像新娘酷窥。我一直安慰自己咽安,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布蓬推。 她就那樣靜靜地躺著妆棒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪沸伏。 梳的紋絲不亂的頭發(fā)上募逞,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音馋评,去河邊找鬼放接。 笑死,一個胖子當著我的面吹牛留特,可吹牛的內容都是我干的纠脾。 我是一名探鬼主播玛瘸,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼苟蹈!你這毒婦竟也來了糊渊?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤慧脱,失蹤者是張志新(化名)和其女友劉穎渺绒,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體菱鸥,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡宗兼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了氮采。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殷绍。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鹊漠,靈堂內的尸體忽然破棺而出主到,到底是詐尸還是另有隱情,我是刑警寧澤躯概,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布登钥,位于F島的核電站,受9級特大地震影響娶靡,放射性物質發(fā)生泄漏牧牢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一固蛾、第九天 我趴在偏房一處隱蔽的房頂上張望结执。 院中可真熱鬧,春花似錦艾凯、人聲如沸献幔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜡感。三九已至,卻和暖如春恃泪,著一層夾襖步出監(jiān)牢的瞬間郑兴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工贝乎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留情连,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓览效,卻偏偏與公主長得像却舀,于是被迫代替她去往敵國和親虫几。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容