Category 面試題總結(jié)

Category(分類)這一Object-C 2.0之后添加的語言特性微饥,在日常開發(fā)中使用頻率非常高埠胖。而且面試時(shí)Category基本上是都會(huì)涉及到的一個(gè)知識(shí)點(diǎn)你雌。下面羅列一下面試中經(jīng)常會(huì)提出的問題止吐,基本上涵蓋了這個(gè)知識(shí)點(diǎn):

  1. Category和Extension的區(qū)別最住。
  2. Category底層實(shí)現(xiàn)原理
  3. Category的加載處理過程
  4. Category中 + load方法的調(diào)用
  5. Category中 + initialize方法的調(diào)用
  6. Category中l(wèi)oad和initialize方法的區(qū)別
  7. Category中添加成員變量的實(shí)現(xiàn)

1. Category和Extension的區(qū)別超凳。

  • Category是在程序運(yùn)行的時(shí)候愈污,runtime會(huì)將Category的數(shù)據(jù)合并到類信息匯中。
  • Class Extension 是在編譯的時(shí)候轮傍,就已經(jīng)將數(shù)據(jù)包含在類信息中暂雹。

2. Category底層實(shí)現(xiàn)原理

Category編譯之后的底層結(jié)構(gòu)是 struct category_t ,里面存儲(chǔ)著分類的對(duì)象方法创夜,類方法杭跪,屬性,協(xié)議信息。


3. Category的加載處理過程

下面創(chuàng)建了4個(gè)類涧尿,一個(gè)People類和3個(gè)People類的分類(Run系奉、Jump、Eat)姑廉。
這4個(gè)類都實(shí)現(xiàn)了 - instanceMethod這個(gè)實(shí)例方法缺亮。
調(diào)用People的這個(gè)實(shí)例方法,查看打印結(jié)果庄蹋。

@interface People : NSObject
- (void)instanceMethod;
@end

@implementation People
- (void)instanceMethod
{
    NSLog(@"people instanceMethod");
}
@end
@interface People (Run)
- (void)instanceMethod;
@end

@implementation People (Run)
- (void)instanceMethod
{
    NSLog(@"people run instanceMethod");
}
@end
@interface People (Jump)
- (void)instanceMethod;
@end

@implementation People (Jump)
- (void)instanceMethod
{
    NSLog(@"people jump instanceMethod");
}
@end
@interface People (Eat)
- (void)instanceMethod;
@end

@implementation People (Eat)
- (void)instanceMethod
{
    NSLog(@"people eat instanceMethod");
}
@end

查看People類中的方法列表:

#import "People.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        unsigned int count;
        Method *methodList = class_copyMethodList([People class], &count);
        for (int i = 0; i < count; i ++) {
            Method method = methodList[I];
            NSLog(@"%@",NSStringFromSelector(method_getName(method)));
        }
        free(methodList);
    }
    return 0;
}
People class method list

發(fā)現(xiàn)People類中有4個(gè)instanceMethod方法瞬内,分類中的instanceMethod也在People類中。而且這時(shí)沒有調(diào)用People的實(shí)例方法限书,是在runtime運(yùn)行中加載了People類之后虫蝶,Category的所有數(shù)據(jù)插入到了People類中。

下面調(diào)用一下People類的實(shí)例方法:

#import <Foundation/Foundation.h>
#import "People.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        People *people = [[People alloc] init];
        [people instanceMethod];   // 打印結(jié)果為:people run instanceMethod
        
    }
    return 0;

打印結(jié)果為 people run instanceMethod
從結(jié)果來看倦西,調(diào)用People的實(shí)例方法時(shí)調(diào)用了分類的方法能真,也就是所有分類的方法都合并到一個(gè)數(shù)組中,然后插入到原有類的前面扰柠,但是為什么是People (Run)分類覆蓋了實(shí)例方法领炫,而不是其他兩個(gè)幽歼?

在TARGETS中查看一下編譯文件排序:

TARGETS - Compile Sources.png

發(fā)現(xiàn) People+Run.m是最后編譯的画畅。也就是說編譯順序在最后的方法會(huì)排在方法列表的最前面谣辞。

