Swift底層進階--008:Mirror反射 & 錯誤處理

Mirror反射

Mirror(反射):可以動態(tài)獲取類型工扎、成員信息徘钥,在運?時可以調(diào)??法、屬性等?為的特性肢娘。
對于?個純Swift類來說呈础,并不?持直接像OC那樣使用Runtime操作。但Swift標準庫依然提供了反射機制橱健,用來訪問成員信息而钞。

訪問成員信息
class LGTeacher {
    var age: Int = 18
    var name: String = "Zang"
}

var t = LGTeacher()

let mirror = Mirror(reflecting: t)

for pro in mirror.children{
    print("\(pro.label):\(pro.value)")
}

//輸出以下內(nèi)容:
//Optional("age"):18
//Optional("name"):Zang

上述代碼中,Mirror反射的是實例對象的成員信息拘荡,傳入的參數(shù)必須是實例對象

傳入一個類對象Mirror(reflecting: LGTeacher)臼节,編譯報錯

傳入類對象

傳入一個類的類型Mirror(reflecting: LGTeacher.self),獲取不到任何成員信息

傳入類的類型

查看Mirror定義

Mirror是一個結構體

Mirror

Mirrorinit方法珊皿,接收一個Any類型參數(shù)

init

Children是一個AnyCollection辐怕,接收一個泛型Mirror.Child

Children

Mirror.Child是一個元組類型

Mirror.Child

JSON解析
class LGTeacher {
    var age: Int = 18
    var name: String = "Zang"
}

func test(_ obj : Any) -> Any {
    
    let mirror = Mirror(reflecting: obj)
    
    guard !mirror.children.isEmpty else {
        return obj
    }
    
    var keyValue: [String: Any] = [:]
    
    for children in mirror.children {
        
        if let keyName = children.label {
            keyValue[keyName] = test(children.value)
        }
        else {
            print("children.label 為空")
        }
    }
    
    return keyValue
}

var t = LGTeacher()
print(test(t))

//輸出以下內(nèi)容:
//["name": "Zang", "age": 18]

上述代碼中袱巨,成功的將實例對象t轉為字典并輸出鸵隧,但在實際開發(fā)中鹃两,這樣的代碼寫的相對丑陋,下面就來對它做一個簡單的封裝

抽取協(xié)議

我們預期在每一個屬性下都能調(diào)用JSON解析的方法驶兜,所以可以將它抽取成一個協(xié)議扼仲,然后提供一個默認實現(xiàn),讓類遵守協(xié)議

protocol CustomJSONMap {
    func jsonMap() -> Any
}

extension CustomJSONMap{
    func jsonMap() -> Any{

        let mirror = Mirror(reflecting: self)
    
        guard !mirror.children.isEmpty else {
            return self
        }
    
        var keyValue: [String: Any] = [:]
    
        for children in mirror.children {
    
            if let value = children.value as? CustomJSONMap {
                
                if let keyName = children.label {
                    keyValue[keyName] = value.jsonMap()
                }
                else {
                    print("key是nil")
                }
            }
            else {
                print("當前-\(children.value)-沒有遵守協(xié)議")
            }
        }
    
        return keyValue
    }
}

class LGTeacher : CustomJSONMap {
    var age: Int = 18
    var name: String = "Zang"
}

var t = LGTeacher()
print(t.jsonMap())

//輸出以下內(nèi)容:
//當前-18-沒有遵守協(xié)議
//當前-Zang-沒有遵守協(xié)議
//[:]

上述代碼中抄淑,因為agename分別為IntString類型屠凶,這些類并沒有遵守CustomJSONMap協(xié)議,所以無法輸出正確結果

extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}

class LGTeacher: CustomJSONMap {
    var age: Int = 18
    var name: String = "Zang"
}

var t = LGTeacher()
print(t.jsonMap())

//輸出以下內(nèi)容:
//["age": 18, "name": "Zang"]

修改代碼肆资,增加IntString類型的extension矗愧,讓它們也遵守CustomJSONMap協(xié)議,打印結果符合預期

上述代碼在實際開發(fā)中用來做JSON解析還有很大差距迅耘。代碼本身并不完善贱枣,也無法支持復雜的嵌套類型监署,所以它只是基于Mirror做的簡單案例颤专,用來理解Mirror到底能做些什么。

錯誤處理
Error協(xié)議

Swift提供Error協(xié)議來標識當前應?程序發(fā)?錯誤的情況钠乏,不管是struct栖秕、Classenum都可以通過遵循這個協(xié)議來表示?個錯誤晓避。Error的定義如下:

public protocol Error{
}

上述JSON解析案例中簇捍,有兩個print分別打印未遵守協(xié)議key是nil只壳,下面演示如何通過Error協(xié)議處理這兩種異常情況

return
上述代碼中,將JSONMapError.emptyKeyJSONMapError.notConformProtocol進行return暑塑,以此代替之前兩個print吼句。但對于jsonMap方法來說,由于返回值是Any類型事格,故此我們無法區(qū)分返回結果是解析成功的字典惕艳,還是錯誤的枚舉

對于異常情況,可以使用throw關鍵字將錯誤拋出驹愚,將代碼中的return改為throw

throw

上圖使用throw編譯報錯远搪,因為方法還沒有聲明成throws。需要在方法返回值前面增加throws關鍵字逢捺,告訴方法有錯誤拋出

throws

方法使用throws關鍵字修飾谁鳍,調(diào)用該方法的代碼編譯報錯。對于有錯誤拋出的方法劫瞳,需要在調(diào)用方法前使用try關鍵字

enum JSONMapError: Error{
    case emptyKey
    case notConformProtocol
}

protocol CustomJSONMap {
    func jsonMap() throws-> Any
}

extension CustomJSONMap{
    func jsonMap() throws-> Any{

        let mirror = Mirror(reflecting: self)
    
        guard !mirror.children.isEmpty else {
            return self
        }
    
        var keyValue: [String: Any] = [:]
    
        for children in mirror.children {
    
            if let value = children.value as? CustomJSONMap {
                
                if let keyName = children.label {
                    keyValue[keyName] = try value.jsonMap()
                }
                else {
                    throw JSONMapError.emptyKey
                }
            }
            else {
                throw JSONMapError.notConformProtocol
            }
        }
    
        return keyValue
    }
}

extension Int : CustomJSONMap{}
extension String : CustomJSONMap{}

class LGTeacher : CustomJSONMap {
    var age: Int = 18
    var name: String = "Zang"
}

var t = LGTeacher()
print(try t.jsonMap())

到這?一個完整的Swift錯誤表達?式就完成了

try關鍵字

使?try關鍵字是Swift中錯誤處理最簡便的方式倘潜,相當于帥鍋。將異常向上拋出柠新,拋給上層函數(shù)窍荧。使?try關鍵字有兩個注意點:

  • try?:返回?個可選類型,這?的結果要么是成功恨憎,返回具體結果蕊退。要么是錯誤,返回nil憔恳。這種方式我們不關心具體是哪?類錯誤瓤荔,統(tǒng)?返回nil
  • try!:表示你對這段代碼有絕對的?信,這?代碼絕對不會發(fā)?錯誤

IntString遵守CustomJSONMap協(xié)議的代碼注釋钥组,打印try? t.jsonMap()输硝,直接返回nil,看不出具體錯誤原因

try?

相同代碼程梦,使用try! t.jsonMap()測試点把,程序直接閃退

try!

使用try t.jsonMap(),將異常拋給上層函數(shù)屿附,如果全程沒有函數(shù)處理異常郎逃,最終拋給main函數(shù)也沒辦法處理,程序直接閃退

try

do...catch

Swiftdo...catch是錯誤處理的另一種方式

//extension Int : CustomJSONMap{}
//extension String : CustomJSONMap{}

class LGTeacher : CustomJSONMap {
    var age: Int = 18
    var name: String = "Zang"
}

var t = LGTeacher()

do{
    try t.jsonMap()
}catch{
    print(error)
}

//輸出以下內(nèi)容:
//notConformProtocol

上述代碼中挺份,通過do作用域捕獲異常褒翰,通過catch作用域處理異常,最終打印出錯誤類型notConformProtocol

LocalError協(xié)議

如果使?Error協(xié)議不能詳盡表達錯誤信息,可以使?LocalError協(xié)議优训,定義如下:

public protocol LocalizedError : Error {

    //錯誤的描述
    /// A localized message describing what error occurred.
    var errorDescription: String? { get }

    //失敗的原因
    /// A localized message describing the reason for the failure.
    var failureReason: String? { get }

    //恢復的建議
    /// A localized message describing how one might recover from the failure.
    var recoverySuggestion: String? { get }

    //給開發(fā)者的幫助
    /// A localized message providing "help" text if the user requests help.
    var helpAnchor: String? { get }
}

修改JSON解析案例朵你,使用LocalizedError協(xié)議,打印具體的錯誤描述

enum JSONMapError: Error{
    case emptyKey
    case notConformProtocol
}

