NSObject協議及本身詳解

?NSObject作為一個基類,這個類遵守了NSObject協議,并且實現了NSObject協議里的所有方法,所以NSObject類及其子類都可以調用這些方法。下面主要介紹NSObject協議里的屬性和方法。

一、NSObject協議

  • - (BOOL)isEqual:(id)object;:比較兩個對像是否相同。比較的是成員變量的值是否相同,這與 ==符號有很大的不同:在對對象進行比較時,==符號判斷是否是同一個對象,比較的是內存地址。
UIImageView * imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"loli"]];
UIImage * image = [UIImage imageNamed:@"loli"];

NSLog(@"imageView.image == image : %d", (imageView.image == image));
NSLog(@"[imageView.image isEqual:image] : %d",[imageView.image isEqual:image]);

結果

imageView.image == image : 0
[imageView.image isEqual:image] : 1
  • @property (readonly) NSUInteger hash;:對象的哈希值,只讀屬性,具有唯一性。- (NSUInteger)hash方法只在對象被添加至NSSet和設置為NSDictionary的key時會調用。

  • @property (readonly) Class superclass;:獲取父類。

  • - (Class)class:獲取自身所屬的類。

  • - (instancetype)self;:獲取對象自己。

  • - (id)performSelector:(SEL)aSelector;- (id)performSelector:(SEL)aSelector withObject:(id)object;- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;:對象響應某一方法,object是傳遞給響應方法的參數。

    • performSelector是程序 運行時 系統負責去找方法,在編譯時不做任何校驗。如果方法由對象直接調用,編譯時會自動校驗。Cocoa支持在運行時向某個類添加方法,即方法編譯時不存在,但是運行時候存在,這時候必然需要使用performSelector去調用。所以有時候如果使用了performSelector,為了使程序能正常運行,一般會使用檢查方法- (BOOL)respondsToSelector:(SEL)aSelector;先行檢驗一下,針對不同情況做出正確的應對措施。
    • 這三個方法,均為同步執行,與線程無關,主線程和子線程中均可調用成功,等同于直接調用該方法。
- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelector:@selector(sendSomeThing:antherImage:) withObject:[UIImage imageNamed:@"boy"] withObject:[UIImage imageNamed:@"poem"]];
}

- (void)sendSomeThing:(UIImage *)aImage antherImage:(UIImage *)bImage
{
    NSLog(@"%@, %@", aImage, bImage);
}
  • - (BOOL)isProxy;:判斷是否是NSProxy的實例。

  • - (BOOL)isKindOfClass:(Class)aClass;:判斷某個對象是否是某個類或者子類的實例。

  • - (BOOL)isMemberOfClass:(Class)aClass;:判斷某個對象是否是某個類的實例。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    UIImageView * imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"loli"]];
    NSLog(@"isKindOfClass : %d", [imageView isKindOfClass:[UIView class]]);
    NSLog(@"isMemberOfClass : %d", [imageView isMemberOfClass:[UIView class]]);
    NSLog(@"isMemberOfClass : %d", [imageView isMemberOfClass:[UIImageView class]]);
}

結果:

isKindOfClass : 1
isMemberOfClass : 0
isMemberOfClass : 1
  • - (BOOL)conformsToProtocol:(Protocol *)aProtocol;:是否遵循了某一協議,至于是否實現了協議中的方法,該方法不關心。
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    NSLog(@"UIViewAnimating : %d", [self conformsToProtocol:@protocol(UIViewAnimating)]);
    NSLog(@"UITableViewDelegate : %d", [self conformsToProtocol:@protocol(UITableViewDelegate)]);
}

結果:

UIViewAnimating : 1
UITableViewDelegate : 0
  • - (BOOL)respondsToSelector:(SEL)aSelector;:是否 能響應 某一方法,若能則返回YES,不能返回NO。這里需要特別注意:類對象(Object)只能響應類方法(+方法),實例對象(object)只能響應實例方法(-方法)
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    NSLog(@"respondsToSelector : %d", [self respondsToSelector:@selector(viewWillAppear:)]);
    NSLog(@"respondsToSelector : %d", [self respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]);
}

