關(guān)聯(lián)對(duì)象底層原理探究

通常我們會(huì)在分類(lèi)中添加方法,而無(wú)法在在分類(lèi)中添加屬性,我們?cè)诜诸?lèi)中添加@property(nonatomic, copy) NSString *name;時(shí)編譯器并不會(huì)在編譯時(shí)幫我們自動(dòng)生成setter和getter方法凶赁,也不會(huì)生成”_屬性名“的成員變量。

但我們可以通過(guò)關(guān)聯(lián)對(duì)象技術(shù)給類(lèi)添加屬性。例如,我們要給Animal類(lèi)添加一個(gè)name屬性备韧,可以這么實(shí)現(xiàn):

@interface Animal (Cate)

@property(nonatomic, copy) NSString *name;

@end

@implementation Animal (Cate)

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, "name");
}

@end

關(guān)聯(lián)對(duì)象技術(shù)我們使用了使用了兩個(gè)api -- objc_setAssociatedObjectobjc_getAssociatedObject,它的底層是如何實(shí)現(xiàn)的呢痪枫,我們可以通過(guò)objc-7.8.1探究织堂。

objc_setAssociatedObject

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    SetAssocHook.get()(object, key, value, policy);
}

SetAssocHook是一個(gè)封裝了一個(gè)函數(shù)指針的對(duì)象,他在源碼里是這么定義的

static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};

關(guān)于ChainedHookFunction奶陈,點(diǎn)進(jìn)去查看它的實(shí)現(xiàn)

// Storage for a thread-safe chained hook function.
// get() returns the value for calling.
// set() installs a new function and returns the old one for chaining.
// More precisely, set() writes the old value to a variable supplied by
// the caller. get() and set() use appropriate barriers so that the
// old value is safely written to the variable before the new value is
// called to use it.
//
// T1: store to old variable; store-release to hook variable
// T2: load-acquire from hook variable; call it; called hook loads old variable

template <typename Fn>
class ChainedHookFunction {
    std::atomic<Fn> hook{nil};

public:
    ChainedHookFunction(Fn f) : hook{f} { };

    Fn get() {
        return hook.load(std::memory_order_acquire);
    }

    void set(Fn newValue, Fn *oldVariable)
    {
        Fn oldValue = hook.load(std::memory_order_relaxed);
        do {
            *oldVariable = oldValue;
        } while (!hook.compare_exchange_weak(oldValue, newValue,
                                             std::memory_order_release,
                                             std::memory_order_relaxed));
    }
};

通過(guò)它的注釋可以了解到捧挺,ChainedHookFunction是用于線程安全鏈構(gòu)函數(shù)的存儲(chǔ),通過(guò)get()返回調(diào)用值尿瞭,通過(guò)set()安裝一個(gè)新的函數(shù),并返回舊函數(shù)翅睛,更確切的說(shuō)声搁,set()將舊值寫(xiě)入調(diào)用方提供的變量,get()set()使用適當(dāng)?shù)臇艡谑沟迷谛轮嫡{(diào)用前安全地寫(xiě)入變量捕发。

所以疏旨,SetAssocHook.get()返回的是傳入的函數(shù)指針_base_objc_setAssociatedObjectobjc_setAssociatedObject底層調(diào)用的其實(shí)是_base_objc_setAssociatedObject扎酷,我們也可以通過(guò)符號(hào)斷點(diǎn)驗(yàn)證調(diào)用的是否正確檐涝,這里就不做演示。

進(jìn)入_base_objc_setAssociatedObject的實(shí)現(xiàn)查看法挨,它的底層調(diào)用了_object_set_associative_reference谁榜。

static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
  _object_set_associative_reference(object, key, value, policy);
}

