Runtime Method Swizzling黑魔法的用法

前言:什么是Method Swizzling配深,在iOS開發(fā)中它有什么作用?

簡單來說我們主要是使用Method Swizzling來把系統(tǒng)的方法交換為我們自己的方法烈掠,從而給系統(tǒng)方法添加一些我們想要的功能左敌。該篇文章主要列舉Method Swizzling在開發(fā)中的一些現(xiàn)實用例母谎。

目前已更新實例匯總:
  • 實例一:替換ViewController生命周期方法
  • 實例二:解決獲取索引奇唤、添加咬扇、刪除元素越界崩潰問題
  • 實例三:防止按鈕重復(fù)暴力點擊
  • 實例四:全局更換控件初始效果
  • 實例五:App熱修復(fù)
  • 實例六:App異常加載占位圖通用類封裝
  • 實例七:全局修改導(dǎo)航欄后退(返回)按鈕
Method Swizzling通用方法封裝

在列舉之前懈贺,我們可以將Method Swizzling功能封裝為類方法梭灿,作為NSObject的類別堡妒,這樣我們后續(xù)調(diào)用也會方便些皮迟。

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface NSObject (Swizzling) 

+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector
                         bySwizzledSelector:(SEL)swizzledSelector;
@end
#import "NSObject+Swizzling.h"
@implementation NSObject (Swizzling)