結果

respondsToSelector : 1
respondsToSelector : 0
  • @property (readonly, copy) NSString *description;@property (readonly, copy) NSString *debugDescription;description是代碼打印輸出 實例 的時候調用的方法,debugDescription是控制臺也就是po的時候輸出實例的時候調用的方法,都可以自定義。

?關于description/debugDescription方法(下面以description為例說明):
?description方法默認返回對象的描述信息(默認實現是返回類名和對象的內存地址),這樣的話,使用NSLog輸出OC對象,意義就不是很大,因為我們并不關心對象的內存地址,比較關心的是對象內部的一些成變量的值。因此,會經常重寫description方法,覆蓋description方法的默認實現。
?description有一個實例方法-(NSString *)description;(NSLog輸出該類的實例對象時調用),一個類方法+(NSString *)description;(NSLog輸出該類的類對象時調用)。

// 未重寫description方法情況
Person * person_1 = [[Person alloc] init];
NSLog(@"%@", person_1);
NSLog(@"%@",[person_1 class]);
// 輸出結果
<Person: 0x60000003a700>
Person
// 重寫description方法情況
// Person.m
-(NSString *)description
{
    return @"ASD";
}
+(NSString *)description
{
    return @"QWE";
}
// 其他類調用
Person * person_1 = [[Person alloc] init];
NSLog(@"%@", person_1);
NSLog(@"%@",[person_1 class]);
// 輸出結果
ASD
QWE

特別注意:在重寫的description方法中不能同時使用%@ 和self。

// 下面這種寫法會導致循環引用,拋出異常
-(NSString *)description
{
    return [NSString stringWithFormat:@"%@", self];
}

但是有個例外情況當在該方法內部打印時同時使用會導致該方法連續調用三次后自動結束。

// Person.m
-(NSString *)description
{
    NSLog(@"self = %@", self);
    return @"ASD";
}
// 其他類調用
Person * person_1 = [[Person alloc] init];
NSLog(@"%@", person_1);
// 輸出結果
self = ASD
self = ASD
self = ASD
ASD

二、NSObject基類

  • + (void)initialize;類方法,在該類收到第一條消息前初始化該類。

1、 -- > 一般情況下,每個類的這一方法只調用一次。

@implementation FatherView

+ (void)initialize
{
    NSLog(@"father -->  self == %@, functionString == %s", [self class], __FUNCTION__);
}

@end


@implementation OneViewController

- (void)viewWillAppear:(BOOL)animated
{
    FatherView * fatherView = [[FatherView alloc] init];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    FatherView * fatherView = [[FatherView alloc] init];
}

@end

運行打印結果只會打印一次:結果為

father -->  self == FatherView, functionString == +[FatherView initialize]

2、 -- > 超類在它們的子類之前收到這個消息。

@implementation FatherView

+ (void)initialize
{
    NSLog(@"father -->  self == %@, functionString == %s", [self class], __FUNCTION__);
}

@end


@implementation BodyView

+ (void)initialize
{
    NSLog(@"body -->  self == %@, functionString == %s", [self class], __FUNCTION__);
}

@end


- (void)viewDidLoad {
    [super viewDidLoad];
    BodyView * bodyView = [[BodyView alloc] init];
}

運行打印結果為:

father -->  self == FatherView, functionString == +[FatherView initialize]
body -->  self == BodyView, functionString == +[BodyView initialize]

3、 -- > 如果子類不實現+(void)initialize方法或者如果子類顯式調用[super initialize],超類的實現可能會被多次調用 。

// 如上面2、中示例所示,將BodyView.m中的 + (void)initialize方法注釋掉再運行。
// 結果為:
/**
father -->  self == FatherView, functionString == +[FatherView initialize]
father -->  self == BodyView, functionString == +[FatherView initialize]
*/

4、--> 如果想使類本身+(void) initialize方法保護自己部分內容不被多次運行,可以按照以下方式構建實現:

@implementation FatherView

