KVC&KVO

鍵值編碼是由NSKeyValueCoding非正式協(xié)議啟用的一種機制,對象采用該協(xié)議來提供對其屬性的間接訪問。當對象符合鍵值編碼時砍聊,其屬性可以通過簡潔、統(tǒng)一的消息傳遞接口通過字符串參數(shù)進行尋址贰军。這種間接訪問機制補充了實例變量及其關聯(lián)訪問器方法提供的直接訪問玻蝌。
您通常使用訪問器方法來訪問對象的屬性。get訪問器(或getter)返回屬性的值词疼。集合訪問器(或設置器)設置屬性的值俯树。在Objective-C中,您還可以直接訪問屬性的基礎實例變量贰盗。以上述任何一種方式訪問對象屬性都很簡單许饿,但需要調(diào)用特定于屬性的方法或變量名稱。隨著屬性列表的增長或變化舵盈,訪問這些屬性的代碼也必須增長或變化陋率。相比之下球化,符合鍵值編碼的對象提供了一個簡單的消息傳遞界面,該界面在其所有屬性中都一致翘贮。KVC是一個基本概念赊窥,是許多其他可可技術的基礎,如KVC狸页、可可綁定锨能、核心數(shù)據(jù)和AppleScript可性。在某些情況下芍耘,鍵值編碼也有助于簡化代碼址遇。

實現(xiàn)機制(摘自蘋果文檔,下面有干貨非純文檔)

valueforkey

valueForKey:的默認實現(xiàn),給定一個key參數(shù)作為輸入斋竞,執(zhí)行以下過程倔约,從接收valueForKey:調(diào)用的類實例中操作。

  1. 按順序在實例中搜索第一個訪問器方法坝初,名稱如get<Key>浸剩、<key>is<Key>_<key>鳄袍。如果找到绢要,請調(diào)用它,然后繼續(xù)第5步并附上結果拗小。否則重罪,請繼續(xù)下一步。

  2. 如果沒有找到簡單的訪問器方法哀九,請在實例中搜索名稱與模式countOf<Key>objectIn<Key>AtIndex:(對應NSArray類定義的原始方法)和<key>AtIndexes:(對應NSArray方法objectsAtIndexes:)匹配的方法剿配。

    如果找到其中第一個和至少兩個中的一個,請創(chuàng)建一個響應所有NSArray方法的集合代理對象并返回該對象阅束。否則呼胚,請繼續(xù)第3步。

    代理對象隨后將其收到的任何NSArray消息轉換為countOf<Key>息裸、objectIn<Key>AtIndex:<key>AtIndexes:消息的某種組合砸讳,轉換為創(chuàng)建它的鍵值編碼兼容對象。如果原始對象還實現(xiàn)了名為get<Key>:range:的可選方法界牡,則代理對象也會在適當時使用該方法簿寂。實際上,代理對象與鍵值編碼兼容對象一起工作宿亡,允許基礎屬性的行為就像NSArray一樣常遂,即使它不是。

  3. If no simple accessor method or group of array access methods is found, look for a triple of methods named countOf<Key>, enumeratorOf<Key>, and memberOf<Key>: (corresponding to the primitive methods defined by the NSSet class).

    如果找到所有三種方法挽荠,請創(chuàng)建一個響應所有NSSet方法的集合代理對象并返回該對象克胳。否則平绩,請繼續(xù)第4步。

    此代理對象隨后將其接收的任何NSSet消息轉換為countOf<Key>漠另、enumeratorOf<Key>memberOf<Key>:消息的某種組合到創(chuàng)建它的對象捏雌。實際上,代理對象與鍵值編碼兼容對象一起工作笆搓,允許基礎屬性的行為就像NSSet一樣性湿,即使它不是。

  4. 如果沒有找到簡單的訪問器方法或集合訪問方法組满败,并且接收器的類方法accessInstanceVariablesDirectly返回YES肤频,請按此順序搜索名為_<key>_is<Key>算墨、<key>is<Key>的實例變量宵荒。如果找到,請直接獲取實例變量的值净嘀,然后轉到第5步报咳。否則,請繼續(xù)第6步挖藏。

  5. 如果檢索到的屬性值是對象指針暑刃,只需返回結果。 如果該值是NSNumber支持的標量類型熬苍,請將其存儲在NSNumber實例中并返回稍走。
    如果結果是NSNumber不支持的標量類型袁翁,請轉換為NSValue對象并返回該對象柴底。

  6. 如果所有其他方法都失敗了,請調(diào)用valueForUndefinedKey:粱胜。默認情況下柄驻,這會引發(fā)異常,但NSObject的子類可能會提供特定于密鑰的行為焙压。