+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector{
    Class class = [self class];
    //原有方法
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    //替換原有方法的新方法
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    //先嘗試給源SEL添加IMP忿檩,這里是為了避免源SEL沒有實現(xiàn)IMP的情況
    BOOL didAddMethod = class_addMethod(class,originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {//添加成功:說明源SEL沒有實現(xiàn)IMP燥透,將源SEL的IMP替換到交換SEL的IMP
        class_replaceMethod(class,swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {//添加失斒揸:說明源SEL已經(jīng)有IMP,直接將兩個SEL的IMP交換即可
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
@end
??補充知識點
- SEL窖壕、Method鸳吸、IMP的含義及區(qū)別

在運行時速勇,類(Class)維護了一個消息分發(fā)列表來解決消息的正確發(fā)送。每一個消息列表的入口是一個方法(Method)养匈,這個方法映射了一對鍵值對呕乎,其中鍵是這個方法的名字(SEL)猬仁,值是指向這個方法實現(xiàn)的函數(shù)指針 implementation(IMP)的烁。
偽代碼表示:

Class {
      MethodList (
                  Method{
                      SEL:IMP撮躁;
                  }
                  Method{
                      SEL:IMP把曼;
                  }
                  );
      };

Method Swizzling就是改變類的消息分發(fā)列表來讓消息解析時從一個選擇器(SEL)對應(yīng)到另外一個的實現(xiàn)(IMP)嗤军,同時將原始的方法實現(xiàn)混淆到一個新的選擇器(SEL)叙赚。

  • 為什么要添加didAddMethod判斷震叮?

先嘗試添加原SEL其實是為了做一層保護苇瓣,因為如果這個類沒有實現(xiàn)originalSelector,但其父類實現(xiàn)了媳禁,那class_getInstanceMethod會返回父類的方法画切。這樣method_exchangeImplementations替換的是父類的那個方法毫别,這當(dāng)然不是我們想要的拧烦。所以我們先嘗試添加 orginalSelector恋博,如果已經(jīng)存在,再用 method_exchangeImplementations 把原方法的實現(xiàn)跟新的方法實現(xiàn)給交換掉炼吴。
如果理解還不夠透徹硅蹦,我們可以進入runtime.h中查看class_addMethod源碼解釋:

/** 
* Adds a new method to a class with a given name and implementation.
* 
* @param cls The class to which to add a method.
* @param name A selector that specifies the name of the method being added.
* @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
* @param types An array of characters that describe the types of the arguments to the method. 
* 
* @return YES if the method was added successfully, otherwise NO 
*  (for example, the class already contains a method implementation with that name).
*
* @note class_addMethod will add an override of a superclass's implementation, 
*  but will not replace an existing implementation in this class. 
*  To change an existing implementation, use method_setImplementation.
*/

大概的意思就是我們可以通過class_addMethod為一個類添加方法(包括方法名稱(SEL)和方法的實現(xiàn)(IMP))鲤拿,返回值為BOOL類型生音,表示方法是否成功添加窒升。需要注意的地方是class_addMethod會添加一個覆蓋父類的實現(xiàn)域醇,但不會取代原有類的實現(xiàn)歹苦。也就是說如果class_addMethod返回YES,說明子類中沒有方法originalSelector号杠,通過class_addMethod為其添加了方法originalSelector,并使其實現(xiàn)(IMP)為我們想要替換的實現(xiàn)眼溶。

class_addMethod(class,originalSelector,
                                      method_getImplementation(swizzledMethod),
                                      method_getTypeEncoding(swizzledMethod));

同時再將原有的實現(xiàn)(IMP)替換到swizzledMethod方法上堂飞,

class_replaceMethod(class,swizzledSelector,
                          method_getImplementation(originalMethod),
                          method_getTypeEncoding(originalMethod));

從而實現(xiàn)了方法的交換枢泰,并且未影響父類方法的實現(xiàn)衡蚂。反之如果class_addMethod返回NO,說明子類中本身就具有方法originalSelector的實現(xiàn)骏庸,直接調(diào)用交換即可毛甲。

method_exchangeImplementations(originalMethod, swizzledMethod);
-------------------------------實例列舉-------------------------------
實例一:替換ViewController生命周期方法

App跳轉(zhuǎn)到某具有網(wǎng)絡(luò)請求的界面時,為了用戶體驗效果常會添加加載欄或進度條來顯示當(dāng)前請求情況或進度具被。這種界面都會存在這樣一個問題玻募,在請求較慢時,用戶手動退出界面硬猫,這時候需要去除加載欄补箍。
當(dāng)然可以依次在每個界面的viewWillDisappear方法中添加去除方法,但如果類似的界面過多啸蜜,一味的復(fù)制粘貼也不是方法。這時候就能體現(xiàn)Method Swizzling的作用了,我們可以替換系統(tǒng)的viewWillDisappear方法,使得每當(dāng)執(zhí)行該方法時即自動去除加載欄。

#import "UIViewController+Swizzling.h"
#import "NSObject+Swizzling.h"
@implementation UIViewController (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self methodSwizzlingWithOriginalSelector:@selector(viewWillDisappear:) bySwizzledSelector:@selector(sure_viewWillDisappear:)];
    });
}

- (void)sure_viewWillDisappear:(BOOL)animated {
    [self sure_viewWillDisappear:animated];
    [SVProgressHUD dismiss];
}

代碼如上,這樣就不用考慮界面是否移除加載欄的問題了。補充一點,通常我們也會在生命周期方法中設(shè)置默認(rèn)界面背景顏色宪塔,因若背景顏色默認(rèn)為透明對App的性能也有一定影響来吩,這大家可以在UIKit性能優(yōu)化那篇文章中查閱怠苔。但類似該類操作也可以書寫在通用類中锅劝,所以具體使用還要靠自己定奪。

??補充知識點
  • 為什么方法交換調(diào)用在+load方法中结窘?
    在Objective-C runtime會自動調(diào)用兩個類方法,分別為+load+ initialize确买。+load 方法是在類被加載的時候調(diào)用的,也就是一定會被調(diào)用。而+initialize方法是在類或它的子類收到第一條消息之前被調(diào)用的,這里所指的消息包括實例方法和類方法的調(diào)用。也就是說+initialize方法是以懶加載的方式被調(diào)用的,如果程序一直沒有給某個類或它的子類發(fā)送消息袁滥,那么這個類的+initialize方法是永遠(yuǎn)不會被調(diào)用的靴拱。此外+load方法還有一個非常重要的特性偎窘,那就是子類仆葡、父類和分類中的+load方法的實現(xiàn)是被區(qū)別對待的腰涧。換句話說在 Objective-C runtime自動調(diào)用+load方法時窒悔,分類中的+load方法并不會對主類中的+load方法造成覆蓋芙粱。綜上所述振峻,+load 方法是實現(xiàn) Method Swizzling 邏輯的最佳“場所”拔创。如需更深入理解,可參考Objective-C 深入理解 +load 和 +initialize剩燥。
  • 為什么方法交換要在dispatch_once中執(zhí)行立轧?
    方法交換應(yīng)該要線程安全,而且保證在任何情況下(多線程環(huán)境,或者被其他人手動再次調(diào)用+load方法)只交換一次,防止再次調(diào)用又將方法交換回來。除非只是臨時交換使用芬骄,在使用完成后又交換回來撇贺。 最常用的解決方案是在+load方法中使用dispatch_once來保證交換是安全的。之前有讀者反饋+load方法本身即為線程安全骇吭,為什么仍需添加dispatch_once橙弱,其原因就在于+load方法本身無法保證其中代碼只被執(zhí)行一次
  • 為什么沒有發(fā)生死循環(huán)?
    一定有很多讀者有疑惑棘脐,為什么sure_viewWillDisappear方法中的代碼沒有發(fā)生遞歸死循環(huán)斜筐。其原因很簡單,因為方法已經(jīng)執(zhí)行過交換蛀缝,調(diào)用[self sure_viewWillDisappear:animated]本質(zhì)是在調(diào)用原有方法viewWillDisappear顷链,反而如果我們在方法中調(diào)用[self viewWillDisappear:animated]才真的會發(fā)生死循環(huán)。是不是很繞屈梁?仔細(xì)看看嗤练。
實例二:解決獲取索引、添加在讶、刪除元素越界崩潰問題

對于NSArray煞抬、NSDictionary、NSMutableArray构哺、NSMutableDictionary不免會進行索引訪問革答、添加、刪除元素的操作遮婶,越界問題也是很常見蝗碎,這時我們可以通過Method Swizzling解決這些問題,越界給予提示防止崩潰旗扑。

這里以NSMutableArray為例說明

#import "NSMutableArray+Swizzling.h"
#import "NSObject+Swizzling.h"
@implementation NSMutableArray (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [objc_getClass("__NSArrayM") methodSwizzlingWithOriginalSelector:@selector(removeObject:) bySwizzledSelector:@selector(safeRemoveObject:) ];
        [objc_getClass("__NSArrayM") methodSwizzlingWithOriginalSelector:@selector(addObject:) bySwizzledSelector:@selector(safeAddObject:)];
        [objc_getClass("__NSArrayM") methodSwizzlingWithOriginalSelector:@selector(removeObjectAtIndex:) bySwizzledSelector:@selector(safeRemoveObjectAtIndex:)];
        [objc_getClass("__NSArrayM") methodSwizzlingWithOriginalSelector:@selector(insertObject:atIndex:) bySwizzledSelector:@selector(safeInsertObject:atIndex:)];
        [objc_getClass("__NSArrayM") methodSwizzlingWithOriginalSelector:@selector(objectAtIndex:) bySwizzledSelector:@selector(safeObjectAtIndex:)];
    });
}
- (void)safeAddObject:(id)obj {
    if (obj == nil) {
        NSLog(@"%s can add nil object into NSMutableArray", __FUNCTION__);
    } else {
        [self safeAddObject:obj];
    }
}
- (void)safeRemoveObject:(id)obj {
    if (obj == nil) {
        NSLog(@"%s call -removeObject:, but argument obj is nil", __FUNCTION__);
        return;
    }
    [self safeRemoveObject:obj];
}
- (void)safeInsertObject:(id)anObject atIndex:(NSUInteger)index {
    if (anObject == nil) {
        NSLog(@"%s can't insert nil into NSMutableArray", __FUNCTION__);
    } else if (index > self.count) {
        NSLog(@"%s index is invalid", __FUNCTION__);
    } else {
        [self safeInsertObject:anObject atIndex:index];
    }
}
- (id)safeObjectAtIndex:(NSUInteger)index {
    if (self.count == 0) {
        NSLog(@"%s can't get any object from an empty array", __FUNCTION__);
        return nil;
    }
    if (index > self.count) {
        NSLog(@"%s index out of bounds in array", __FUNCTION__);
        return nil;
    }
    return [self safeObjectAtIndex:index];
}
- (void)safeRemoveObjectAtIndex:(NSUInteger)index {
    if (self.count <= 0) {
        NSLog(@"%s can't get any object from an empty array", __FUNCTION__);
        return;
    }
    if (index >= self.count) {
        NSLog(@"%s index out of bound", __FUNCTION__);
        return;
    }
    [self safeRemoveObjectAtIndex:index];
}
@end