所以Category的加載處理過程是:
1. 通過runtime加載某個(gè)類的所有的Category數(shù)據(jù)。
2. 將所有的Category數(shù)據(jù)(方法劝枣、屬性汤踏、協(xié)議)合并成到一個(gè)大數(shù)組中。這些數(shù)據(jù)后面參與編譯的Category數(shù)據(jù)舔腾,會(huì)保存在數(shù)組的前面溪胶。
3. 將合并后的分類數(shù)據(jù)(方法、屬性稳诚、協(xié)議)插入到類的原來的數(shù)據(jù)的前面哗脖。


4. Category中 + load方法的調(diào)用

- Category有l(wèi)oad方法。
- load方法在Runtime加載類扳还、分類時(shí)就會(huì)調(diào)用才避。
- 每個(gè)類、分類在程序運(yùn)行過程中氨距,只調(diào)用一次load方法桑逝。

創(chuàng)建6個(gè)類,之間的關(guān)系是:
Animal : NSObject
People : NSObject
Student : People
People Category : People+Run , People+Jump , People+Eat

Animal 衔蹲、 People 繼承自 NSObject;
Student 繼承自People
People+Run , People+Jump , People+Eat 是People的分類

分別實(shí)現(xiàn)一下load方法:

@implementation Animal
+ (void)load
{
    NSLog(@"animal load method");
}
@end
@implementation People
+ (void)load
{
    NSLog(@"people load method");
}
@end
@interface Student : People

@end

@implementation Student
+ (void)load
{
    NSLog(@"student load method");
}
@end
@implementation People (Run)
+ (void)load
{
    NSLog(@"people run load method");
}
@end
@implementation People (Jump)
+ (void)load
{
    NSLog(@"people jump load method");
}
@end
@implementation People (Eat)
+ (void)load
{
    NSLog(@"people eat load method");
}
@end

然后在main.m中不引入類的頭文件:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {

    }
    return 0;
}

類的編譯順序是:

編譯順序

按照之前的思路,打印的順序應(yīng)該是:
student舆驶、jump橱健、animal、eat沙廉、People拘荡、run
或者是:
run、People撬陵、eat珊皿、animal、jump巨税、student

但是打印結(jié)果不是這樣蟋定,打印出結(jié)果:

+ load method result

原因是調(diào)用+load 方法不是通過消息發(fā)送機(jī)制(objc_msgSend),而是根據(jù)內(nèi)存中函數(shù)地址直接調(diào)用草添。而且是在runtime加載類驶兜、分類時(shí)調(diào)用。

+load方法調(diào)用順序總結(jié)如下:

  • +load方法時(shí)在runtime加載類远寸、分類的時(shí)候調(diào)用抄淑。
  • 每個(gè)類、分類的+load方法在程序運(yùn)行中只調(diào)用一次
  1. 先調(diào)用類的+load方法
    1.1 調(diào)用類的+load方法時(shí)驰后,按照編譯先后順序調(diào)用(先調(diào)用Student再調(diào)用Animal)
    1.2 調(diào)用子類的+load方法時(shí)肆资,先調(diào)用父類的+load方法(調(diào)用Student時(shí),先調(diào)用People灶芝,再調(diào)用Student)
    于是調(diào)用順序是:People郑原、Student、Animal
  2. 再調(diào)用分類的+load方法
    2.1 調(diào)用分類+load方法時(shí)监署,按照編譯先后順序調(diào)用

PS. 如果是手動(dòng)調(diào)用 load方法颤专,則會(huì)觸發(fā)消息機(jī)制(objc_msgSend)調(diào)用。按照消息機(jī)制調(diào)用順序執(zhí)行钠乏。但是一般不會(huì)手動(dòng)調(diào)用load方法栖秕。


5. Category中+ initialize方法的調(diào)用

+initialize是在類第一次接收消息時(shí)調(diào)用的。

創(chuàng)建幾個(gè)類晓避,他們之間的關(guān)系是:
People : NSObject
Student : People
People Category : People+Run , People+Jump , People+Eat

People 繼承自 NSObject簇捍;
Student 繼承自People
People+Run , People+Jump , People+Eat 是People的分類

分別實(shí)現(xiàn) + initialize 方法:

@interface People : NSObject
@end