extension JSONMapError: LocalizedError{
    var errorDescription: String?{
        switch self {
            case .emptyKey:
                return "key為空"
            case .notConformProtocol:
                return "沒有遵守協(xié)議"
        }
    }
}

//extension Int : CustomJSONMap{}
//extension String : CustomJSONMap{}

class LGTeacher : CustomJSONMap {
    var age: Int = 18
    var name: String = "Zang"
}

var t = LGTeacher()

do{
    try t.jsonMap()
}catch{
    print(error.localizedDescription)
}

//輸出以下內(nèi)容:
//沒有遵守協(xié)議

上述代碼中揣非,為JSONMapError增加extension擴展抡医,并遵守LocalizedError協(xié)議,在catch作用域中打印error.localizedDescription早敬,最終輸出錯誤描述:沒有遵守協(xié)議

CustomError協(xié)議

CustomNSError相當于OC中的NSError魂拦,有三個默認屬性:

public protocol CustomNSError : Error {

    /// The domain of the error.
    static var errorDomain: String { get }

    /// The error code within the given domain.
    var errorCode: Int { get }

    /// The user-info dictionary.
    var errorUserInfo: [String : Any] { get }
}

修改JSON解析案例,使用CustomNSError協(xié)議搁嗓,打印錯誤碼

enum JSONMapError: Error{
    case emptyKey
    case notConformProtocol
}

extension JSONMapError: CustomNSError{
    var errorCode: Int{
        switch self {
        case .emptyKey:
            return -1
        case .notConformProtocol:
            return -2
        }
    }
}

//extension Int : CustomJSONMap{}
//extension String : CustomJSONMap{}

class LGTeacher : CustomJSONMap {
    var age: Int = 18
    var name: String = "Zang"
}

var t = LGTeacher()

do{
    try t.jsonMap()
}catch{
    print("\(String(describing: (error as? CustomNSError)?.errorCode))")
}

//輸出以下內(nèi)容:
//Optional(-2)

上述代碼中芯勘,為JSONMapError增加extension擴展,并遵守CustomNSError協(xié)議腺逛,在catch作用域中使用as?關鍵字將error強轉為CustomNSError類型荷愕,并打印errorCode,最終輸出錯誤碼:Optional(-2)

Mirror源碼解析
@_silgen_name關鍵字

@_silgen_name關鍵字用來改變當前方法的調(diào)用方式

swift項目中棍矛,創(chuàng)建test.c安疗,里面聲明lg_add函數(shù),傳入兩個參數(shù)够委,返回參數(shù)相加的結果

lg_add

main.swift中荐类,定義swift_lg_add方法,參數(shù)茁帽、返回值和test.clg_add一致玉罐,并加入@_silgen_name("lg_add")關鍵字聲明,嘗試調(diào)用swift_lg_add方法潘拨,發(fā)現(xiàn)最終會調(diào)用lg_add

swift_lg_add

源碼解析

打開Mirror.swift吊输,可以看到Mirror是一個結構體類型

Mirror

結構體有一個初始化方法,傳入一個Any铁追。這里判斷subject是否符合CustomReflectable類型季蚂,符合由customMirror確定屬性,不符合由系統(tǒng)生成

init

首先看一下由系統(tǒng)生成的Mirror琅束,來到internalReflecting方法定義扭屁,首先通過_getNormalizedType方法獲取類型,再通過_getChildCount方法獲取屬性大小涩禀,最后通過遍歷將屬性存儲到集合中

internalReflecting

搜索_getNormalizedType方法料滥,進入ReflectionMirror.swift文件。該方法用到@_silgen_name關鍵字聲明埋泵,最終會調(diào)用swift_reflectionMirror_normalizedType方法

_getNormalizedType

來到swift_reflectionMirror_normalizedType方法定義幔欧,有三個參數(shù),valueMirror實例丽声,type是調(diào)用時通過type(of:)獲取動態(tài)類型礁蔗,T是自己傳入的類型。最后調(diào)用call方法返回impltype屬性雁社,而implReflectionMirrorImpl結構

swift_reflectionMirror_normalizedType

來到call方法定義浴井,將方法TpassedValue參數(shù),傳給unwrapExistential方法拿到type霉撵。如果傳入的passedType不為空磺浙,就將傳入的passedType賦值給type

call

來到unwrapExistential方法定義,通過getDynamicType方法獲取當前的動態(tài)類型徒坡。通過內(nèi)部方法調(diào)用撕氧,最終發(fā)現(xiàn)獲取類型的本質(zhì)是依賴Metadata元數(shù)據(jù)。

unwrapExistential