對應(yīng)大家可以舉一反三蹦骑,相應(yīng)的實現(xiàn)添加、刪除等臀防,以及NSArray眠菇、NSDictionary等操作,因代碼篇幅較大袱衷,這里就不一一書寫了捎废。
這里沒有使用self來調(diào)用,而是使用objc_getClass("__NSArrayM")來調(diào)用的致燥。因為NSMutableArray的真實類只能通過后者來獲取登疗,而不能通過[self class]來獲取,而method swizzling只對真實的類起作用嫌蚤。這里就涉及到一個小知識點:類簇辐益。補充以上對象對應(yīng)類簇表。

類簇表

實例三:防止按鈕重復(fù)暴力點擊

程序中大量按鈕沒有做連續(xù)響應(yīng)的校驗脱吱,連續(xù)點擊出現(xiàn)了很多不必要的問題智政,例如發(fā)表帖子操作,用戶手快點擊多次箱蝠,就會導(dǎo)致同一帖子發(fā)布多次续捂】汛梗可以參考我上篇文章http://www.reibang.com/p/170c55bd7bd0

實例四:全局更換控件初始效果

以UILabel為例,在項目比較成熟的基礎(chǔ)上牙瓢,應(yīng)用中需要引入新的字體劫拗,需要更換所有Label的默認(rèn)字體,但是同時一罩,對于一些特殊設(shè)置了字體的label又不需要更換杨幼。乍看起來,這個問題確實十分棘手聂渊,首先項目比較大,一個一個設(shè)置所有使用到的label的font工作量是巨大的四瘫,并且在許多動態(tài)展示的界面中汉嗽,可能會漏掉一些label,產(chǎn)生bug找蜜。其次饼暑,項目中的label來源并不唯一,有用代碼創(chuàng)建的洗做,有xib和storyBoard中的弓叛,這也將浪費很大的精力。這時Method Swizzling可以解決此問題诚纸,避免繁瑣的操作撰筷。