+ (void)initialize
{
    NSLog(@"會調用好幾次~~");
    if (self == [FatherView self]) {
        NSLog(@"father -->  self == %@, functionString == %s", [self class], __FUNCTION__);
    }
}

@end


@implementation BodyView

// 將該方法注銷掉
//+ (void)initialize
//{
//    NSLog(@"body -->  self == %@, functionString == %s", [self class], __FUNCTION__);
//}

@end


@implementation OneViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    BodyView * bodyView = [[BodyView alloc] init];
}

@end

運行結果:

會調用好幾次~~
father -->  self == FatherView, functionString == +[FatherView initialize]
會調用好幾次~~

可以看到在第二次調用父類的initialize方法時if內部代碼沒有再次運行。

5、-- > 如果類包含分類,且分類重寫initialize方法,那么則會調用分類的 initialize 實現,而原類的該方法實現不會被調用,這個機制同 NSObject 的其他方法一樣(除 + (void)load 方法外) 。在這不在舉例演示。

  • + (void)load;每當將類或類別添加到Objective-C runtime時調用。該方法在+(void)initialize方法后調用。

    • 超類+(void)load方法先于子類方法的調用;

    • 如果分類也實現了+(void)load方法,那么該方法也會調用,但是是在類本身的+(void)load方法調用之后!

@implementation BodyView

+ (void)load
{
    NSLog(@"body_load -->  self == %@, functionString == %s", [self class], __FUNCTION__);
}

@end


@implementation BodyView (ImageView)

+(void)load
{
    NSLog(@"BodyView+ImageView_load -->  self == %@, functionString == %s", [self class], __FUNCTION__);
}

@end

打印結果:

body_load -->  self == BodyView, functionString == +[BodyView load]
BodyView+ImageView_load -->  self == BodyView, functionString == +[BodyView(ImageView) load]

?


重點注意:無論是 +(void)initialize方法還是 +(void)load方法,均是在系統運行時執行的,調用會發生在-(BOOL)application:willFinishLaunchingWithOptions: 調用之前調用。可以依此對該方法進行瘦身 -- > 可以看一下Sunny的這篇文章Notification Once,在此表示感謝!。

?

  • + (instancetype)alloc/ + (instancetype)allocWithZone:(struct _NSZone *)zone--在使用時參數傳nil /- (instancetype)init+ (instancetype)new:初始化方法

  • - (id)copy:返回NSCopying協議方法- (id)copyWithZone:(nullable NSZone *)zone;返回的對象。NSObject本身不支持NSCopying協議,其子類若想實現該方法就必須實現NSCopying協議并實現- (nonnull id)copyWithZone:(nullable NSZone *)zone方法,否者會報錯:-[子類 copyWithZone:]: unrecognized selector sent to instance ........

  • - (id)mutableCopy;:返回NSMutableCopying協議方法- (id)mutableCopyWithZone:(nullable NSZone *)zone在參數為nil情況下返回的對象。其子類若想實現該方法就必須實現NSMutableCopying協議并實現- (id)mutableCopyWithZone:(nullable NSZone *)zone方法,否者會報錯:-[子類 mutabelCopyWithZone:]: unrecognized selector sent to instance .......

    • 在這里引申出深拷貝和淺拷貝含義:

      • 淺拷貝:就是對指針的拷貝,但是目標對象指針和源對象指針指向同一片內存空間。
      • 深拷貝:是指拷貝對象的具體內容,而內存地址是自主分配的,拷貝結束之后,兩個對象的值雖然是相同的,但是內存地址不一樣,兩個對象互不影響,互不干涉。
      • 主要常見的涉及深拷貝淺拷貝的類有:NSStringNSMutableStringNSArrayNSMutableArrayNSDictionaryNSMutableDictionary。它們都已經遵循了NSCopying或者NSMutableCopying協議,對于不可變類(NS····)copy即淺拷貝,mutableCopy即為深拷貝;對于可變類(NSMutable····)無論copy還是mutableCopy都是深拷貝。
      • 其實對于所有(NS *)和(NSMutable *)上述結論都適用。對于所有父類是NSObject的自定義類而言,無論copy還是mutableCopy都是深拷貝。
  • - (void)dealloc:釋放系統無法釋放的對象占用的資源。常見關于dealloc的問題:

    • 現在ARC下不需要調用[super dealloc];系統會自動幫你調用;

    • 可以用來釋放通知或者KVO的觀察者:通知中心是系統的單例,當在注冊通知的觀察者時,實際上是在通知中心注冊的,這樣在界面dismiss后即使ARC下系統幫我們釋放了對象,但是在通知中心的觀察還是沒有移除,那么當有該通知時,依然會嘗試調用該對象的接受通知的方法,這可能會導致一些問題。在控制器中也可以在- (void)viewDidDisappear:(BOOL)animated中釋放。

    • 在控制器dismiss或者pop后dealloc不調用問題:

      • 可能是使用了NSTimer導致的,需要在- (void)viewWillDisappear:(BOOL)animated;或者- (void)viewDidDisappear:(BOOL)animated;方法中調用[timer invalidate]即可。
      • 代理屬性設置為strong的情況下可能發生循環引用問題導致無法釋放,自然也不會調用該方法。
      • 控制器中存在Block的循環引用問題。也會造成dealloc方法不調用問題。常見block中使用self的問題 --- >__weak Viewcontroller * weakSelf = self;
  • + (BOOL)isSubclassOfClass:(Class)aClass:返回一個布爾值,該值指示接收類是否是給定類的子類或完全相同。

  • + (NSUInteger)hash:類對象的哈希值。

  • + (Class)superclass:返回接收者超類的類對象。

  • + (Class)class:返回類對象。