@implementation People
+(void)initialize
{
    NSLog(@"people initialize");
}
@end
@interface Student : People
@end

@implementation Student
+(void)initialize
{
    NSLog(@"student initialize");
}
@end
@implementation People (Run)
+(void)initialize
{
    NSLog(@"people run initialize");
}
@end
@implementation People (Jump)
+(void)initialize
{
    NSLog(@"people jump initialize");
}
@end
@implementation People (Eat)
+(void)initialize
{
    NSLog(@"people eat initialize");
}
@end

分別調(diào)用People的alloc方法和Student的alloc方法:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [People alloc];
    }
    return 0;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [Student alloc];
    }
    return 0;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 分別調(diào)用People和Student的alloc
        [People alloc]; 
        [Student alloc];
    }
    return 0;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 調(diào)用一次People allocation,三次Student allocation
        [People alloc];
        [Student alloc];
        [Student alloc];
        [Student alloc];
    }
    return 0;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 調(diào)用三次Student allocation
        [Student alloc];
        [Student alloc];
        [Student alloc];
    }
    return 0;
}

編譯的順序是:

initialize類編譯順序
// 打印結(jié)果
[People alloc];
 --> people run initialize

[Student alloc]; 
--> people run initialize 
--> student initialize

[People alloc];
[Student alloc]; 
--> people run initialize 
--> student initialize

[People alloc];
[Student alloc]; 
[Student alloc]; 
[Student alloc]; 
--> people run initialize 
--> student initialize

[Student alloc]; 
[Student alloc]; 
[Student alloc]; 
--> people run initialize 
--> student initialize

發(fā)現(xiàn)有幾個(gè)現(xiàn)象:

  • 調(diào)用People alloc時(shí)打印的是People分類Run的 initialize方法
  • 調(diào)用Student alloc時(shí)打印的是People分類Run的initialize方法和Student initialize方法
  • 調(diào)用People 和 Student的alloc時(shí)打印的還是和調(diào)用Student alloc一樣的結(jié)果
  • 多次調(diào)用Student alloc時(shí)打印的結(jié)果和調(diào)用一次Student alloc的一樣

所以得出以下幾個(gè)結(jié)論:

  • +initialize是類第一次接收消息的時(shí)候調(diào)用
  • +initialize是通過objc_msgSend(消息機(jī)制)調(diào)用俏拱,所以分類方法會(huì)覆蓋類方法
  • 調(diào)用子類(Student)的+initialize方法時(shí)底層會(huì)先調(diào)用父類(People)的+initialize方法暑塑,再調(diào)用子類的方法
    objc_msgSend([People class], @selector(initialize));
    objc_msgSend([People class], @selector(initialize));
  • 每個(gè)類只會(huì)初始化一次(只調(diào)用一次initialize),多次接收消息只調(diào)用一次+initialize方法

因?yàn)? initialize是通過objc_msgSend調(diào)用的锅必,所以會(huì)有以下特點(diǎn):

  • 如果子類沒有實(shí)現(xiàn) + initialize方法事格,會(huì)調(diào)用父類的 + initialize方法惕艳。所以當(dāng)多個(gè)子類都沒有實(shí)現(xiàn) + initialize方法的話,會(huì)多次調(diào)用父類 + initialize方法驹愚。

  • 當(dāng)分類實(shí)現(xiàn)了 + initialize方法远搪,會(huì)覆蓋類本身的 + initialize方法調(diào)用。因?yàn)镃ategory的加載過程是將所有的Category的方法逢捺、屬性谁鳍、協(xié)議信息合成一個(gè)大數(shù)組,再將這個(gè)大數(shù)組插入到類信息的前面劫瞳。Category中編譯越靠后越優(yōu)先調(diào)用倘潜。


6. Category中l(wèi)oad和initialize方法的區(qū)別

Category 中 + load 和 + initialize 方法的區(qū)別總結(jié)如下:

調(diào)用方式
  1. +load是根據(jù)方法函數(shù)的內(nèi)存地址直接調(diào)用
  2. +initialize是通過objc_msgSend調(diào)用