#import "UILabel+Swizzling.h"
#import "NSObject+Swizzling.h"
@implementation UILabel (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self methodSwizzlingWithOriginalSelector:@selector(init) bySwizzledSelector:@selector(sure_Init)];
        [self methodSwizzlingWithOriginalSelector:@selector(initWithFrame:) bySwizzledSelector:@selector(sure_InitWithFrame:)];
        [self methodSwizzlingWithOriginalSelector:@selector(awakeFromNib) bySwizzledSelector:@selector(sure_AwakeFromNib)];
    });
}
- (instancetype)sure_Init{
    id __self = [self sure_Init];
    UIFont * font = [UIFont fontWithName:@"Zapfino" size:self.font.pointSize];
    if (font) {
        self.font=font;
    }
    return __self;
}
- (instancetype)sure_InitWithFrame:(CGRect)rect{
    id __self = [self sure_InitWithFrame:rect];
    UIFont * font = [UIFont fontWithName:@"Zapfino" size:self.font.pointSize];
    if (font) {
        self.font=font;
    }
    return __self;
}
- (void)sure_AwakeFromNib{
    [self sure_AwakeFromNib];
    UIFont * font = [UIFont fontWithName:@"Zapfino" size:self.font.pointSize];
    if (font) {
        self.font=font;
    }
}
@end

這一實例個人認(rèn)為使用率可能不高,對于產(chǎn)品的設(shè)計這些點都是已經(jīng)確定好的畦徘,更改的幾率很低毕籽。況且我們也可以使用appearance來進行統(tǒng)一設(shè)置。

實例五:App熱修復(fù)

因為AppStore上線審核時間較長井辆,且如果在線上版本出現(xiàn)bug修復(fù)起來也是很困難关筒,這時App熱修復(fù)就可以解決此問題。熱修復(fù)即在不更改線上版本的前提下杯缺,對線上版本進行更新甚至添加模塊蒸播。國內(nèi)比較好的熱修復(fù)技術(shù):JSPatch。JSPatch能做到通過JS調(diào)用和改寫OC方法最根本的原因是Objective-C是動態(tài)語言萍肆,OC上所有方法的調(diào)用/類的生成都通過Objective-C Runtime在運行時進行袍榆,我們可以通過類名/方法名反射得到相應(yīng)的類和方法,進而替換出現(xiàn)bug的方法或者添加方法等匾鸥。bang的博客上有詳細(xì)的描述有興趣可以參考蜡塌,這里就不贅述了。

實例六:App異常加載占位圖通用類封裝(更新于:2016/12/01)