?


下面是與函數調用和runtime相關方法:OC是一門動態語言,一個函數是由一個selector(SEL),和一個implement(IMP)組成的。Selector相當于索引,而Implement才是真正的函數實現。

  • + (BOOL)instancesRespondToSelector:(SEL)aSelector;:返回一個布爾值,指示類本身是否能夠響應給定的選擇器。注意與- (BOOL)respondsToSelector:(SEL)aSelector;方法的區別!!!用法參考上面respondsToSelector用法。

  • + (BOOL)conformsToProtocol:(Protocol *)protocol是否遵循了某一協議。詳細見上面object協議中方法描述,唯一不同即這是一個類方法。

  • - (IMP)methodForSelector:(SEL)aSelector;+ (IMP)instanceMethodForSelector:(SEL)aSelector; 定位并返回接收方執行方法的地址,以便將其作為函數調用。

    • IMP是真正的函數指針,SEL只是索引。

    • 參數SEL必須是有效的,所以可以在調用此方法之前使用- (BOOL)respondsToSelector:(SEL)aSelector;或者+ (BOOL)instancesRespondToSelector:(SEL)aSelector進行檢驗。

    • 如果調用者是一個實例,應該參考一個實例方法; 如果調動者是一個類,它應該引用一個類的方法!!!

為了方便了解下面的方法,下面先給出選擇器執行函數的過程圖:

添加:
  • + (BOOL)resolveClassMethod:(SEL)sel:解析類方法,動態地為類方法的給定選擇器提供實現。

  • + (BOOL)resolveInstanceMethod:(SEL)sel:解析實例方法,動態地為實例方法的給定選擇器提供實現。

    ?對于上面兩個方法而言:

    • 如果參數方法被發現并且被添加到接收者,則返回YES,否則,返回NO;
    • 這個函數在運行時(runtime)如果沒有找到SEL的IML,就會執行。這個函數是給類利用class_addMethod添加函數的機會。
    • 如果類重寫了該函數,并使用class_addMethod()添加了相應的selector,并返回YES,那么后面forwardingTargetForSelector等方法就不會被調用,如果在該函數中沒有添加相應的selector,那么不管返回什么,后面都會繼續調用相關方法或者爆出異常。
#import <objc/runtime.h>

void addNewMethod()
{
    NSLog(@"method == %s", __FUNCTION__);
}

@implementation BodyView

   + (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"解析實例方法 == %s", __FUNCTION__);