調(diào)用時(shí)刻
  1. +load是runtime加載類、分類時(shí)調(diào)用(只會(huì)調(diào)用一次)
  2. +initialize是類第一次接收消息時(shí)調(diào)用志于,每一個(gè)類只會(huì)初始化(initialize)一次涮因,但是父類的+ initialize方法可能會(huì)調(diào)用多次。
調(diào)用順序
  1. +load
    1.1 先調(diào)用類的+load方法
    編譯越早恨憎,調(diào)用越早
    調(diào)用子類的+load方法時(shí)蕊退,先調(diào)用父類的+load方法
    1.2 再調(diào)用分類的+load方法
    編譯越早,調(diào)用越早

  2. +initialize
    2.1 先初始化父類
    2.2 再初始化子類憔恳,若子類沒有實(shí)現(xiàn)+initialize方法瓤荔,最終還是會(huì)調(diào)用父類的+initialize方法
    2.3 如果分類實(shí)現(xiàn)了+initialize方法,會(huì)覆蓋類的+initialize方法钥组。編譯越晚输硝,調(diào)用越早。


7. Category中添加成員變量的實(shí)現(xiàn)

一個(gè)類中如果寫一個(gè)屬性的話程梦,編譯器會(huì)自動(dòng)做3件事情:

  1. 生成一個(gè)成員變量
  2. 生成成員變量的getter点把、setter聲明
  3. 生成getter和setter的實(shí)現(xiàn)

但是如果在一個(gè)分類中寫一個(gè)屬性,編譯器只會(huì)做1件事情:

  1. 生成getter和setter的聲明

根據(jù)分類的結(jié)構(gòu)屿附,不能直接給分類添加一個(gè)成員變量郎逃,但是可以間接實(shí)現(xiàn)分類有成員變量的效果:使用關(guān)聯(lián)對(duì)象(Association Object)。

關(guān)聯(lián)對(duì)象是runtime中的方法挺份,使用時(shí)需要引入<objc/runtime.h>

關(guān)聯(lián)對(duì)象主要的方法有3個(gè):

  1. 設(shè)置關(guān)聯(lián)對(duì)象
    OBJC_EXPORT void
    objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)

返回類型為 void褒翰,其中有4個(gè)參數(shù):
id _Nonnull object : 給哪一個(gè)對(duì)象添加關(guān)聯(lián)對(duì)象
const void * _Nonnull key :傳入一個(gè)指針進(jìn)去,接收的是地址值
id _Nullable value :關(guān)聯(lián)什么值
objc_AssociationPolicy policy :關(guān)聯(lián)的策略

關(guān)聯(lián)策略:

objc_AssociationPolicy :

// 給關(guān)聯(lián)對(duì)象指向一個(gè)弱引用
OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
// 給關(guān)聯(lián)對(duì)象指向一個(gè)強(qiáng)引用匀泊,這個(gè)關(guān)聯(lián)對(duì)象是非原子性
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
// 給關(guān)聯(lián)對(duì)象指向copy优训,這個(gè)關(guān)聯(lián)對(duì)象是非原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
// 給關(guān)聯(lián)對(duì)象指向一個(gè)強(qiáng)引用,這個(gè)關(guān)聯(lián)對(duì)象是原子性
OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
// 給關(guān)聯(lián)對(duì)象指向copy各聘,這個(gè)關(guān)聯(lián)對(duì)象是非原子性
OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

// 關(guān)聯(lián)對(duì)象策略對(duì)應(yīng)的修飾符:
// 關(guān)聯(lián)對(duì)象策略中沒有weak修飾符揣非,沒有弱引用這種效果
OBJC_ASSOCIATION_ASSIGN            === assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC  === strong,nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC    === copy躲因,nonatomic
OBJC_ASSOCIATION_RETAIN            === strong早敬,atomic
OBJC_ASSOCIATION_COPY              === copy忌傻,atomic
  1. 獲取關(guān)聯(lián)對(duì)象
    OBJC_EXPORT id _Nullable
    objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

返回類型為 id,其中有2個(gè)參數(shù):
id _Nonnull object : 獲取哪一個(gè)對(duì)象的關(guān)聯(lián)對(duì)象
const void * _Nonnull key :傳入一個(gè)指針進(jìn)去搞监,接收的是地址值

  1. 移除關(guān)聯(lián)對(duì)象
    OBJC_EXPORT void
    objc_removeAssociatedObjects(id _Nonnull object)