詳情可見文章:《零行代碼為App添加異常加載占位圖》在該功能模塊中勿负,使用Runtime Method Swizzling進行替換tableView馏艾、collectionView的reloadData方法劳曹,使得每當(dāng)執(zhí)行刷新操作時,自動檢測當(dāng)前組數(shù)與行數(shù)琅摩,從而實現(xiàn)零代碼判斷占位圖是否顯示的功能铁孵,同樣也適用于網(wǎng)絡(luò)異常等情況,詳細(xì)設(shè)置可前往閱讀房资。

實例七:全局修改導(dǎo)航欄后退(返回)按鈕(更新于:2016/12/05)

在真實項目開發(fā)中蜕劝,會全局統(tǒng)一某控件樣式,以導(dǎo)航欄后退(返回)按鈕為例,通常項目中會固定為返回字樣澳窑,或者以圖片進行顯示等鲜滩。

iOS默認(rèn)的返回按鈕樣式如下,默認(rèn)為藍(lán)色左箭頭婴削,文字為上一界面標(biāo)題文字。

默認(rèn)返回按鈕樣式

這里我們?nèi)钥梢酝ㄟ^Runtime Method Swizzling來實現(xiàn)該需求牙肝,在使用Method Swizzling進行更改之前唉俗,必須考慮注意事項,即盡可能的不影響原有操作配椭,比如對于系統(tǒng)默認(rèn)的返回按鈕虫溜,與其對應(yīng)的是有界面邊緣右滑返回功能的,因此我們進行統(tǒng)一更改后不可使其功能廢棄股缸。

閑話少說衡楞,我們創(chuàng)建基于UINavigationItem的類別,在其load方法中替換方法backBarButtonItem
代碼如下

#import "UINavigationItem+Swizzling.h"
#import "NSObject+Swizzling.h"
static char *kCustomBackButtonKey;
@implementation UINavigationItem (Swizzling)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self methodSwizzlingWithOriginalSelector:@selector(backBarButtonItem)
                               bySwizzledSelector:@selector(sure_backBarButtonItem)];

    });
}

- (UIBarButtonItem*)sure_backBarButtonItem {
    UIBarButtonItem *backItem = [self sure_backBarButtonItem];
    if (backItem) {
        return backItem;
    }
    backItem = objc_getAssociatedObject(self, &kCustomBackButtonKey);
    if (!backItem) {
        backItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:NULL];
        objc_setAssociatedObject(self, &kCustomBackButtonKey, backItem, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return backItem;
}
@end

這里進行將返回按鈕的文字清空操作乓序,其他需求樣式大家也可隨意替換寺酪,現(xiàn)在再次運行程序,就會發(fā)現(xiàn)所有的返回按鈕均只剩左箭頭替劈,并右滑手勢依然有效寄雀。如圖所示

全局統(tǒng)一設(shè)置返回按鈕

轉(zhuǎn)載于http://www.reibang.com/p/f6dad8e1b848

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市陨献,隨后出現(xiàn)的幾起案子盒犹,更是在濱河造成了極大的恐慌,老刑警劉巖眨业,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件急膀,死亡現(xiàn)場離奇詭異,居然都是意外死亡龄捡,警方通過查閱死者的電腦和手機卓嫂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來聘殖,“玉大人晨雳,你說我怎么就攤上這事行瑞。” “怎么了餐禁?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵血久,是天一觀的道長。 經(jīng)常有香客問我帮非,道長氧吐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任末盔,我火速辦了婚禮筑舅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陨舱。我一直安慰自己豁翎,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布隅忿。 她就那樣靜靜地躺著,像睡著了一般邦尊。 火紅的嫁衣襯著肌膚如雪背桐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天蝉揍,我揣著相機與錄音链峭,去河邊找鬼。 笑死又沾,一個胖子當(dāng)著我的面吹牛弊仪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播杖刷,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼励饵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了滑燃?” 一聲冷哼從身側(cè)響起役听,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎表窘,沒想到半個月后典予,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡乐严,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年瘤袖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昂验。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡捂敌,死狀恐怖艾扮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情黍匾,我是刑警寧澤栏渺,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站锐涯,受9級特大地震影響磕诊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纹腌,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一霎终、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧升薯,春花似錦莱褒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蛛枚,卻和暖如春谅海,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹦浦。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工扭吁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盲镶。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓侥袜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親溉贿。 傳聞我的和親對象是個殘疾皇子枫吧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

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