找到Metadata喇完,由于類結構的Metadata過于復雜伦泥,我們下面以結構體TargetStructMetadata為例,它繼承于TargetValueMetadata

TargetStructMetadata

來到TargetValueMetadata定義锦溪,它繼承于TargetMetadata不脯,除了繼承下來的kind屬性,還有一個ConstTargetMetadataPointer類型的description屬性刻诊,而description屬性記錄的就是有關元數(shù)據(jù)的描述防楷。ConstTargetMetadataPointer實際是一個泛型,需要重點分析的是TargetValueTypeDescription

TargetValueMetadata

下面介紹元數(shù)據(jù)的描述则涯,進入TargetValueTypeDescription定義复局,它繼承于TargetTypeContextDescription

TargetValueTypeDescription

進入TargetTypeContextDescription定義,里面有一個TargetRelativeDirectPointer類型的Name屬性粟判,用于記錄當前類型的名稱

TargetTypeContextDescription

進入TargetRelativeDirectPointer定義肖揣,它是RelativeDirectPointer類型的別名

TargetRelativeDirectPointer

進入RelativeDirectPointer定義,它是一個模板類浮入,繼承于RelativeDirectPointerImpl龙优,傳入類型TNullabletrue事秀、Offsetint32_t

RelativeDirectPointer

進入RelativeDirectPointerImpl定義彤断,它有一個int32_t類型的RelativeOffset屬性

RelativeDirectPointerImpl

還包含ValueTyPointerTy易迹,其中ValueTy代表T的類型宰衙,PointerTy代表T的指針類型

ValueTy、PointerTy

回到RelativeDirectPointer定義睹欲,獲取值ValueTy供炼、獲取指針PointerTy調(diào)用的都是this->get()方法

獲取ValueTy一屋、PointerTy

進入get方法,又回到RelativeDirectPointerImpl定義袋哼,方法內(nèi)調(diào)用applyRelativeOffset方法

get

進入applyRelativeOffset方法冀墨,返回base + extendOffsetbase是當前指針地址涛贯,extendOffset是相對地址(偏移地址),最終相加得到偏移后的地址存儲的是類型

applyRelativeOffset

回到call函數(shù)诽嘉,可以看到很多依賴都是基于call函數(shù)。比如獲取Class數(shù)據(jù)時弟翘,通過callClass方法得到一個ClassImpl虫腋。如果是元組類型,會得到一個TupleImpl稀余。如果是結構體悦冀,會得到StructImpl

call

以結構體為例睛琳,進入StructImpl定義雏门,繼承于ReflectionMirrorImpl。其中isReflectable方法掸掏,也是通過Metadata獲取getDescriotion茁影,找到isReflectable判斷是否支持反射

isReflectable

count方法,同樣通過Metadata獲取getDescriotion丧凤,找到NumFields獲取屬性總數(shù)

count

subscipt方法募闲,獲取屬性名稱和屬性值。通過getFieldOffests()[i]得到一個屬性的偏移值愿待。通過getFiledAt方法拿到屬性名稱浩螺,bytes是當前value地址,加上fieldOffset偏移地址仍侥,就是屬性的值

subscipt

進入getFiledAt方法要出,通過baseDescFields.get()獲取fields,將fields賦值給FieldDescriptor類型的descriptor农渊,通過descriptor.geyFields[index]獲取一個屬性field患蹂,最終通過field.getFieldName()拿到屬性值的名稱

getFiledAt

Fields也是一個相對指針的存儲,類型是FieldDescriptor

FieldDescriptor

getFieldName方法調(diào)用的是FieldName.get(),在FieldRecord類里

FieldRecord

下面通過Swift代碼仿寫Mirror的實現(xiàn)進行原理解析

原理解析

定義RelativePointer結構體砸紊,仿照RelativeDirectPointerImplget方法传于,實現(xiàn)當前指針的偏移

struct RelativePointer<T> {
    var offset: Int32

    mutating func get() -> UnsafeMutablePointer<T>{
        let offset = self.offset

        return withUnsafePointer(to: &self) { p in
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
        }
    }
}
  • offset:偏移地址
  • UnsafeMutablePointer:轉換為UnsafeMutablePointer類型指針
  • UnsafeRawPointer(p):當前this
  • advanced:移動步長
  • numericCast:位的強轉
  • assumingMemoryBound:假定內(nèi)存綁定為T的類型

定義FieldRecordT結構體,實現(xiàn)類似getFiledAt方法醉顽,在連續(xù)內(nèi)存空間中沼溜,移動步長,拿到每一個FieldRecord

