Runtime應用之關聯(lián)對象和MethodSwizzling

最近用到了sunnyxx的forkingdog系列《UIView-FDCollapsibleConstraints》,紀錄下關聯(lián)對象和MethodSwizzling在實際場景中的應用。

基本概念

關聯(lián)對象

  • 關聯(lián)對象操作函數(shù)

    • 設置關聯(lián)對象:
    /**
     *  設置關聯(lián)對象
     *
     *  @param object 源對象
     *  @param key    關聯(lián)對象的key
     *  @param value  關聯(lián)的對象
     *  @param policy 關聯(lián)策略
     */
    void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    
  - 獲取關聯(lián)對象:

  ```objc
  /**
   *  獲取關聯(lián)對象
   *
   *  @param object 源對象
   *  @param key    關聯(lián)對象的key
   *
   *  @return 關聯(lián)的對象
   */
  id objc_getAssociatedObject(id object, const void *key)

其中設置關聯(lián)對象的策略有以下5種:

  • 和MRC的內存操作retain懂衩、assign方法效果差不多
    • 比如設置的關聯(lián)對象是一個UIView,并且這個UIView已經有父控件時实辑,可以使用OBJC_ASSOCIATION_ASSIGN
OBJC_ASSOCIATION_ASSIGN             // 對關聯(lián)對象進行弱引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC   // 對關聯(lián)對象進行強引用(非原子)
OBJC_ASSOCIATION_COPY_NONATOMIC     // 對關聯(lián)對象進行拷貝引用(非原子)
OBJC_ASSOCIATION_RETAIN             // 對關聯(lián)對象進行強引用
OBJC_ASSOCIATION_COPY               // 對關聯(lián)對象進行拷貝引用

關聯(lián)對象在一些第三方框架的分類中常常見到,這里在分析前先看下分類的結構:

struct category_t {
    // 類名
    const char *name;
    // 類
    classref_t cls;
    // 實例方法
    struct method_list_t *instanceMethods;
    // 類方法
    struct method_list_t *classMethods;
    // 協(xié)議
    struct protocol_list_t *protocols;
    // 屬性
    struct property_list_t *instanceProperties;
};

從以上的分類結構藻丢,可以看出剪撬,分類中是不能添加成員變量的,也就是Ivar類型郁岩。所以婿奔,如果想在分類中存儲某些數(shù)據(jù)時,關聯(lián)對象就是在這種情況下的常用選擇问慎。

需要注意的是萍摊,關聯(lián)對象并不是成員變量,關聯(lián)對象是由一個全局哈希表存儲的鍵值對中的值如叼。

全局哈希表的定義如下:

class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { spinlock_lock(&_lock); }
    ~AssociationsManager()  { spinlock_unlock(&_lock); }

    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

其中的AssociationsHashMap就是那個全局哈希表冰木,而注釋中也說明的很清楚了:哈希表中存儲的鍵值對是(源對象指針 : 另一個哈希表)。而這個value笼恰,即ObjectAssociationMap對應的哈希表如下:

// hash_map和unordered_map是模版類
// 查看源碼后可以看出AssociationsHashMap的key是disguised_ptr_t類型踊沸,value是ObjectAssociationMap *類型
// ObjectAssociationMap的key是void *類型,value是ObjcAssociation類型

#if TARGET_OS_WIN32
    typedef hash_map ObjectAssociationMap;
    typedef hash_map AssociationsHashMap;
#else
    typedef ObjcAllocator > ObjectAssociationMapAllocator;
    class ObjectAssociationMap : public std::map {
    public:
        void *operator new(size_t n) { return ::_malloc_internal(n); }
        void operator delete(void *ptr) { ::_free_internal(ptr); }
    };
    typedef ObjcAllocator > AssociationsHashMapAllocator;

    class AssociationsHashMap : public unordered_map {
    public:
        void *operator new(size_t n) { return ::_malloc_internal(n); }
        void operator delete(void *ptr) { ::_free_internal(ptr); }
    };
#endif

其中的ObjectAssociationMap就是value的類型社证。同時逼龟,也可以知道ObjectAssociationMap的鍵值對類型為(關聯(lián)對象對應的key : 關聯(lián)對象),也就是函數(shù)objc_setAssociatedObject的對應的key:value參數(shù)追葡。

大部分情況下腺律,關聯(lián)對像會使用getter方法的SEL當作key(getter方法中可以這樣表示:_cmd)。

更多和關聯(lián)對象有關的底層信息宜肉,可以查看Dive into Category

MethodSwizzling

MethodSwizzling主要原理就是利用runtime的動態(tài)特性匀钧,交換方法對應的實現(xiàn),也就是IMP谬返。
通常之斯,MethodSwizzling的封裝為:

+ (void)load
{
// 源方法--原始的方法
// 目的方法--我們自己實現(xiàn)的,用來替換源方法

    static dispatch_once_t onceToken;
    // MethodSwizzling代碼只需要在類加載時調用一次遣铝,并且需要線程安全環(huán)境
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        // 獲取方法的SEL
        SEL origionSel = @selector(viewDidLoad);
        SEL swizzlingSel = @selector(tpc_viewDidLoad);
        //    IMP origionMethod = class_getMethodImplementation(class, origionSel);
        //    IMP swizzlingMethod = class_getMethodImplementation(class, swizzlingSel);
        // 根據(jù)SEL獲取對應的Method
        Method origionMethod = class_getInstanceMethod(class, origionSel);
        Method swizzlingMethod = class_getInstanceMethod(class, swizzlingSel);

        // 向類中添加目的方法對應的Method
        BOOL hasAdded = class_addMethod(class, origionSel, method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));

        // 交換源方法和目的方法的Method方法實現(xiàn)
        if (hasAdded) {
            class_replaceMethod(class, swizzlingSel, method_getImplementation(origionMethod), method_getTypeEncoding(origionMethod));
        } else {
            method_exchangeImplementations(origionMethod, swizzlingMethod);
        }
    });
}

為了便于區(qū)別佑刷,這里列出Method的結構:

typedef struct method_t *Method;

// method_t
struct method_t {
    SEL name;
    const char *types;
    IMP imp;
    ...
}

實現(xiàn)MethodSwizzling需要了解的有以下幾個常用函數(shù):

// 返回方法的具體實現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name )

// 返回方法描述
Method class_getInstanceMethod ( Class cls, SEL name )

// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types )

// 替代方法的實現(xiàn)
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types )

// 返回方法的實現(xiàn)
IMP method_getImplementation ( Method m );

// 獲取描述方法參數(shù)和返回值類型的字符串
const char * method_getTypeEncoding ( Method m );

// 交換兩個方法的實現(xiàn)
void method_exchangeImplementations ( Method m1, Method m2 );

介紹MethodSwizzling的文章很多莉擒,更多和MethodSwizzling有關的信息,可以查看Objective-C的hook方案(一): Method Swizzling

針對UIView-FDCollapsibleConstraints的應用

UIView-FDCollapsibleConstraints是sunnyxx陽神寫的一個UIView分類项乒,可以實現(xiàn)僅在IB中對UIView上的約束進行設置啰劲,就達到以下效果,而不需要編寫改變約束的代碼:(圖片來源UIView-FDCollapsibleConstraints

UIView下
UITableView下

這里介紹下自己對這個分類的理解:

  • 實現(xiàn)思路
    • 將需要和UIView關聯(lián)且需要動態(tài)修改的約束添加進一個和UIView綁定的特定的數(shù)組里面
    • 根據(jù)UIView的內容是否為nil檀何,對這個特定數(shù)組中的約束值進行統(tǒng)一設置

而在分類不能增加成員變量的情況下,和UIView綁定的特定的數(shù)組就是用關聯(lián)對象實現(xiàn)的廷支。

先從分類的頭文件開始:

頭文件

@interface UIView (FDCollapsibleConstraints)

/// Assigning this property immediately disables the view's collapsible constraints'
/// by setting their constants to zero.
@property (nonatomic, assign) BOOL fd_collapsed;

/// Specify constraints to be affected by "fd_collapsed" property by connecting in
/// Interface Builder.
@property (nonatomic, copy) IBOutletCollection(NSLayoutConstraint) NSArray *fd_collapsibleConstraints;

@end

@interface UIView (FDAutomaticallyCollapseByIntrinsicContentSize)

/// Enable to automatically collapse constraints in "fd_collapsibleConstraints" when
/// you set or indirectly set this view's "intrinsicContentSize" to {0, 0} or absent.
///
/// For example:
///  imageView.image = nil;
///  label.text = nil, label.text = @"";
///
/// "NO" by default, you may enable it by codes.
@property (nonatomic, assign) BOOL fd_autoCollapse;

/// "IBInspectable" property, more friendly to Interface Builder.
/// You gonna find this attribute in "Attribute Inspector", toggle "On" to enable.
/// Why not a "fd_" prefix? Xcode Attribute Inspector will clip it like a shit.
/// You should not assgin this property directly by code, use "fd_autoCollapse" instead.
@property (nonatomic, assign, getter=fd_autoCollapse) IBInspectable BOOL autoCollapse;

分析幾點:

  • IBOutletCollection频鉴,詳情參考IBAction / IBOutlet / IBOutlet?Collection
    • 表示將SB中相同的控件連接到一個數(shù)組中;這里使用這個方式恋拍,將在SB中的NSLayoutConstraint添加到fd_collapsibleConstraints數(shù)組中垛孔,以便后續(xù)對約束進行統(tǒng)一操作
    • IBOutletCollectionh和IBOutlet操作方式一樣,需要在IB中進行相應的拖拽才能把對應的控件加到數(shù)組中(UIView->NSLayoutConstraint
    • 設置了IBOutletCollection之后施敢,當從storybooard或者xib中加載進行解檔時周荐,最終會調用fd_collapsibleConstraints的setter方法,然后就可以在其setter方法中做相應的操作了
  • IBInspectable 表示這個屬性可以在IB中更改僵娃,如下圖

Snip20150704_1.png

- 還有一個這里沒用概作,IB_DESIGNABLE,這個表示可以在IB中實時顯示修改的效果默怨,詳情參考@IBDesignable和@IBInspectable

主文件

NSLayoutConstraint (_FDOriginalConstantStorage)
  • 因為在修改約束值后讯榕,需要還原操作,但是分類中無法添加成員變量匙睹,所以在這個分類中愚屁,給NSLayoutConstraint約束關聯(lián)一個存儲約束初始值的浮點數(shù),以便在修改約束值后痕檬,可以還原
/// A stored property extension for NSLayoutConstraint's original constant.
@implementation NSLayoutConstraint (_FDOriginalConstantStorage)

// 給NSLayoutConstraint關聯(lián)一個初始約束值
- (void)setFd_originalConstant:(CGFloat)originalConstant
{
    objc_setAssociatedObject(self, @selector(fd_originalConstant), @(originalConstant), OBJC_ASSOCIATION_RETAIN);
}

- (CGFloat)fd_originalConstant
{
#if CGFLOAT_IS_DOUBLE
    return [objc_getAssociatedObject(self, _cmd) doubleValue];
#else
    return [objc_getAssociatedObject(self, _cmd) floatValue];
#endif
}

@end
UIView (FDCollapsibleConstraints)
  • 同樣霎槐,因為需要對UIView上綁定的約束進行改動,所以需要在分類中添加一個可以記錄所有約束的對象梦谜,需要用到關聯(lián)對象

  • 實現(xiàn)fd_collapsibleConstraints屬性的setter和getter方法 (關聯(lián)一個存儲約束的對象)

    • getter方法中創(chuàng)建關聯(lián)對象constraints(和懶加載的方式類似丘跌,不過不是創(chuàng)建成員變量)
    • setter方法中設置約束的初始值,并添加進關聯(lián)對象constraints中改淑,方便統(tǒng)一操作
  • 從IB中關聯(lián)的約束碍岔,最終會調用setFd_collapsibleConstraints:方法,也就是這一步不需要手動調用朵夏,系統(tǒng)自己完成(在awakeFromNib之前完成IB這些值的映射)

    - (NSMutableArray *)fd_collapsibleConstraints
    {
      // 獲取對象的所有約束關聯(lián)值
      NSMutableArray *constraints = objc_getAssociatedObject(self, _cmd);
      if (!constraints) {
          constraints = @[].mutableCopy;
          // 設置對象的所有約束關聯(lián)值
          objc_setAssociatedObject(self, _cmd, constraints, OBJC_ASSOCIATION_RETAIN);
      }
    
      return constraints;
    }
    
    // IBOutletCollection表示xib中的相同的控件連接到一個數(shù)組中
    // 因為設置了IBOutletCollection蔼啦,所以從xib進行解檔時,最終會調用set方法
    // 然后就來到了這個方法
    - (void)setFd_collapsibleConstraints:(NSArray *)fd_collapsibleConstraints
    {
      // Hook assignments to our custom `fd_collapsibleConstraints` property.
      // 返回保存原始約束的數(shù)組仰猖,使用關聯(lián)對象
      NSMutableArray *constraints = (NSMutableArray *)self.fd_collapsibleConstraints;
    
      [fd_collapsibleConstraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
          // Store original constant value
          // 保存原始的約束
          constraint.fd_originalConstant = constraint.constant;
          [constraints addObject:constraint];
      }];
    }
    
    
  • 使用Method Swizzling交換自己的和系統(tǒng)的-setValue:forKey:方

    • 實現(xiàn)自己的KVC的-setValue:forKey:方法
 // load先從原類,再調用分類的開始調用
  // 也就是調用的順序是
  // 原類
  // FDCollapsibleConstraints
  // FDAutomaticallyCollapseByIntrinsicContentSize
  // 所以并不沖突

  + (void)load
  {
      // Swizzle setValue:forKey: to intercept assignments to `fd_collapsibleConstraints`
      // from Interface Builder. We should not do so by overriding setvalue:forKey:
      // as the primary class implementation would be bypassed.
      SEL originalSelector = @selector(setValue:forKey:);
      SEL swizzledSelector = @selector(fd_setValue:forKey:);

      Class class = UIView.class;
      Method originalMethod = class_getInstanceMethod(class, originalSelector);
      Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

      method_exchangeImplementations(originalMethod, swizzledMethod);
  }

  // xib也就是xml捏肢,再加載進行decode時奈籽,會調用setValue:forKey:,把他的方法替換成自身的鸵赫,然后獲取添加的約束
  // 作者說明不使用重寫這個KVC方法的方式衣屏,是因為這樣會覆蓋view本身在這個方法中進行的操作

  - (void)fd_setValue:(id)value forKey:(NSString *)key
  {
      NSString *injectedKey = [NSString stringWithUTF8String:sel_getName(@selector(fd_collapsibleConstraints))];

      if ([key isEqualToString:injectedKey]) {
          // This kind of IBOutlet won't trigger property's setter, so we forward it.
          // 作者的意思是,IBOutletCollection不會觸發(fā)對應屬性的setter方法辩棒,所以這里執(zhí)行手動調用
          self.fd_collapsibleConstraints = value;
      } else {
          // Forward the rest of KVC's to original implementation.
          [self fd_setValue:value forKey:key];
      }
  }
  • 上面使用Method Swizzling的原因作者認為是這種類型的IBOutlet不會觸發(fā)其setter方法狼忱,但是經過測試,注釋掉這段代碼后一睁,系統(tǒng)還是自己觸發(fā)了setter方法钻弄,說明這種IBOutlet還是可以觸發(fā)setter方法的。所以者吁,即使沒有這一段代碼窘俺,應該也是可行的
操作結果
  • 設置對應的約束值

    • 這里給UIView對象提供一個關聯(lián)對象,來判斷是否將約束值清零
    • 注意复凳,這里只要傳入的是YES瘤泪,那么,這個UIView對應存入constraints關聯(lián)對象的所有約束育八,都會置為0
    #pragma mark - Dynamic Properties
    
    - (void)setFd_collapsed:(BOOL)collapsed
    {
        [self.fd_collapsibleConstraints enumerateObjectsUsingBlock:
     ^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
         if (collapsed) {
             // 如果view的內容為nil对途,則將view關聯(lián)的constraints對象所有值設置為0
             constraint.constant = 0;
         } else {
            // 如果view的內容不為nil,則將view關聯(lián)的constraints對象所有值返回成原值
             constraint.constant = constraint.fd_originalConstant;
         }
     }];
        // 設置fd_collapsed關聯(lián)對象单鹿,供自動collapsed使用
        objc_setAssociatedObject(self, @selector(fd_collapsed), @(collapsed), OBJC_ASSOCIATION_RETAIN);
    }
    
    - (BOOL)fd_collapsedFDAutomaticallyCollapseByIntrinsicContentSize{
    return [objc_getAssociatedObject(self, _cmd) boolValue];
    }
    @end
    

######UIView (FDAutomaticallyCollapseByIntrinsicContentSize)
- 使用Method Swizzling交換自己實現(xiàn)的-fd_updateConstraints和系統(tǒng)的updateConstraints方法
  - [self fd_updateConstraints]調用的是self的updateConstraints方法掀宋,fd_updateConstraints和updateConstraints方法的IMP,即方法實現(xiàn)已經調換了
  - 可以看到仲锄,加入這里不使用Method Swizzling劲妙,那么要實現(xiàn)在更新約束時就需要`重寫updateConstraints`方法,而這只能在`繼承UIView`的情況下才能完成的儒喊;而實用了Method Swizzling镣奋,就可以直接在`分類`中實現(xiàn)在`調用系統(tǒng)updateConstraints的前提下`,又`添加自己想要執(zhí)行的附加代碼`
  - `intrinsicContentSize(控件的內置大小)`默認為UIViewNoIntrinsicMetric怀愧,當`控件中沒有內容時`侨颈,調用intrinsicContentSize返回的即為`默認值`,詳情參考([intrinsicContentSize和Content Hugging Priority](http://www.mgenware.com/blog/?p=491))

  ```objc
  #pragma mark - Hacking "-updateConstraints"

    + (void)load
    {
    // Swizzle to hack "-updateConstraints" method
    SEL originalSelector = @selector(updateConstraints);
    SEL swizzledSelector = @selector(fd_updateConstraints);

    Class class = UIView.class;
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    method_exchangeImplementations(originalMethod, swizzledMethod);
    }

    - (void)fd_updateConstraints
    {
    // Call primary method's implementation
    [self fd_updateConstraints];

    if (self.fd_autoCollapse && self.fd_collapsibleConstraints.count > 0) {

        // "Absent" means this view doesn't have an intrinsic content size, {-1, -1} actually.
        const CGSize absentIntrinsicContentSize = CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);

        // 當設置控件顯示內容為nil時芯义,計算出來的contentSize和上面的相等
        // Calculated intrinsic content size
        const CGSize contentSize = [self intrinsicContentSize];

        // When this view doesn't have one, or has no intrinsic content size after calculating,
        // it going to be collapsed.
        if (CGSizeEqualToSize(contentSize, absentIntrinsicContentSize) ||
            CGSizeEqualToSize(contentSize, CGSizeZero)) {
            // 當控件沒有內容時哈垢,則設置控件關聯(lián)對象constraints的所有約束值為0
            self.fd_collapsed = YES;
        } else {
            // 當控件有內容時,則設置控件關聯(lián)對象constraints的所有約束值返回為原值
            self.fd_collapsed = NO;
        }
    }
    }

  • 設置一些動態(tài)屬性(關聯(lián)對象)

    • 給UIView關聯(lián)一個對象扛拨,來判斷是否需要自動對約束值進行清零
    #pragma mark - Dynamic Properties
    
      - (BOOL)fd_autoCollapse
    