_object_set_associative_reference中便有關(guān)聯(lián)對(duì)象實(shí)現(xiàn)的具體代碼

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};

    // retain the new value (if any) outside the lock.
    association.acquireValue();

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());

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

            /* 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);

                    }
                }
            }
        }
    }

    // release the old value (outside of the lock).
    association.releaseHeldValue();
}

通過(guò)閱讀源碼,關(guān)聯(lián)對(duì)象設(shè)值流程為:

  1. 將被關(guān)聯(lián)對(duì)象封裝成DisguisedPtr類(lèi)型凡纳,將策略和關(guān)聯(lián)值封裝成ObjcAssociation類(lèi)型窃植,并根據(jù)策略處理關(guān)聯(lián)值。
  2. 創(chuàng)建一個(gè)AssociationsManager管理類(lèi)
  3. 獲取唯一的全局靜態(tài)哈希Map
  4. 判斷是否插入的關(guān)聯(lián)值是否存在荐糜,如果存在走第4步巷怜,如果不存在則執(zhí)行關(guān)聯(lián)對(duì)象插入空流程
  5. 創(chuàng)建一個(gè)空的ObjectAssociationMap去取查詢(xún)的鍵值對(duì)
  6. 如果發(fā)現(xiàn)沒(méi)有這個(gè)key就插入一個(gè)空的BucketT進(jìn)去返回
  7. 標(biāo)記對(duì)象存在關(guān)聯(lián)對(duì)象
  8. 用當(dāng)前修飾策略和值組成了一個(gè)ObjcAssociation替換原來(lái)BucketT中的空
  9. 標(biāo)記一下ObjectAssociation的第一次為false

關(guān)聯(lián)對(duì)象插入空流程為:

  1. 根據(jù)DisguisedPtr找到AssociationsHashMap中的迭代查詢(xún)器、
  2. 清理迭代器
  3. 插入空值(相當(dāng)于清除)

處理傳入變量

DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value};

// retain the new value (if any) outside the lock.
association.acquireValue();

這部分代碼比較簡(jiǎn)單暴氏,可以進(jìn)入association.acquireValue看看關(guān)聯(lián)值是如何處理的延塑。

inline void acquireValue() {
    if (_value) {
        switch (_policy & 0xFF) {
        case OBJC_ASSOCIATION_SETTER_RETAIN:
            _value = objc_retain(_value);
            break;
        case OBJC_ASSOCIATION_SETTER_COPY:
            _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
            break;
        }
    }
}

這份代碼中,如果_policyOBJC_ASSOCIATION_SETTER_RETAIN答渔,則對(duì)關(guān)聯(lián)值進(jìn)行retain操作关带,如果是OBJC_ASSOCIATION_SETTER_COPY,則對(duì)關(guān)聯(lián)值進(jìn)行copy操作研儒,其他的則不做任何處理豫缨。

創(chuàng)建哈希表管理類(lèi)獲取全局哈希表

class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;

public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }

    AssociationsHashMap &get() {
        return _mapStorage.get();
    }

    static void init() {
        _mapStorage.init();
    }
};

AssociationsManager的構(gòu)造方法里独令,通過(guò)AssociationsManagerLock.lock加了一把鎖,在當(dāng)前AssociationsManager釋放之前好芭,后續(xù)創(chuàng)建的AssociationsManager都無(wú)法對(duì)其管理的資源進(jìn)行操作燃箭,從而保證了線程安全,在通過(guò)get()拿到全局唯一的哈希表(因?yàn)?code>_mapStorage是static修飾的)

關(guān)聯(lián)非空值流程

當(dāng)要關(guān)聯(lián)的值非空時(shí)舍败,我們需要將這個(gè)值與當(dāng)前對(duì)象關(guān)聯(lián)起來(lái)招狸,這一部分代碼為

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

/* 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);
}

通過(guò)源碼,一步步分析它的原理

分析 try_emplace 實(shí)現(xiàn)流程

try_emplaceDenseMap類(lèi)的一個(gè)方法邻薯,這個(gè)方法在這個(gè)流程中被調(diào)用兩次裙戏,第一次調(diào)用的是全局關(guān)聯(lián)對(duì)象哈希表的try_emplace,傳了封裝了當(dāng)前對(duì)象的disguised作為key和一個(gè)空的ObjectAssociationMap作為第二個(gè)元素厕诡,第二次累榜,調(diào)用的第一次try_emplace得到的map,傳遞了關(guān)聯(lián)對(duì)象的key值作為key灵嫌,傳遞了封裝value和策略的association作為第二個(gè)參數(shù)壹罚。

try_emplace內(nèi)部到底做了什么事情呢,我們可以去源碼一探究竟寿羞。

// Inserts key,value pair into the map if the key isn't already in the map.
// The value is constructed in-place if the key is not in the map, otherwise
// it is not moved.
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
  return std::make_pair(
           makeIterator(TheBucket, getBucketsEnd(), true),
           false); // Already in map.

// Otherwise, insert the new element.
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
         makeIterator(TheBucket, getBucketsEnd(), true),
         true);
}

try_emplace具體流程分析如下:

  1. 創(chuàng)建一個(gè)空的BucketT猖凛,調(diào)用LookupBucketFor查找Key對(duì)應(yīng)的BucketT,如果能夠找到绪穆,將找到的BucketT賦值給剛剛創(chuàng)建的那個(gè)空的BucketT辨泳,并將BucketT封裝成DenseMapIterator作為類(lèi)對(duì)的第一個(gè)元素,將false作為第二個(gè)元素玖院,并將該類(lèi)對(duì)返回菠红,此時(shí)的BucketT存放的是上次存放的keyvalue
  2. 如果沒(méi)有找到,那么將傳入的Key和Value插入創(chuàng)建新的BucketT难菌,同樣創(chuàng)建一個(gè)DenseMapIteratorBool組成的類(lèi)對(duì)途乃,只不過(guò)此時(shí)傳遞布爾值為true,此時(shí)的BucketT已經(jīng)存放了傳入的keyvalue
LookupBucketFor

這個(gè)方法是在當(dāng)前DenseMap中通過(guò)key查找對(duì)應(yīng)的bucket扔傅,如果找到匹配的耍共,并且bucket包含key和value,則返回true猎塞,并將找到bucket通過(guò)FoundBucket返回试读,否則,返回false并通過(guò)FoundBucket返回一個(gè)空的bucket荠耽。

另外貼上代碼

/// LookupBucketFor - Lookup the appropriate bucket for Val, returning it in
/// FoundBucket.  If the bucket contains the key and a value, this returns
/// true, otherwise it returns a bucket with an empty marker or tombstone and
/// returns false.
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val,
                   const BucketT *&FoundBucket) const {
const BucketT *BucketsPtr = getBuckets();
const unsigned NumBuckets = getNumBuckets();

if (NumBuckets == 0) {
  FoundBucket = nullptr;
  return false;
}

// FoundTombstone - Keep track of whether we find a tombstone while probing.
const BucketT *FoundTombstone = nullptr;
const KeyT EmptyKey = getEmptyKey();
const KeyT TombstoneKey = getTombstoneKey();
assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
       !KeyInfoT::isEqual(Val, TombstoneKey) &&
       "Empty/Tombstone value shouldn't be inserted into map!");

unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
while (true) {
  const BucketT *ThisBucket = BucketsPtr + BucketNo;
  // Found Val's bucket?  If so, return it.
  if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
    FoundBucket = ThisBucket;
    return true;
  }

  // If we found an empty bucket, the key doesn't exist in the set.
  // Insert it and return the default value.
  if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
    // If we've already seen a tombstone while probing, fill it in instead
    // of the empty bucket we eventually probed to.
    FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
    return false;
  }

  // If this is a tombstone, remember it.  If Val ends up not in the map, we
  // prefer to return it than something that would require more probing.
  // Ditto for zero values.
  if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
      !FoundTombstone)
    FoundTombstone = ThisBucket;  // Remember the first tombstone found.
  if (ValueInfoT::isPurgeable(ThisBucket->getSecond())  &&  !FoundTombstone)
    FoundTombstone = ThisBucket;

  // Otherwise, it's a hash collision or a tombstone, continue quadratic
  // probing.
  if (ProbeAmt > NumBuckets) {
    FatalCorruptHashTables(BucketsPtr, NumBuckets);
  }
  BucketNo += ProbeAmt++;
  BucketNo &= (NumBuckets-1);
}
}

非空值流程流程分析

通過(guò)閱讀源碼钩骇,可以總結(jié)出他的流程為:

  1. 嘗試通過(guò)關(guān)聯(lián)對(duì)象全局hashMap的try_emplace方法找到當(dāng)前對(duì)象對(duì)應(yīng)的bucket,此時(shí)的bucketkey為當(dāng)前對(duì)象,value為一張ObjectAssociationMap倘屹,也是一張hashMap
  2. 如果查找返回的refs_result類(lèi)對(duì)第二個(gè)元素為true银亲,也就是說(shuō)當(dāng)前對(duì)象第一次有關(guān)聯(lián)對(duì)象,將當(dāng)前對(duì)象標(biāo)記為有關(guān)聯(lián)對(duì)象(修改isa)
  3. 通過(guò)refs_result.first->second拿到當(dāng)前對(duì)象對(duì)應(yīng)ObjectAssociationMap纽匙,調(diào)用這張ObjectAssociationMaptry_emplace找到關(guān)聯(lián)對(duì)象標(biāo)識(shí)符對(duì)應(yīng)的value务蝠,也就是valuepolicy組裝成的ObjcAssociation對(duì)象
  4. 如果第二個(gè)try_emplace方法返回的result的第二個(gè)元素為true說(shuō)明這是該對(duì)象第一次插入該標(biāo)識(shí)符的值,此時(shí)的valuepolicy已經(jīng)在try_emplace插入到ObjectAssociationMap烛缔,不需要進(jìn)一步處理
  5. 如果result.secondfalse馏段,說(shuō)明原先的對(duì)象的該標(biāo)識(shí)符對(duì)應(yīng)的關(guān)聯(lián)對(duì)象有值,調(diào)用association.swap(result.first->second)交換修改關(guān)聯(lián)對(duì)象(result.first->second存放的是valuepolicy組裝成的ObjcAssociation對(duì)象)

關(guān)聯(lián)空值流程

關(guān)聯(lián)空值其實(shí)就是刪除關(guān)聯(lián)對(duì)象践瓷,這部分代碼為:

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);

        }
    }
}

其實(shí)通過(guò)非空關(guān)鍵對(duì)象流程的探究對(duì)關(guān)聯(lián)對(duì)象原理了解院喜,這部分代碼就顯得簡(jiǎn)單很多。

  1. 以用當(dāng)前對(duì)象封裝的DisguisedPtr對(duì)象為key在全局關(guān)聯(lián)對(duì)象表associations中查找得到refs_it
  2. 如果refs_it不等于associations的最后一個(gè)元素晕翠,通過(guò)refs_it->second拿到當(dāng)前對(duì)象對(duì)象的ObjectAssociationMap對(duì)象refs喷舀,也就是存放標(biāo)識(shí)符和ObjcAssociation對(duì)象it(value和policy)的那張哈希表
  3. 通過(guò)標(biāo)識(shí)符找到ObjcAssociation
  4. 如果it不是refs最后一個(gè)元素,交換原有標(biāo)識(shí)符對(duì)應(yīng)的關(guān)聯(lián)對(duì)象淋肾,因?yàn)閭魅氲臑榭赵越粨Q后標(biāo)識(shí)符對(duì)應(yīng)的對(duì)象為空
  5. 調(diào)用refs.erase(it),刪除該標(biāo)識(shí)符對(duì)應(yīng)的bucket
  6. 如果此時(shí)對(duì)象對(duì)應(yīng)的ObjectAssociationMap大小為0巫员,則刪除該對(duì)象對(duì)應(yīng)的關(guān)聯(lián)表

objc_getAssociatedObject

objc_getAssociatedObject底層調(diào)用的是_object_get_associative_reference

id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}

_object_get_associative_reference

進(jìn)入_object_get_associative_reference

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();
}

這個(gè)部分代碼比較簡(jiǎn)單,大致可以分為以下幾個(gè)步驟:

  1. 創(chuàng)建AssociationsManager對(duì)象甲棍,通過(guò)AssociationsManager對(duì)象拿到全局唯一的關(guān)聯(lián)對(duì)象管理表
  2. 以對(duì)象為key在關(guān)聯(lián)對(duì)象表中查找對(duì)象的AssociationsHashMap::iterator
  3. 如果找到了简识,取到iterator的第二個(gè)元素,也就是ObjectAssociationMap感猛,用標(biāo)識(shí)符為key在這個(gè)ObjectAssociationMap查找
  4. 如果找到了七扰,取找到的refs的第二個(gè)元素,也就是set時(shí)存放的value(第一個(gè)為policy)陪白,返回
  5. 以上如果都沒(méi)有找到颈走,返回nil

總結(jié)

關(guān)聯(lián)對(duì)象其實(shí)使用了兩張hashMap,可以用一張圖解釋他的原理咱士。

ED603188-5741-48F5-AEFD-77EC8A2553DC.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末立由,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子序厉,更是在濱河造成了極大的恐慌锐膜,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弛房,死亡現(xiàn)場(chǎng)離奇詭異道盏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)荷逞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)媒咳,“玉大人,你說(shuō)我怎么就攤上這事种远∩瑁” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵院促,是天一觀的道長(zhǎng)筏养。 經(jīng)常有香客問(wèn)我,道長(zhǎng)常拓,這世上最難降的妖魔是什么啡莉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮胡控,結(jié)果婚禮上缕允,老公的妹妹穿的比我還像新娘。我一直安慰自己掂恕,他們只是感情好拖陆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著懊亡,像睡著了一般依啰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上店枣,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天速警,我揣著相機(jī)與錄音,去河邊找鬼鸯两。 笑死闷旧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的钧唐。 我是一名探鬼主播忙灼,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼钝侠!你這毒婦竟也來(lái)了该园?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤帅韧,失蹤者是張志新(化名)和其女友劉穎爬范,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體弱匪,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡青瀑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年璧亮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斥难。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枝嘶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哑诊,到底是詐尸還是另有隱情群扶,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布镀裤,位于F島的核電站竞阐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏暑劝。R本人自食惡果不足惜骆莹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望担猛。 院中可真熱鬧幕垦,春花似錦、人聲如沸傅联。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蒸走。三九已至仇奶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間比驻,已是汗流浹背该溯。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嫁艇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓弦撩,卻偏偏與公主長(zhǎng)得像步咪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子益楼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345