//    if (![self respondsToSelector:sel]) { //該方法中不可使用respondsToSelector:
//    或者instancesRespondToSelector:方法,這兩個方法參數SEL在類中如果沒有實現,
//    也會引起resolveInstanceMethod方法的調用!!!
    if (sel == @selector(forwardMethod)) {
        class_addMethod([self class], sel, (IMP)addNewMethod, "v@:");
        NSLog(@"記錄次數");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

@end
轉發:
  • - (id)forwardingTargetForSelector:(SEL)aSelector返回無法識別的消息首先被指向的對象。

    • 當某個對象不能接受某個selector時,將對該selector的調用轉發給另一個對象即返回的對象。但是該方法不建議返回nil(若返回nil則當消息無法識別時會拋出異常)或者self(會造成死循環)。這個方法在這一消息被一個更昂貴的forwardInvocation:方法接管之前,給了一個對象一個機會來重定向發送給它的未知消息。

    • 如果你在一個非root類中實現這個方法,如果你的類對于給定的選擇器沒有任何返回值,那么你應該返回調用super的實現的結果。

    • 僅支持一個對象的返回,也就是說消息只能被轉發給一個對象。

@interface FatherView : UIView

- (void)forwardMethod;

@end

@implementation FatherView

- (void)forwardMethod
{
    NSLog(@"fatherView ---- forwardMethod");
}

@end


@implementation BodyView

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    FatherView * aView = [FatherView new];
    if ([aView respondsToSelector:@selector(forwardMethod)]) {
        return aView;
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end


@implementation OneViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    BodyView * bodyView = [[BodyView alloc] init];
    [bodyView performSelector:@selector(forwardMethod)];
}

@end

如果上述例子中將BodyView中的- (id)forwardingTargetForSelector:(SEL)aSelector方法注釋掉,再次運行會拋出異常-[BodyView forwardMethod]: unrecognized selector sent to instance *******

  • - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;方法簽名。返回一個包含由給定選擇器方法標識的描述的NSMethodSignature對象,如果aSelector沒找到就返回nil。一般復寫用于runtime提供方法簽名。

  • + (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;同樣是方法簽名,但是一般不復寫,只是調用,用于獲取類方法簽名。

  • - (void)forwardInvocation:(NSInvocation *)anInvocation:由子類覆蓋以將消息轉發給其他對象。

    • 為了響應你的對象本身不能識別的方法,除了forwardInvocation:方法外,還必須重寫- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法,轉發消息的機制是使用從methodSignatureForSelector:方法返回的信息創建要轉發的NSInvocation對象。

    • forwardInvocation:方法的實現有兩個任務:

      • 查找可以響應參數anInvocation中編碼的消息的對象。
      • 向使用參數anInvocation的對象發送消息。參數anInvocation將會保存結果,運行時系統將提取并將此結果傳遞給原始發件人。
      • 可以實現向多個目標轉發消息。
@implementation FatherView

- (void)forwardMethod
{
    NSLog(@"fatherView ---- forwardMethod");
}

@end


@implementation BodyView

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"%s", __FUNCTION__);
    if (aSelector == @selector(forwardMethod)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"執行轉換對象的方法 == %s", __FUNCTION__);
    SEL aSelector = [anInvocation selector];
    FatherView * aView = [FatherView new];
    if ([aView respondsToSelector:aSelector]){
        [anInvocation invokeWithTarget:aView];
    }else{
        [super forwardInvocation:anInvocation];
    }
//    if (anInvocation.selector == @selector(forwardMethod)) {
//        FatherView * aView = [FatherView new];
//        [aView performSelector:@selector(forwardMethod)];
//    }else{
//        [super forwardInvocation:anInvocation];
//    }
}

@end

// 調用
BodyView * bodyView = [[BodyView alloc] init];
[bodyView performSelector:@selector(forwardMethod)];

打印結果:

-[BodyView methodSignatureForSelector:]
執行轉換對象的方法 == -[BodyView forwardInvocation:]
fatherView ---- forwardMethod
  • - (void)doesNotRecognizeSelector:(SEL)aSelector;處理接收器無法識別的消息,一般不要重寫!!!
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內容