{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}

- (void)setFd_autoCollapse:(BOOL)autoCollapse

{
objc_setAssociatedObject(self, @selector(fd_autoCollapse), @(autoCollapse), OBJC_ASSOCIATION_RETAIN);
}

- (void)setAutoCollapse:(BOOL)collapse

{
// Just forwarding
self.fd_autoCollapse = collapse;
}


##總結

總體來說耘分,在分類中要想實現(xiàn)相對復雜的邏輯,卻`不能添加成員變量`,也`不想對需要操作的類進行繼承`求泰,這時就需要runtime中的`關聯(lián)對象和MethodSwizzling`技術了央渣。

forkingdog系列分類都用到了runtime的一些知識,代碼簡潔注釋齊全風格也不錯渴频,比較適合需要學習runtime應用知識的我芽丹。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市卜朗,隨后出現(xiàn)的幾起案子拔第,更是在濱河造成了極大的恐慌,老刑警劉巖场钉,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楼肪,死亡現(xiàn)場離奇詭異,居然都是意外死亡惹悄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門肩钠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泣港,“玉大人,你說我怎么就攤上這事价匠〉鄙矗” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵踩窖,是天一觀的道長坡氯。 經常有香客問我,道長洋腮,這世上最難降的妖魔是什么箫柳? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮啥供,結果婚禮上悯恍,老公的妹妹穿的比我還像新娘。我一直安慰自己伙狐,他們只是感情好涮毫,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贷屎,像睡著了一般罢防。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唉侄,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天咒吐,我揣著相機與錄音,去河邊找鬼。 笑死渤滞,一個胖子當著我的面吹牛贬墩,可吹牛的內容都是我干的。 我是一名探鬼主播妄呕,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼陶舞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了绪励?” 一聲冷哼從身側響起肿孵,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疏魏,沒想到半個月后停做,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡大莫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年蛉腌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片只厘。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡烙丛,死狀恐怖,靈堂內的尸體忽然破棺而出羔味,到底是詐尸還是另有隱情河咽,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布赋元,位于F島的核電站忘蟹,受9級特大地震影響,放射性物質發(fā)生泄漏搁凸。R本人自食惡果不足惜媚值,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坪仇。 院中可真熱鬧杂腰,春花似錦、人聲如沸椅文。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽皆刺。三九已至少辣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間羡蛾,已是汗流浹背漓帅。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人忙干。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓器予,卻偏偏與公主長得像,于是被迫代替她去往敵國和親捐迫。 傳聞我的和親對象是個殘疾皇子乾翔,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內容