setValueForKey

  1. 按順序查找第一個名為set<Key>:_set<Key>的訪問器鸿脓。如果找到,請使用輸入值(或根據(jù)需要打開的值)調(diào)用它涯曲,然后完成野哭。

  2. 如果沒有找到簡單的訪問器,并且類方法accessInstanceVariablesDirectly返回YES幻件,請按順序查找名稱為_<key>拨黔、_is<Key><key>is<Key>的實例變量绰沥。如果找到篱蝇,請直接使用輸入值(或未包裝的值)設置變量并完成贺待。

  3. 找不到訪問器或實例變量時,調(diào)用setValue:forUndefinedKey:零截。默認情況下麸塞,這會引發(fā)異常,但NSObject的子類可能會提供特定于密鑰的行為涧衙。

Array/Set

mutableOrderedSetValueForKey/mutableSetValueForKey:的默認實現(xiàn):識別與valueForKey相同的簡單訪問器方法和有序集訪問器方法:哪工,并遵循相同的直接實例變量訪問策略,但總是返回可變集合代理對象绍撞,而不是valueForKey:返回的不可變集合正勒。此外,它還執(zhí)行以下操作:

  1. 搜索名稱如下的方法insertObject:in<Key>AtIndex: and removeObjectFrom<Key>AtIndex: (對應于 NSMutableOrderedSetclass), 而且insert<Key>:atIndexes:remove<Key>AtIndexes:`(對應 insertObjects:atIndexes:removeObjectsAtIndexes:).

如果找到至少一種插入方法和至少一種刪除方法傻铣,則返回的代理對象將發(fā)送以下方法的組合: insertObject:in<Key>AtIndex:, removeObjectFrom<Key>AtIndex:, insert<Key>:atIndexes:, and remove<Key>AtIndexes: 發(fā)送給原始收件人的消息mutableOrderedSetValueForKey: 收到“NSMutableOrderedSet”消息時顯示消息章贞。

代理對象還使用名稱為replaceObjectIn<Key>AtIndex:withObject:replace<Key>AtIndexes:with<Key>:等名稱的方法,當它們存在于原始對象中時非洲。

  1. 如果沒有找到可變集方法鸭限,搜索名稱為set<Key>:的訪問器方法。在這種情況下两踏,返回的代理對象每次收到NSMutableOrderedSet消息時都會向原始接收器發(fā)送set<Key>:消息败京。

  2. 如果既沒有找到可變集消息也沒有找到訪問器,并且接收器的accessInstanceVariablesDirectly類方法返回YES梦染,請按照此順序搜索名稱為_<key><key>的實例變量赡麦。如果找到此類實例變量,返回的代理對象會將其收到的任何NSMutableOrderedSet消息轉發(fā)到實例變量的值帕识,該值通常是NSMutableOrderedSet或其子類之一的實例泛粹。

  3. 如果所有其他操作都失敗,則返回的代理對象將發(fā)送
    setValue:forUndefinedKey:發(fā)送給原始收件人的消息mutableOrderedSetValueForKey:每當它收到可變集合消息時肮疗。
    setValue:forUndefinedKey:的默認實現(xiàn)會引發(fā)NSUndefinedKeyException晶姊,但對象可能會覆蓋此行為。

自定義實現(xiàn)KVC

因為蘋果內(nèi)部閉源所以根據(jù)原理猜測他的實現(xiàn)方式


- (BOOL)performSelectorWithMethodName:(NSString *)aSelector value:(id)anArgument {
    SEL func = NSSelectorFromString(aSelector);
    if ([self respondsToSelector:func]) {
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        [self performSelector:func withObject:anArgument];
        _Pragma("clang diagnostic pop")

        return YES;
    }
    return NO;
}



-(void)jl_setValue:(nullable id)value forKey:(NSString *)key {
    
    //key非空判斷
    if (!key || key.length == 0) {
        return;
    }
    
    //找到相關方法 set<Kety> _set<Key> setIs<Key>
    //Key要大寫
    NSString * Key = key.capitalizedString;
    
    NSString * setKey = [NSString stringWithFormat:@"set%@:",Key];
    NSString * _setKey = [NSString stringWithFormat:@"_set%@:",Key];
    NSString * setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
    
    if ([self performSelectorWithMethodName:setKey value:value]) {
        return;
    }
    if ([self performSelectorWithMethodName:_setKey value:value]) {
        return;
    }
    if ([self performSelectorWithMethodName:setIsKey value:value]) {
        return;
    }
    
    //判斷當前是否能否直接復制示例變量
    if ([self.class accessInstanceVariablesDirectly]) {
        @throw  [NSException exceptionWithName:@"JLUnknownKeyException" reason:[NSString stringWithFormat:@"***[%@ valueForUndefineKey:]: this class is not key value codeing-comliant for the key name.****",self] userInfo:nil];
        return;
    }
    
    //4. 找相關示例變量進行賦值
    //4.1定一個收集實例變量的可變數(shù)組
    NSMutableArray * mArray = [self getIvarListName];
    //_<key> _is<Key> <key> is<Key>
    
    NSString * _Key = [NSString stringWithFormat:@"_%@:",Key];
    NSString * _isKey = [NSString stringWithFormat:@"_is%@:",Key];
    NSString * isKey = [NSString stringWithFormat:@"is%@:",Key];
    
    NSArray * keyarr = @[_Key,_isKey,key,isKey];
    for (NSString * ikey in keyarr) {
        if ([mArray containsObject:ikey]) {
            Ivar ivar = class_getInstanceVariable([self class], ikey.UTF8String);
            object_setIvar(self, ivar, value);
            return;
        }
    }
    
    @throw  [NSException exceptionWithName:@"JLUnknownKeyException" reason:[NSString stringWithFormat:@"***[%@ valueForUndefineKey:]: this class is not key value codeing-comliant for the key name.****",self] userInfo:nil];
}

-(nullable id)jl_valueforKey:(NSString *)key {
    // 1:刷選key 判斷非空
    if (key == nil  || key.length == 0) {
        return nil;
    }
    // 2:找到相關方法 get<Key> <key> countOf<Key>  objectIn<Key>AtIndex
    // key 要大寫
    NSString *Key = key.capitalizedString;
    // 拼接方法
    NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
    NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
    NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
    
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
        return [self performSelector:NSSelectorFromString(getKey)];
    }else if ([self respondsToSelector:NSSelectorFromString(key)]){
        return [self performSelector:NSSelectorFromString(key)];
    }else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
        if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
            int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
            NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
            for (int i = 0; i<num-1; i++) {
                num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
            }
            for (int j = 0; j<num; j++) {
                id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
                [mArray addObject:objc];
            }
            return mArray;
        }
    }
#pragma clang diagnostic pop
    
    return nil;
}

上面代碼是根據(jù)kvc文檔簡單的猜測了他內(nèi)部實現(xiàn)了原理伪货。但是他還有個進階用法们衙,其實上面的原理上面也寫到了

請參考下列代碼

class Person: NSObject {
    var penArr:[String] = []
}


 Person *p = [Person new];
    p.penArr = [NSMutableArray arrayWithObjects:@"pen0", @"pen1", @"pen2", @"pen3", nil];
    NSArray *arr = [p valueForKey:@"pens"]; // 動態(tài)成員變量
    NSLog(@"pens = %@", arr);
    //NSLog(@"%@",arr[0]);
    NSLog(@"%d",[arr containsObject:@"pen9"]);
    

正常情況下因為沒有pens這個屬性調(diào)用肯定會崩潰,但是如果進行一步騷操作就不會崩了這個操作就是實現(xiàn)對應的


// 個數(shù)
- (NSUInteger)countOfPens {
    return [self.penArr count];
}

//// 獲取值
- (id) objectInPensAtIndex:(NSUInteger)index {
    return [NSString stringWithFormat:@"pens %lu", index];
}

// 是否包含這個成員對象
- (id)memberOfPens:(id)object {
    return [self.penArr containsObject:object] ? object : nil;
}

// 迭代器
- (id)enumeratorOfPens {
    // objectEnumerator
    return [self.penArr reverseObjectEnumerator];
}

將該key映射到原來的消息體中碱呼。這樣就實現(xiàn)了kvc的騷操作蒙挑。
不得不說看文檔還是牛逼啊,以后要多看文檔可以發(fā)現(xiàn)許多牛逼的東西

KVO

當我們對A類添加監(jiān)聽的時候愚臀,系統(tǒng)會自動生成一個NSKVONotifying_A的子類忆蚀,這個類重寫了A的class、superclass、deealloc方法和該屬性的Set方法蜓谋,同時A類的對象的isa指針指向了該虛擬子類梦皮。當監(jiān)聽屬性改變的時候系統(tǒng)調(diào)用NSSetobjectValueandNotify,這個方法的執(zhí)行流程是(willchangeValueforkey->改變父類的值->didchangeValueforkey->observeValueForKey:ofObject:change:context:),如果設置*automaticallyNotifiesObserversForKey:(NSString )key為NO的時候則需要手動觸發(fā)KVO即手動調(diào)用willchangeValueforkey和didchangeValueforkey.

func _NSSetObjectValueAndNotify {
    ...
    willchangeValueforkey
    ...
    "
    objc_msgSendSupper '改變父類的值(猜測這樣實現(xiàn))
    "
    ...
    didchangeValueforkey
    ...
    observeValueForKey:ofObject:change:context: 
}

Q1: 為什么重寫系統(tǒng)的class/superclass桃焕、deealloc方法

因為該子類為系統(tǒng)自動生成蘋果想偽裝成并沒有這個類 所以重寫class/superclass 剑肯,但是調(diào)用 objc_getClass()這個方法時候依然會暴露,因為這個方法是調(diào)用調(diào)用對象的isa指針指向观堂。dealloc則是系統(tǒng)還有一些其他的事情處理

自定義KVO

自定義KVO需要遵守KVO的幾個基本原理让网,下面方法只是簡單實現(xiàn)了一個kvo,存在context判斷以及多個observer都存在的時候處理师痕。這個都需要加判斷

1.重寫他的set方法所以當調(diào)用addObserver的時候需要判斷當前類有沒有實現(xiàn)set<KeyPath>方法
2.生成NSKVONotifying_XX的派生類
3. 重寫父類的class方法溃睹,setKeyPath,dealloc方法

#pragma mark - 從get方法獲取set方法的名稱 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter){
    
    if (getter.length <= 0) { return nil;}
    
    NSString *firstString = [[getter substringToIndex:1] uppercaseString];
    NSString *leaveString = [getter substringFromIndex:1];
    
    return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}

#pragma mark - 從set方法獲取getter方法的名稱 set<Key>:===> key
static NSString *getterForSetter(NSString *setter){
    
    if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
    
    NSRange range = NSMakeRange(3, setter.length-4);
    NSString *getter = [setter substringWithRange:range];
    NSString *firstString = [[getter substringToIndex:1] lowercaseString];
    return  [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}


- (Class)createChildClassWithKeyPath:(NSString *)keyPath{

    NSString * oldName = NSStringFromClass([self class]);
    //動態(tài)生成子類
    NSString * newClassName = [NSString stringWithFormat:@"%@%@",kJLKVOPrefix,oldName];
    
    Class newClass = NSClassFromString(newClassName);
    if (newClass) {///判斷類是否已經(jīng)被注冊
        return newClass;
    }
    
    /**
     * 如果內(nèi)存不存在,創(chuàng)建生成
     * 參數(shù)一: 父類
     * 參數(shù)二: 新類的名字
     * 參數(shù)三: 新類的開辟的額外空間
     */
    // 2.1 : 申請類
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 2.2 : 注冊類
    objc_registerClassPair(newClass);
    
    // 2.3.1 : 添加class : class的指向是父類
    SEL classSel = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class], classSel);
    const char * classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSel, (IMP)jl_class, classTypes);
    
    // 2.3.2 : 添加setter
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)jl_setter, setterTypes);
    
    return newClass;
}

- (void)jl_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
    //1.驗證是否存在示例方法不讓示例進來
    [self judgeSetterMethodFromKeyPath:keyPath];
    
    // 2.動態(tài)生成子類
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    
    // 3. isa的指向 : KVONotifying_Person
    object_setClass(self, newClass);
    // 4: 保存觀察者
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kJLKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

- (void)jl_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    // 指回給父類
    Class superClass = [self class];
    object_setClass(self, superClass);
}


static void jl_setter(id self,SEL _cmd,id newValue){
    NSLog(@"來了:%@",newValue);
    // 4: 消息轉發(fā) : 轉發(fā)給父類
    // 改變父類的值 --- 可以強制類型轉換
    
    void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    // void /* struct objc_super *super, SEL op, ... */
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self)),
    };
    //objc_msgSendSuper(&superStruct,_cmd,newValue)
    lg_msgSendSuper(&superStruct,_cmd,newValue);
    
    // 既然觀察到了,下一步不就是回調(diào) -- 讓我們的觀察者調(diào)用
    // - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    // 1: 拿到觀察者
    id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kJLKVOAssiociateKey));
    
    // 2: 消息發(fā)送給觀察者
    SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
//    objc_msgSend(observer,observerSEL);
   objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
//    [self observeValueForKeyPath:keyPath ofObject:nil change:@{keyPath:newValue} context:nil];
//    objc_msgSend(self);
}

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胰坟,一起剝皮案震驚了整個濱河市因篇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌笔横,老刑警劉巖竞滓,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钠龙,死亡現(xiàn)場離奇詭異稠鼻,居然都是意外死亡,警方通過查閱死者的電腦和手機搔驼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門厢塘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茶没,“玉大人,你說我怎么就攤上這事晚碾∽グ耄” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵迄薄,是天一觀的道長琅关。 經(jīng)常有香客問我煮岁,道長讥蔽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任画机,我火速辦了婚禮冶伞,結果婚禮上,老公的妹妹穿的比我還像新娘步氏。我一直安慰自己响禽,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芋类,像睡著了一般隆嗅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上侯繁,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天胖喳,我揣著相機與錄音,去河邊找鬼贮竟。 笑死丽焊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的咕别。 我是一名探鬼主播技健,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼惰拱!你這毒婦竟也來了雌贱?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤偿短,失蹤者是張志新(化名)和其女友劉穎帽芽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翔冀,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡导街,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了纤子。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搬瑰。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖控硼,靈堂內(nèi)的尸體忽然破棺而出泽论,到底是詐尸還是另有隱情,我是刑警寧澤卡乾,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布翼悴,位于F島的核電站,受9級特大地震影響幔妨,放射性物質發(fā)生泄漏鹦赎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一误堡、第九天 我趴在偏房一處隱蔽的房頂上張望古话。 院中可真熱鬧,春花似錦锁施、人聲如沸陪踩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肩狂。三九已至摘完,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間傻谁,已是汗流浹背描焰。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留栅螟,地道東北人荆秦。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像力图,于是被迫代替她去往敵國和親步绸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

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