返回類型為 void芯勘,其中有1個(gè)參數(shù):
id _Nonnull object : 移除哪一個(gè)對(duì)象的所有關(guān)聯(lián)對(duì)象

其他3個(gè)參數(shù)比較明了,說一下key這個(gè)參數(shù)的用法腺逛,一般key的常見用法有4種:

  1. static void *myKey = &myKey;
- (void)setAge:(int)age
{
    objc_AssociationPolicy policy = OBJC_ASSOCIATION_ASSIGN;
    objc_setAssociatedObject(self, myKey, @(age), policy);
}

- (int)age
{
    return [objc_getAssociatedObject(self, myKey) intValue];
}
  1. static char myKey;
- (void)setAge:(int)age
{
    objc_AssociationPolicy policy = OBJC_ASSOCIATION_ASSIGN;
    objc_setAssociatedObject(self, &myKey, @(age), policy);
}

- (int)age
{
    return [objc_getAssociatedObject(self, &myKey) intValue];
}
  1. 直接使用屬性名作為key
    使用屬性名可以防止名稱沖突,而且每一個(gè)不同的字符串的地址不一樣
- (void)setAge:(int)age
{
    objc_AssociationPolicy policy = OBJC_ASSOCIATION_ASSIGN;
    objc_setAssociatedObject(self, @"age", @(age), policy);
}

- (int)age
{
    return [objc_getAssociatedObject(self, @"age") intValue];
}
  1. 使用get方法的@selector作為key
- (void)setAge:(int)age
{
    objc_AssociationPolicy policy = OBJC_ASSOCIATION_ASSIGN;
    objc_setAssociatedObject(self, @selector(age), @(age), policy);
}

- (int)age
{
    return [objc_getAssociatedObject(self, @selector(age)) intValue];
}

// 在getter中可以使用隱式參數(shù)_cmd衡怀,_cmd對(duì)應(yīng)當(dāng)前方法的selector
- (void)setAge:(int)age
{
    objc_AssociationPolicy policy = OBJC_ASSOCIATION_ASSIGN;
    objc_setAssociatedObject(self, @selector(age), @(age), policy);
}

- (int)age
{
    return [objc_getAssociatedObject(self, _cmd) intValue];
}

這樣就可以在分類中實(shí)現(xiàn)有成員變量的效果:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        People *people  = [[People alloc] init];
        people.age = 10;
        
        NSLog(@"age = %d",people.age); // age = 10
    }
    return 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棍矛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子抛杨,更是在濱河造成了極大的恐慌够委,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怖现,死亡現(xiàn)場離奇詭異茁帽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)屈嗤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門潘拨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人饶号,你說我怎么就攤上這事铁追。” “怎么了茫船?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵琅束,是天一觀的道長。 經(jīng)常有香客問我算谈,道長涩禀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任然眼,我火速辦了婚禮艾船,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罪治。我一直安慰自己丽声,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布觉义。 她就那樣靜靜地躺著雁社,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晒骇。 梳的紋絲不亂的頭發(fā)上霉撵,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天磺浙,我揣著相機(jī)與錄音,去河邊找鬼徒坡。 笑死撕氧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喇完。 我是一名探鬼主播伦泥,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锦溪!你這毒婦竟也來了不脯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤刻诊,失蹤者是張志新(化名)和其女友劉穎防楷,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體则涯,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡复局,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粟判。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亿昏。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖档礁,靈堂內(nèi)的尸體忽然破棺而出龙优,到底是詐尸還是另有隱情,我是刑警寧澤事秀,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布彤断,位于F島的核電站,受9級(jí)特大地震影響易迹,放射性物質(zhì)發(fā)生泄漏宰衙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一睹欲、第九天 我趴在偏房一處隱蔽的房頂上張望供炼。 院中可真熱鬧,春花似錦窘疮、人聲如沸袋哼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涛贯。三九已至,卻和暖如春蔚出,著一層夾襖步出監(jiān)牢的瞬間弟翘,已是汗流浹背虫腋。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留稀余,地道東北人悦冀。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像睛琳,于是被迫代替她去往敵國和親盒蟆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

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