struct FieldRecordT<Element> {
    var element: Element
    mutating func element(at i: Int) -> UnsafeMutablePointer<Element> {
        return withUnsafePointer(to: &self) { p in
            return UnsafeMutablePointer(mutating:  UnsafeRawPointer(p).assumingMemoryBound(to: Element.self).advanced(by: i))
        }
    }
}

定義StructMetadata結構體游添,相當于TargetStructMetadata

struct StructMetadata{
    var kind: Int
    var typeDescriptor: UnsafeMutablePointer<StructDescriptor>
}

定義StructDescriptor結構體系草,相當于TargetValueTypeDescription

struct StructDescriptor {
    let flags: Int32
    let parent: Int32
    var name: RelativePointer<CChar>
    var AccessFunctionPtr: RelativePointer<UnsafeRawPointer>
    var Fields: RelativePointer<FieldDescriptor>
    var NumFields: Int32
    var FieldOffsetVectorOffset: Int32
}

定義FieldDescriptor結構體通熄,相當于FieldDescriptor

struct FieldDescriptor {
    var MangledTypeName: RelativePointer<CChar>
    var Superclass: RelativePointer<CChar>
    var kind: UInt16
    var fieldRecordSize: Int16
    var numFields: Int32
    //連續(xù)的存儲空間
    var fields: FieldRecordT<FieldRecord>
}

定義FieldRecord結構體,相當于FieldRecord

struct FieldRecord {
    var Flags: Int32
    var MangledTypeName: RelativePointer<CChar>
    var FieldName: RelativePointer<CChar>
}

定義LGTeacher結構體

struct LGTeacher{
    var age = 18
    var name = "Zang"
}

使用unsafeBitCast內(nèi)存按位轉換找都,將LGTeacher的類型綁定到StructMetadata

let ptr = unsafeBitCast(LGTeacher.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)

獲取類型

let namePtr = ptr.pointee.typeDescriptor.pointee.name.get()
print("類型:\(String(cString: namePtr))")

//輸出以下結果:
//類型:LGTeacher

獲取屬性大小

let fieldDescriptorPtr = ptr.pointee.typeDescriptor.pointee.Fields.get()
print("屬性大写奖妗:\(ptr.pointee.typeDescriptor.pointee.NumFields)")

//輸出以下結果:
//屬性大小:2

遍歷出所有屬性名稱

for index in 0..<ptr.pointee.typeDescriptor.pointee.NumFields {
    
    let recordPtr = fieldDescriptorPtr.pointee.fields.element(at: Int(index))
    let valOffset=recordPtr.pointee.FieldName.get().pointee

    print("屬性\(index):\(String(cString: recordPtr.pointee.FieldName.get()))")
}

//輸出以下結果:
//屬性0:age
//屬性1:name
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末檐嚣,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子啰扛,更是在濱河造成了極大的恐慌嚎京,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隐解,死亡現(xiàn)場離奇詭異鞍帝,居然都是意外死亡,警方通過查閱死者的電腦和手機煞茫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門帕涌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人续徽,你說我怎么就攤上這事蚓曼。” “怎么了钦扭?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵纫版,是天一觀的道長。 經(jīng)常有香客問我客情,道長其弊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任膀斋,我火速辦了婚禮梭伐,結果婚禮上,老公的妹妹穿的比我還像新娘仰担。我一直安慰自己糊识,他們只是感情好,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布摔蓝。 她就那樣靜靜地躺著技掏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪项鬼。 梳的紋絲不亂的頭發(fā)上哑梳,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天,我揣著相機與錄音绘盟,去河邊找鬼鸠真。 笑死悯仙,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的吠卷。 我是一名探鬼主播锡垄,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼祭隔!你這毒婦竟也來了货岭?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤疾渴,失蹤者是張志新(化名)和其女友劉穎千贯,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搞坝,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡搔谴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了桩撮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敦第。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖店量,靈堂內(nèi)的尸體忽然破棺而出芜果,到底是詐尸還是另有隱情,我是刑警寧澤融师,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布师幕,位于F島的核電站,受9級特大地震影響诬滩,放射性物質(zhì)發(fā)生泄漏霹粥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一疼鸟、第九天 我趴在偏房一處隱蔽的房頂上張望后控。 院中可真熱鬧,春花似錦空镜、人聲如沸浩淘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽张抄。三九已至,卻和暖如春洼怔,著一層夾襖步出監(jiān)牢的瞬間署惯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工镣隶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留极谊,地道東北人诡右。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像轻猖,于是被迫代替她去往敵國和親帆吻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

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